如何捕获SIGINT信号以中断waitpid()函数?
实现SIGINT打断waitpid并继续执行的方案
刚好之前做过类似的需求,核心要抓住信号会打断阻塞的系统调用这个特性——waitpid()就是典型的会被信号打断的函数,当它被信号中断时会返回-1,同时errno被设置为EINTR。我们可以利用这一点来实现你的需求,具体步骤如下:
1. 编写可靠的SIGINT信号处理函数
信号处理函数不需要复杂逻辑,甚至可以是空函数,只要它被注册,就能触发waitpid()的中断。不过为了友好性,你可以加个提示信息,但要注意信号处理函数里尽量用异步信号安全的函数(比如write(),而不是printf(),因为printf不是异步安全的,极端场景可能出问题):
#include <signal.h> #include <unistd.h> void sigint_handler(int sig) { // 用write输出提示,确保异步安全 write(STDOUT_FILENO, "\n收到CTRL+C,正在退出子进程等待...\n", 36); }
2. 用sigaction注册信号处理函数(别用signal()!)
signal()函数的行为在不同Unix-like系统上不一致,推荐用sigaction()来注册信号处理函数,关键是不要设置SA_RESTART标志——这个标志会让被信号打断的系统调用自动重启,那样waitpid()就不会返回-1,也就没法退出等待了:
#include <errno.h> #include <sys/wait.h> #include <stdio.h> int main() { struct sigaction sa; sa.sa_handler = sigint_handler; sigemptyset(&sa.sa_mask); // 处理SIGINT期间不屏蔽其他信号(可按需调整) sa.sa_flags = 0; // 核心:不要加SA_RESTART! if (sigaction(SIGINT, &sa, NULL) == -1) { perror("sigaction注册信号失败"); return 1; } // 先fork一个子进程模拟场景 pid_t child_pid = fork(); if (child_pid == -1) { perror("fork子进程失败"); return 1; } else if (child_pid == 0) { // 子进程:模拟长时间任务,比如sleep 10秒 sleep(10); return 0; } // 父进程等待子进程的逻辑 int status; pid_t wait_ret; while (1) { wait_ret = waitpid(child_pid, &status, 0); if (wait_ret == -1) { if (errno == EINTR) { // 捕获到EINTR,说明waitpid被SIGINT打断了,退出循环继续执行 printf("waitpid已退出,开始执行后续程序逻辑...\n"); break; } else { // 其他waitpid错误,比如子进程不存在等 perror("waitpid执行出错"); return 1; } } else { // 子进程正常退出,处理退出状态 if (WIFEXITED(status)) { printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("子进程被信号终止,信号编号:%d\n", WTERMSIG(status)); } break; } } // 这里就是你要继续执行的正常逻辑 printf("程序继续运行,完成剩余操作...\n"); return 0; }
关键注意事项
- 禁止SA_RESTART标志:这是最容易踩的坑,如果设置了这个标志,
waitpid()被信号打断后会自动重启,永远不会返回-1,也就无法退出等待。 - 异步信号安全函数:信号处理函数里只能调用异步信号安全的函数,比如
write()、_exit()等,避免用printf()、malloc()这类非安全函数。 - 可选:终止子进程:如果你希望按下CTRL+C时不仅打断
waitpid(),还要终止子进程,可以在信号处理函数里给子进程发信号(比如kill(child_pid, SIGTERM)),但要注意把child_pid声明为全局变量或者静态变量,因为信号处理函数无法直接访问局部变量。
内容的提问来源于stack exchange,提问作者Nado Ba
相关产品推荐
相关产品推荐

