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


关于stackoverflow的一些类似问题:

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


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

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


当前回答

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

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

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

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

其他回答

我正在尝试解决同样的问题,但由于我的程序必须运行在OS X上,所以只运行linux的解决方案对我不起作用。

我得到了与本页其他人相同的结论——当父母去世时,没有一种与posix兼容的方式来通知孩子。所以我想出了次好的办法——让孩子投票。

当父进程(由于任何原因)终止时,子进程将变成进程1。如果子进程只是定期轮询,它可以检查父进程是否为1。如果是,子进程应该退出。

这不是很好,但它可以工作,并且比本文其他地方建议的TCP套接字/锁文件轮询解决方案更容易。

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

Historically, from UNIX v7, the process system has detected orphanity of processes by checking a process' parent id. As I say, historically, the init(8) system process is a special process by only one reason: It cannot die. It cannot die because the kernel algorithm to deal with assigning a new parent process id, depends on this fact. when a process executes its exit(2) call (by means of a process system call or by external task as sending it a signal or the like) the kernel reassigns all children of this process the id of the init process as their parent process id. This leads to the most easy test, and most portable way of knowing if a process has got orphan. Just check the result of the getppid(2) system call and if it is the process id of the init(2) process then the process got orphan before the system call.

这种方法会产生两个问题:

first, we have the possibility of changing the init process to any user process, so How can we assure that the init process will always be parent of all orphan processes? Well, in the exit system call code there's a explicit check to see if the process executing the call is the init process (the process with pid equal to 1) and if that's the case, the kernel panics (It should not be able anymore to maintain the process hierarchy) so it is not permitted for the init process to do an exit(2) call. second, there's a race condition in the basic test exposed above. Init process' id is assumed historically to be 1, but that's not warranted by the POSIX approach, that states (as exposed in other response) that only a system's process id is reserved for that purpose. Almost no posix implementation does this, and you can assume in original unix derived systems that having 1 as response of getppid(2) system call is enough to assume the process is orphan. Another way to check is to make a getppid(2) just after the fork and compare that value with the result of a new call. This simply doesn't work in all cases, as both call are not atomic together, and the parent process can die after the fork(2) and before the first getppid(2) system call. The processparent id only changes once, when its parent does anexit(2)call, so this should be enough to check if thegetppid(2)result changed between calls to see that parent process has exit. This test is not valid for the actual children of the init process, because they are always children ofinit(8)`, but you can assume safely these processes as having no parent either (except when you substitute in a system the init process)

正如其他人指出的那样,当父进程退出时,依赖父进程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)
    ;

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

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

即使7年过去了,我刚刚遇到这个问题,因为我正在运行SpringBoot应用程序,需要在开发期间启动webpack-dev-server,并需要在后端进程停止时杀死它。

我尝试使用Runtime.getRuntime()。addShutdownHook,但它在Windows 10上工作,但在Windows 7上不工作。

我已经将其更改为使用一个专门的线程来等待进程退出或用于InterruptedException,这似乎在两个Windows版本上都正确工作。

private void startWebpackDevServer() {
    String cmd = isWindows() ? "cmd /c gradlew webPackStart" : "gradlew webPackStart";
    logger.info("webpack dev-server " + cmd);

    Thread thread = new Thread(() -> {

        ProcessBuilder pb = new ProcessBuilder(cmd.split(" "));
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        pb.directory(new File("."));

        Process process = null;
        try {
            // Start the node process
            process = pb.start();

            // Wait for the node process to quit (blocking)
            process.waitFor();

            // Ensure the node process is killed
            process.destroyForcibly();
            System.setProperty(WEBPACK_SERVER_PROPERTY, "true");
        } catch (InterruptedException | IOException e) {
            // Ensure the node process is killed.
            // InterruptedException is thrown when the main process exit.
            logger.info("killing webpack dev-server", e);
            if (process != null) {
                process.destroyForcibly();
            }
        }

    });

    thread.start();
}