为何父进程发送的信号无法被子进程捕获?C语言信号问题
问题分析与修复
我帮你梳理下代码里的几个关键问题,这就是信号处理函数没触发的核心原因:
核心错误点
- 信号发错了目标:父进程里的
kill(getppid(), SIGTERM)是把信号发给了父进程的父进程(也就是启动你程序的shell),而不是子进程!应该用kill(child, SIGTERM),child变量保存的正是子进程的PID。 - 子进程逻辑混乱:子进程里的
wait(NULL)完全没必要——子进程没有自己的子进程,这个调用会直接返回错误;而且你的逻辑顺序错了,子进程应该先准备好信号处理,再等待信号,而不是先执行无意义的wait。 - 信号阻塞的继承问题:fork后子进程会继承父进程的信号掩码,所以子进程一开始也是阻塞SIGTERM的,不过这部分你后面用
sigprocmask(SIG_UNBLOCK)解除了,这个是对的,但前提是信号真的发过来了。
修正后的基础代码
先把这些错误改掉,让信号处理函数能正常触发:
#include <stdlib.h> #include <stdio.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> void handler(int signumber){ // 先用来验证功能,后面再替换成信号安全的写法 printf("Signal with number %i has arrived\n", signumber); } int main(){ sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGTERM); // 父进程先阻塞SIGTERM,子进程会继承这个掩码 sigprocmask(SIG_BLOCK, &sigset, NULL); // 先注册信号处理函数,再fork,避免子进程错过注册时机 signal(SIGTERM, handler); pid_t child = fork(); if (child > 0) { printf("I'm parent (PID: %d)\n", getpid()); printf("Waits 2 seconds, then send a SIGTERM %d signal to child (PID: %d)\n", SIGTERM, child); sleep(2); // 发给子进程! kill(child, SIGTERM); printf("I sent it.\n"); // 父进程等待子进程结束 wait(NULL); } else if (child == 0) { printf("I'm the child (PID: %d), waiting for signal.\n", getpid()); // 解除SIGTERM的阻塞,让信号能被处理 sigprocmask(SIG_UNBLOCK, &sigset, NULL); // 子进程等待信号到来,这里用sleep只是临时方案,后面换sigsuspend sleep(5); printf("Child process ended\n"); } else { perror("fork failed"); exit(1); } return 0; }
运行这个修正后的代码,你就能看到信号处理函数的输出了。
用sigsuspend替代不安全的sleep和裸解除阻塞
你提到printf在信号处理函数里不安全,而且用sigsuspend可以避免竞态条件(比如在解除阻塞和sleep之间,信号可能已经到达,导致sleep不会被打断)。sigsuspend的作用是:临时替换当前的信号掩码,然后进入休眠,直到收到一个信号并处理,之后恢复原来的信号掩码。
我们可以修改子进程的逻辑,用sigsuspend来安全等待信号:
优化后的代码(使用sigsuspend)
#include <stdlib.h> #include <stdio.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> // 改用write,因为write是异步信号安全的函数 void handler(int signumber){ char msg[64]; int len = snprintf(msg, sizeof(msg), "Signal with number %i has arrived\n", signumber); // write是异步信号安全的,不会和主程序的IO操作冲突 write(STDOUT_FILENO, msg, len); } int main(){ sigset_t sig_block, sig_wait; sigemptyset(&sig_block); sigaddset(&sig_block, SIGTERM); // 父进程阻塞SIGTERM,子进程继承 sigprocmask(SIG_BLOCK, &sig_block, NULL); signal(SIGTERM, handler); pid_t child = fork(); if (child > 0) { printf("I'm parent (PID: %d)\n", getpid()); printf("Waits 2 seconds, then send a SIGTERM %d signal to child (PID: %d)\n", SIGTERM, child); sleep(2); kill(child, SIGTERM); printf("I sent it.\n"); wait(NULL); } else if (child == 0) { printf("I'm the child (PID: %d), waiting for signal.\n", getpid()); // 准备一个空的信号集:sigsuspend会临时用这个集替换当前掩码,也就是解除SIGTERM的阻塞 sigemptyset(&sig_wait); // sigsuspend会一直休眠,直到收到一个能触发处理函数的信号 // 这里收到SIGTERM后,会先执行handler,然后sigsuspend返回 sigsuspend(&sig_wait); printf("Child process ended\n"); } else { perror("fork failed"); exit(1); } return 0; }
为什么用sigsuspend更安全?
- 避免竞态:如果先调用
sigprocmask(SIG_UNBLOCK)再sleep,有可能在解除阻塞后、sleep前,信号就已经到达并被处理,导致sleep会完整睡够时间,而不是被信号打断。sigsuspend把“解除阻塞+等待信号”做成了原子操作,不会出现这个问题。 - 信号安全:我们把printf换成了write,因为write属于POSIX定义的异步信号安全函数,可以在信号处理函数里安全调用,而printf不是(它会操作全局缓冲区,可能导致数据混乱)。
内容的提问来源于stack exchange,提问作者Gábor
相关产品推荐
相关产品推荐

