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


关于stackoverflow的一些类似问题:

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


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

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


当前回答

在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上的一个例子)。

其他回答

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

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

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

安装一个陷阱处理程序来捕获SIGINT,如果你的子进程还活着,它就会杀死它,尽管其他的帖子是正确的,它不会捕获SIGKILL。

以独占访问的方式打开一个.lockfile,并让子进程尝试打开它——如果打开成功,子进程应该退出

我认为一种快速而又不那么简单的方法是在子节点和父节点之间创建一个管道。当父节点退出时,子节点将收到一个SIGPIPE。

我找到了两个解,都不完美。

1.当收到SIGTERM信号时,通过Kill (-pid)杀死所有子结点。 显然,这个解决方案不能处理“kill -9”,但它确实适用于大多数情况,而且非常简单,因为它不需要记住所有的子进程。


    var childProc = require('child_process').spawn('tail', ['-f', '/dev/null'], {stdio:'ignore'});

    var counter=0;
    setInterval(function(){
      console.log('c  '+(++counter));
    },1000);

    if (process.platform.slice(0,3) != 'win') {
      function killMeAndChildren() {
        /*
        * On Linux/Unix(Include Mac OS X), kill (-pid) will kill process group, usually
        * the process itself and children.
        * On Windows, an JOB object has been applied to current process and children,
        * so all children will be terminated if current process dies by anyway.
        */
        console.log('kill process group');
        process.kill(-process.pid, 'SIGKILL');
      }

      /*
      * When you use "kill pid_of_this_process", this callback will be called
      */
      process.on('SIGTERM', function(err){
        console.log('SIGTERM');
        killMeAndChildren();
      });
    }

通过同样的方式,如果你调用process,你可以像上面那样安装'exit'处理程序。退出的地方。 注意:Ctrl+C和突然崩溃已经被操作系统自动处理来杀死进程组,这里不再赘述。

2.使用chjj/pty.js生成附加控制终端的进程。 当你以任何方式甚至kill -9终止当前进程时,所有的子进程也会被自动终止(由操作系统?)我猜是因为当前进程占用终端的另一侧,所以如果当前进程死亡,子进程将获得SIGPIPE,因此死亡。


    var pty = require('pty.js');

    //var term =
    pty.spawn('any_child_process', [/*any arguments*/], {
      name: 'xterm-color',
      cols: 80,
      rows: 30,
      cwd: process.cwd(),
      env: process.env
    });
    /*optionally you can install data handler
    term.on('data', function(data) {
      process.stdout.write(data);
    });
    term.write(.....);
    */

如果你无法修改子进程,你可以尝试以下方法:

int pipes[2];
pipe(pipes)
if (fork() == 0) {
    close(pipes[1]); /* Close the writer end in the child*/
    dup2(pipes[0], STDIN_FILENO); /* Use reader end as stdin (fixed per  maxschlepzig */
    exec("sh -c 'set -o monitor; child_process & read dummy; kill %1'")
}

close(pipes[0]); /* Close the reader end in the parent */

这将在启用作业控制的shell进程中运行子进程。子进程在后台生成。shell等待换行符(或EOF),然后终止子进程。

当父进程死亡时——不管是什么原因——它将关闭管道的一端。子shell将从read中获得一个EOF,并继续杀死后台的子进程。