关于sigsuspend()结合SIGINT与SIGCHLD的代码疑问及流程解析请求
CSAPP 信号处理示例代码解析与疑问解答
#include "csapp.h" volatile sig_atomic_t pid; void sigchld_handler(int s) { int olderrno = errno; pid = Waitpid(-1, NULL, 0); errno = olderrno; } void sigint_handler(int s) { } #define N 10 int main(int argc, char **argv) { sigset_t mask, prev; int n = N; /* 10 */ Signal(SIGCHLD, sigchld_handler); Signal(SIGINT, sigint_handler); Sigemptyset(&mask); Sigaddset(&mask, SIGCHLD); while (n--) { Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */ if (Fork() == 0) /* Child */ exit(0); /* Wait for SIGCHLD to be received */ pid = 0; while (!pid) Sigsuspend(&prev); /* Optionally unblock SIGCHLD */ Sigprocmask(SIG_SETMASK, &prev, NULL); /* Do some work after receiving SIGCHLD */ printf("."); } printf("\n"); exit(0); }
疑问解答
1. SIGINT与sigsuspend()的交互逻辑
你的判断完全正确:
Signal(SIGINT, sigint_handler)直接覆盖了SIGINT的默认终止行为,换成了一个空处理函数,捕获到SIGINT后不会做任何实质性操作。- 当父进程调用
Sigsuspend(&prev)时,会临时将信号掩码切换为prev(这个掩码里SIGINT是未阻塞的),然后进入休眠等待信号。 - 如果此时收到SIGINT,内核会唤醒进程执行空handler,执行完成后
sigsuspend返回,回到while (!pid)的循环判断。 - 由于SIGINT的handler没有修改
pid的值(pid仍为0),!pid的判断结果为真,循环测试成功,会再次调用Sigsuspend(&prev)继续等待信号。
2. 循环测试失败后SIGCHLD仍阻塞的原因
核心在于sigsuspend的信号掩码恢复规则:
- 调用
Sigsuspend前,我们已经通过Sigprocmask(SIG_BLOCK, &mask, &prev)将SIGCHLD设为阻塞状态。 - 当SIGCHLD信号被捕获时,内核会自动将SIGCHLD加入进程的信号掩码(防止信号嵌套触发),直到handler执行完毕。
- handler执行完成后,
sigsuspend返回,此时进程的信号掩码会恢复到调用sigsuspend之前的状态——也就是重新阻塞SIGCHLD的状态,所以循环退出时SIGCHLD仍然是阻塞的。
完整流程走查
以单次循环为例,完整梳理每一步的执行逻辑:
初始化阶段
- 注册SIGCHLD的处理函数为
sigchld_handler,SIGINT的处理函数为空函数; - 创建仅包含SIGCHLD的信号集合
mask; - 初始化
n=10,进入第一次循环。
- 注册SIGCHLD的处理函数为
阻塞SIGCHLD
- 调用
Sigprocmask(SIG_BLOCK, &mask, &prev):将SIGCHLD加入进程信号掩码(此时SIGCHLD被阻塞),同时保存原始信号掩码到prev(prev中SIGCHLD未被阻塞)。
- 调用
创建并终止子进程
- 调用
Fork()生成子进程,子进程直接exit(0)终止,向父进程发送SIGCHLD信号; - 由于父进程此时阻塞SIGCHLD,该信号会被内核挂起,不会立即交付。
- 调用
等待SIGCHLD信号
- 设置
pid=0,进入while (!pid)循环; - 调用
Sigsuspend(&prev):临时切换信号掩码为prev(解除SIGCHLD阻塞),然后休眠等待信号:- 若收到SIGINT:执行空handler,返回后
pid仍为0,循环继续,再次调用Sigsuspend; - 若收到SIGCHLD:执行
sigchld_handler,调用Waitpid回收子进程,将pid设为回收的子进程ID;handler执行完毕后,sigsuspend返回,信号掩码恢复到调用前的状态(重新阻塞SIGCHLD)。
- 若收到SIGINT:执行空handler,返回后
- 设置
解除SIGCHLD阻塞
- 调用
Sigprocmask(SIG_SETMASK, &prev, NULL):将信号掩码恢复为原始的prev状态,解除SIGCHLD的阻塞。
- 调用
完成当前循环
- 打印一个
.,n减1,进入下一次循环,重复上述流程。
- 打印一个
当10次循环全部执行完毕后,打印换行,进程正常退出。
内容的提问来源于stack exchange,提问作者sociala
相关产品推荐
相关产品推荐

