You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于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仍然是阻塞的。

完整流程走查

以单次循环为例,完整梳理每一步的执行逻辑:

  1. 初始化阶段

    • 注册SIGCHLD的处理函数为sigchld_handler,SIGINT的处理函数为空函数;
    • 创建仅包含SIGCHLD的信号集合mask;
    • 初始化n=10,进入第一次循环。
  2. 阻塞SIGCHLD

    • 调用Sigprocmask(SIG_BLOCK, &mask, &prev):将SIGCHLD加入进程信号掩码(此时SIGCHLD被阻塞),同时保存原始信号掩码到prev(prev中SIGCHLD未被阻塞)。
  3. 创建并终止子进程

    • 调用Fork()生成子进程,子进程直接exit(0)终止,向父进程发送SIGCHLD信号;
    • 由于父进程此时阻塞SIGCHLD,该信号会被内核挂起,不会立即交付。
  4. 等待SIGCHLD信号

    • 设置pid=0,进入while (!pid)循环;
    • 调用Sigsuspend(&prev):临时切换信号掩码为prev(解除SIGCHLD阻塞),然后休眠等待信号:
      • 若收到SIGINT:执行空handler,返回后pid仍为0,循环继续,再次调用Sigsuspend;
      • 若收到SIGCHLD:执行sigchld_handler,调用Waitpid回收子进程,将pid设为回收的子进程ID;handler执行完毕后,sigsuspend返回,信号掩码恢复到调用前的状态(重新阻塞SIGCHLD)。
  5. 解除SIGCHLD阻塞

    • 调用Sigprocmask(SIG_SETMASK, &prev, NULL):将信号掩码恢复为原始的prev状态,解除SIGCHLD的阻塞。
  6. 完成当前循环

    • 打印一个.,n减1,进入下一次循环,重复上述流程。

当10次循环全部执行完毕后,打印换行,进程正常退出。

内容的提问来源于stack exchange,提问作者sociala

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 01:45:33