How to make child process die after parent exits?

后端 未结 24 1724
天涯浪人
天涯浪人 2020-11-22 05:31

Suppose I have a process which spawns exactly one child process. Now when the parent process exits for whatever reason (normally or abnormally, by kill, ^C, assert failure o

相关标签:
24条回答
  • 2020-11-22 06:14

    Under POSIX, the exit(), _exit() and _Exit() functions are defined to:

    • If the process is a controlling process, the SIGHUP signal shall be sent to each process in the foreground process group of the controlling terminal belonging to the calling process.

    So, if you arrange for the parent process to be a controlling process for its process group, the child should get a SIGHUP signal when the parent exits. I'm not absolutely sure that happens when the parent crashes, but I think it does. Certainly, for the non-crash cases, it should work fine.

    Note that you may have to read quite a lot of fine print - including the Base Definitions (Definitions) section, as well as the System Services information for exit() and setsid() and setpgrp() - to get the complete picture. (So would I!)

    0 讨论(0)
  • 2020-11-22 06:15

    Under Linux, you can install a parent death signal in the child, e.g.:

    #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 ...
    

    Note that storing the parent process id before the fork and testing it in the child after prctl() eliminates a race condition between prctl() and the exit of the process that called the child.

    Also note that the parent death signal of the child is cleared in newly created children of its own. It is not affected by an execve().

    That test can be simplified if we are certain that the system process who is in charge of adopting all orphans has 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 ...
    

    Relying on that system process being init and having PID 1 isn't portable, though. POSIX.1-2008 specifies:

    The parent process ID of all of the existing child processes and zombie processes of the calling process shall be set to the process ID of an implementation-defined system process. That is, these processes shall be inherited by a special system process.

    Traditionally, the system process adopting all orphans is PID 1, i.e. init - which is the ancestor of all processes.

    On modern systems like Linux or FreeBSD another process might have that role. For example, on Linux, a process can call prctl(PR_SET_CHILD_SUBREAPER, 1) to establish itself as system process that inherits all orphans of any of its descendants (cf. an example on Fedora 25).

    0 讨论(0)
  • 2020-11-22 06:16

    In case it is relevant to anyone else, when I spawn JVM instances in forked child processes from C++, the only way I could get the JVM instances to terminate properly after the parent process completed was to do the following. Hopefully someone can provide feedback in the comments if this wasn't the best way to do this.

    1) Call prctl(PR_SET_PDEATHSIG, SIGHUP) on the forked child process as suggested before launching the Java app via execv, and

    2) Add a shutdown hook to the Java application that polls until its parent PID equals 1, then do a hard Runtime.getRuntime().halt(0). The polling is done by launching a separate shell that runs the ps command (See: How do I find my PID in Java or JRuby on Linux?).

    EDIT 130118:

    It seems that was not a robust solution. I'm still struggling a bit to understand the nuances of what's going on, but I was still sometimes getting orphan JVM processes when running these applications in screen/SSH sessions.

    Instead of polling for the PPID in the Java app, I simply had the shutdown hook perform cleanup followed by a hard halt as above. Then I made sure to invoke waitpid in the C++ parent app on the spawned child process when it was time to terminate everything. This seems to be a more robust solution, as the child process ensures that it terminates, while the parent uses existing references to make sure that its children terminate. Compare this to the previous solution which had the parent process terminate whenever it pleased, and had the children try to figure out if they had been orphaned before terminating.

    0 讨论(0)
  • 2020-11-22 06:18

    Child can ask kernel to deliver SIGHUP (or other signal) when parent dies by specifying option PR_SET_PDEATHSIG in prctl() syscall like this:

    prctl(PR_SET_PDEATHSIG, SIGHUP);

    See man 2 prctl for details.

    Edit: This is Linux-only

    0 讨论(0)
  • 2020-11-22 06:18

    I have achieved this in the past by running the "original" code in the "child" and the "spawned" code in the "parent" (that is: you reverse the usual sense of the test after fork()). Then trap SIGCHLD in the "spawned" code...

    May not be possible in your case, but cute when it works.

    0 讨论(0)
  • 2020-11-22 06:18

    If you're unable to modify the child process, you can try something like the following:

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

    This runs the child from within a shell process with job control enabled. The child process is spawned in the background. The shell waits for a newline (or an EOF) then kills the child.

    When the parent dies--no matter what the reason--it will close its end of the pipe. The child shell will get an EOF from the read and proceed to kill the backgrounded child process.

    0 讨论(0)
提交回复
热议问题