SIGKILL 上的 SIGCHLD

SIGCHLD on SIGKILL

程序fork_wait.c:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){
    pid_t a = fork();
    if (a != 0) {
        int status;
        printf("I am your father. (...) You know it to be true !");
        wait(&status);
    }
    else {
        printf("NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!");
        sleep(2000);
    }
    return 0;
}

Bash :

$ cc fork_wait.c -o fork_wait && ./fork_wait
I am your father. (...) You know it to be true !
NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!

Bash 执行时:

$ pgrep fork_wait
37818
37819

$ kill -9 $(pgrep fork_wait | tail -1)
$ pgrep fork_wait
(nothing)

当进程被 SIGTERM 杀死时,谁发送了 SIGCHLD 信号?为什么杀了儿子就没有僵尸进程了?

这是对您的第一个程序的改编。它使用默认的 SIGCHLD 信号处理,这意味着当 child 死亡 (forkwait29.c):

时,信号不会(由内核)传递给 parent 进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t a = fork();
    if (a != 0)
    {
        printf("%5d: I am your father. (...) You know it to be true!\n", getpid());
        int corpse;
        int status;
        while ((corpse = wait(&status)) > 0)
            printf("PID %5d exited with status 0x%.4X\n", corpse, status);
    }
    else
    {
        printf("%5d: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!\n", getpid());
        sleep(2000);
    }
    return 0;
}

一个例子 运行 产生:

$ forkwait29
64001: I am your father. (...) You know it to be true!
64002: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
PID 64002 exited with status 0x0009
$

您可以分析返回的状态(使用 WIFSIGNALEDWIFTERMSIGWIFEXITEDWEXITSTATUSWCOREDUMP 等),它会显示child 死掉是因为它收到信号 9,SIGKILL。正如评论中指出的那样,您的 parent 收集了已死的 child 进程(防止它成为 (long-lasting) 僵尸)并退出。

您可以像这样添加一些信号处理 (forkwait73.c):

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

static volatile sig_atomic_t sig_caught = 0;

static void sig_handler(int signum)
{
    sig_caught = signum;
}

int main(void)
{
    pid_t a = fork();
    if (a != 0)
    {
        printf("%5d: I am your father. (...) You know it to be true!\n", getpid());
        struct sigaction sa = { 0 };
        sa.sa_handler = sig_handler;
        sa.sa_flags = SA_RESTART;
        if (sigemptyset(&sa.sa_mask) != 0)
            return 1;
        sigaction(SIGCHLD, &sa, NULL);
        int corpse;
        int status;
        while ((corpse = wait(&status)) > 0)
        {
            printf("PID %5d exited with status 0x%.4X (caught = %d)\n",
                   corpse, status, sig_caught);
        }
    }
    else
    {
        printf("%5d: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!\n", getpid());
        sleep(2000);
    }
    return 0;
}

一个样品 运行 产生:

$ forkwait73
63964: I am your father. (...) You know it to be true!
63965: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
PID 63965 exited with status 0x000F (caught = 20)
$

当我在 macOS Moneterey 12.2.1 上省略 SA_RESTART 标志时,我得到如下结果:

$ forkwait73
63929: I am your father. (...) You know it to be true!
63930: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
$

如果没有 SA_RESTART,parent 进程不会报告其 child 的死亡,因为 wait()errno == EINTR 而失败。可以将循环修改为:

,而不是设置 SA_RESTART
        while ((corpse = wait(&status)) > 0 || errno == EINTR)
        {
            printf("PID %5d exited with status 0x%.4X (caught = %d)\n",
                   corpse, status, sig_caught);
            errno = 0;
        }

如果你想看到一个僵尸进程,你必须安排 parent 暂时不要 wait() child。你也可以让它休眠,当 sleep() 调用结束时,它可以继续 wait() 循环。你也可以安排它处理另一个信号,这样你就可以发出信号让它醒来并发现它 child 已经死了。