假设我有一个进程,它只生成一个子进程。现在,当父进程由于某种原因(正常或不正常,通过kill, ^C,断言失败或其他任何原因)退出时,我希望子进程死亡。如何正确地做到这一点?


关于stackoverflow的一些类似问题:

(前面问过)当父进程退出的时候,我怎么能让子进程退出呢? (稍后问)用fork()创建的子进程会在父进程被杀死时自动被杀死吗?


关于Windows的stackoverflow也有类似的问题:

如何在Windows中自动销毁子进程? 当父进程被杀死时,杀死子进程


当前回答

这个解决方案对我很有效:

将stdin管道传递给子管道-您不必向流中写入任何数据。 Child从stdin无限读取到EOF。EOF表示父节点已经离开。 这是一种万无一失、便于携带的检测父节点何时离开的方法。即使父线程崩溃,操作系统也会关闭管道。

这是针对一个工作者类型的进程,它的存在只有在父进程存在时才有意义。

其他回答

正如其他人指出的那样,当父进程退出时,依赖父进程pid变成1是不可移植的。不需要等待特定的父进程ID,只需要等待ID发生变化:

pit_t pid = getpid();
switch (fork())
{
    case -1:
    {
        abort(); /* or whatever... */
    }
    default:
    {
        /* parent */
        exit(0);
    }
    case 0:
    {
        /* child */
        /* ... */
    }
}

/* Wait for parent to exit */
while (getppid() != pid)
    ;

如果不想以全速轮询,可以根据需要添加微睡眠。

在我看来,这个选项比使用管道或依赖于信号更简单。

在POSIX中,exit(), _exit()和_exit()函数被定义为:

如果该进程是控制进程,则SIGHUP信号应发送给控制终端的前台进程组中属于呼叫进程的每个进程。

因此,如果您安排父进程作为其进程组的控制进程,那么当父进程退出时,子进程应该得到一个SIGHUP信号。我不确定当父节点崩溃时是否会发生这种情况,但我认为确实会发生。当然,对于非崩溃的情况,它应该可以正常工作。

请注意,您可能必须阅读大量的小字——包括基本定义(Definitions)部分,以及exit()和setsid()和setpgrp()的系统服务信息——才能了解完整的情况。(我也是!)

我把父pid用环境传递给子pid, 然后定期检查子进程中是否存在/proc/$ppid。

在Linux下,你可以在子进程中安装父进程死亡信号,例如:

#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#include <signal.h> // signals
#include <unistd.h> // fork()
#include <stdio.h>  // perror()

// ...

pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() != ppid_before_fork)
        exit(1);
    // continue child execution ...

请注意,在fork之前存储父进程id,并在prctl()之后在子进程中测试它,消除了prctl()和调用子进程的退出之间的竞争条件。

还要注意,子进程的父进程死亡信号在新创建的子进程中被清除。它不受execve()的影响。

如果我们确定负责收养所有孤儿的系统进程PID为1,这个测试就可以简化:

pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() == 1)
        exit(1);
    // continue child execution ...

但是,依赖于系统进程的初始化和PID 1是不可移植的。posix . 1的授权- 2008指定:

调用进程的所有现有子进程和僵尸进程的父进程ID应设置为实现定义的系统进程的进程ID。也就是说,这些进程应该由一个特殊的系统进程继承。

传统上,采用所有孤儿进程的系统进程是PID 1,即init -它是所有进程的祖先。

在像Linux或FreeBSD这样的现代系统上,另一个进程可能具有这个角色。例如,在Linux上,一个进程可以调用prctl(PR_SET_CHILD_SUBREAPER, 1)来将自己建立为继承其任何后代的所有孤儿的系统进程(参见Fedora 25上的一个例子)。

通过滥用终端控制和会话,我设法用3个进程实现了一个可移植的、非轮询的解决方案。

诀窍在于:

process A is started process A creates a pipe P (and never reads from it) process A forks into process B process B creates a new session process B allocates a virtual terminal for that new session process B installs SIGCHLD handler to die when the child exits process B sets a SIGPIPE handler process B forks into process C process C does whatever it needs (e.g. exec()s the unmodified binary or runs whatever logic) process B writes to pipe P (and blocks that way) process A wait()s on process B and exits when it dies

这种方式:

如果进程A死亡:进程B得到一个SIGPIPE并死亡 如果进程B死亡:进程A的wait()返回并死亡,进程C将得到一个SIGHUP(因为当一个连接终端的会话的会话领导者死亡时,前台进程组中的所有进程都会得到一个SIGHUP) 如果进程C死亡:进程B得到一个SIGCHLD并死亡,那么进程a也会死亡

缺点:

进程C不能处理SIGHUP 进程C将在不同的会话中运行 进程C不能使用会话/进程组API,因为这会破坏脆弱的设置 为每一个这样的操作创建一个终端并不是最好的主意