为何已调用signal()注册SIGUSR1处理函数,子进程仍被终止?
我写了一段C代码,期望实现父进程发送SIGUSR1信号给子进程,子进程捕获信号并输出指定内容:
void sig_handler(int sig) { printf("Hello child\n"); } int main() { pid_t child = fork(); if (child > 0) { printf("Hello parent\n"); kill(child, SIGUSR1); } else if (child == 0) { signal(SIGUSR1, sig_handler); printf("In child\n"); } else { printf("Error\n"); } return 0; }
期望输出:
Hello parent Hello child In child
但实际运行后,父进程调用kill(child, SIGUSR1)后,子进程直接被终止,实际输出只有:
Hello parent
我知道SIGUSR1的默认行为是终止进程,但明明已经通过signal(SIGUSR1, sig_handler)注册了信号捕获处理函数,为什么子进程还是被终止了?
解答
核心问题出在进程执行的时序不确定性上:
当你调用fork()创建子进程后,父进程和子进程的执行顺序完全由操作系统调度器决定,没有固定的先后顺序。在你的代码里,很大概率是父进程先完成了kill(child, SIGUSR1)的调用,而此时子进程还没来得及执行到signal(SIGUSR1, sig_handler)这一行——也就是说,子进程还没为SIGUSR1注册自定义处理函数,信号的默认终止行为就被触发了,自然看不到后续的输出。
如何解决这个问题?
你需要确保子进程完全注册好信号处理函数之后,父进程再发送信号。这里提供几种常见的方案:
1. 简单延迟(适合测试场景)
在父进程的kill调用前加入短暂睡眠,给子进程足够的时间完成信号注册:
void sig_handler(int sig) { printf("Hello child\n"); } int main() { pid_t child = fork(); if (child > 0) { printf("Hello parent\n"); sleep(1); // 等待子进程完成信号注册 kill(child, SIGUSR1); wait(NULL); // 等待子进程执行完毕 } else if (child == 0) { signal(SIGUSR1, sig_handler); printf("In child\n"); pause(); // 暂停等待信号,避免子进程提前退出 } else { printf("Error\n"); } return 0; }
注意:子进程需加入pause(),否则注册完信号后会直接执行return 0退出,可能父进程发信号时子进程已不存在。
2. 可靠进程同步(适合生产场景)
睡眠的方式依赖系统调度,不够可靠。更严谨的做法是让子进程在注册好信号后主动通知父进程,父进程收到通知后再发送SIGUSR1,比如用另一个信号同步:
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <sys/wait.h> void sig_handler(int sig) { if (sig == SIGUSR1) { printf("Hello child\n"); } } // 父进程接收子进程就绪信号的空处理函数 void ready_handler(int sig) {} int main() { // 父进程先注册子进程就绪的信号处理函数 signal(SIGUSR2, ready_handler); pid_t child = fork(); if (child > 0) { printf("Hello parent\n"); pause(); // 等待子进程发送就绪信号 kill(child, SIGUSR1); // 子进程已就绪,发送目标信号 wait(NULL); // 等待子进程退出 } else if (child == 0) { // 注册SIGUSR1处理函数 signal(SIGUSR1, sig_handler); printf("In child\n"); kill(getppid(), SIGUSR2); // 通知父进程:已准备好接收信号 pause(); // 等待目标信号 } else { printf("Error\n"); } return 0; }
这种双向信号同步的方式,完全不受调度器影响,能确保时序的正确性。
3. 用sigaction替代signal(可选但推荐)
signal函数的行为在不同系统中存在兼容性差异,而sigaction是POSIX标准的信号处理接口,行为更稳定可靠。替换后的注册代码示例:
struct sigaction sa; sa.sa_handler = sig_handler; sigemptyset(&sa.sa_mask); // 清空信号掩码 sa.sa_flags = 0; sigaction(SIGUSR1, &sa, NULL);
内容的提问来源于stack exchange,提问作者Eager-to-learn

