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

关于CSAPP中图8-37的while循环作用及主程序与handler2并发执行流程的技术问询

关于CSAPP中图8-37的while循环作用及主程序与handler2并发执行流程的技术问询

嘿,这两个问题都是CSAPP信号处理章节里非常容易踩坑的点,我来给你掰扯清楚~

1. 为什么while这么关键?

咱们先抓住Unix信号的核心特性:同类型信号是不排队的。也就是说,如果多个子进程几乎同时退出,内核只会给父进程发送一次SIGCHLD信号,而不是每个子进程对应发一次。

看回图8-36的bug代码:它用if (waitpid(...) < 0),每次触发SIGCHLD时只回收一个僵尸进程。假设三个子进程同时退出,内核只发一次SIGCHLD,handler1只会回收一个,剩下两个子进程就会变成僵尸进程,一直占用系统资源,直到父进程退出。

而图8-37里的while (waitpid(-1, NULL, 0) > 0)就完美补上了这个漏洞:

  • waitpid(-1, NULL, 0)会回收任意一个僵尸子进程,返回值是被回收子进程的PID;
  • 当没有僵尸进程时,waitpid会返回-1,同时errno被设为ECHILD;
  • 所以while循环会一直调用waitpid,把当前所有的僵尸进程全部回收干净,直到没有剩下的为止。
    这样哪怕一次SIGCHLD信号对应多个子进程退出,也能一次性把所有僵尸进程都处理掉,不会留下漏网之鱼。

2. 主程序和handler2的“并发”执行流程到底是怎样的?

首先要纠正一个小误解:这不是多线程并行执行,而是异步中断式的交替执行——因为整个进程是单线程的,信号处理函数是通过打断主程序的执行流来运行的。具体流程可以拆成这几步:

  1. 主程序初始化阶段:
    主程序先调用signal(SIGCHLD, handler2)注册信号处理函数,然后循环fork出3个子进程。每个子进程打印信息后立刻调用exit(0)退出,内核会标记这些子进程为僵尸状态,并向父进程发送SIGCHLD信号。

  2. 信号触发与上下文切换:
    此时主程序可能处于两个状态:要么卡在read(STDIN_FILENO, buf, sizeof(buf))等待用户输入,要么已经进入后面的死循环while(1);。当SIGCHLD信号到达时,内核会暂停主程序当前的执行,保存好当前的寄存器、程序计数器等上下文信息,然后切换到handler2的代码开始执行。

  3. handler2执行阶段:
    handler2先保存原来的errno(避免覆盖主程序的错误状态),然后进入while循环回收所有僵尸子进程,每回收一个就打印提示,直到waitpid返回-1且errno为ECHILD(确认没有僵尸进程了)。之后睡眠1秒,恢复原来的errno,然后返回。

  4. 回到主程序继续执行:
    handler2执行完后,内核会恢复之前保存的主程序上下文,让主程序从被打断的那个位置继续执行——比如如果之前在等用户输入,就继续等;如果在死循环,就继续循环下去。

还要补充一个细节:默认情况下,在信号处理函数执行期间,当前信号(这里是SIGCHLD)会被内核屏蔽,也就是说如果在handler2执行时又有子进程退出,内核不会再次触发SIGCHLD,而是把信号标记为“待处理”,等handler2执行完后再处理。不过因为我们用了while循环一次性回收所有僵尸,所以即使有新的信号待处理,后续触发时依然能把所有僵尸收干净。


附原文代码:

图8-36(存在bug的版本)

/*  figure 8-36  */
/* WARNING: This code is buggy! */
void handler1(int sig) 
{
    int olderrno = errno;

    if ((waitpid(-1, NULL, 0)) < 0)
        sio_error("waitpid error");
    Sio_puts("Handler reaped child\n");
    Sleep(1);
    errno = olderrno;
}

int main() 
{
    int i, n;
    char buf[MAXBUF];

    if (signal(SIGCHLD, handler1) == SIG_ERR)
        unix_error("signal error");

    /* Parent creates children */
    for (i = 0; i < 3; i++) {
        if (Fork() == 0) {
            printf("Hello from child %d\n", (int)getpid());
            exit(0);
        }
    }

    /* Parent waits for terminal input and then processes it */
    if ((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0)
        unix_error("read");

    printf("Parent processing input\n");
    while (1)
        ;

    exit(0);
}

图8-37(修复后的版本)

/* figure 8-37 An improved version of Figure 8.36 that correctly accounts for the fact that signals are not queued */
void handler2(int sig) 
{
    int olderrno = errno;

    while (waitpid(-1, NULL, 0) > 0) {
        Sio_puts("Handler reaped child\n");
    }
    if (errno != ECHILD)
        Sio_error("waitpid error");
    Sleep(1);
    errno = olderrno;
}

备注:内容来源于stack exchange,提问作者Ldemon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 08:48:03