How to make child process die after parent exits?

后端 未结 24 1757
天涯浪人
天涯浪人 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:25

    If you send a signal to the pid 0, using for instance

    kill(0, 2); /* SIGINT */
    

    that signal is sent to the entire process group, thus effectively killing the child.

    You can test it easily with something like:

    (cat && kill 0) | python
    

    If you then press ^D, you'll see the text "Terminated" as an indication that the Python interpreter have indeed been killed, instead of just exited because of stdin being closed.

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

    This solution worked for me:

    • Pass stdin pipe to child - you don't have to write any data into the stream.
    • Child reads indefinitely from stdin until EOF. An EOF signals that the parent has gone.
    • This is foolproof and portable way to detect when the parent has gone. Even if parent crashes, OS will close the pipe.

    This was for a worker-type process whose existence only made sense when the parent was alive.

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

    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.

    Two issues emerge from this approach that can lead to issues:

    • 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)
    0 讨论(0)
  • 2020-11-22 06:28

    Inspired by another answer here, I came up with the following all-POSIX solution. The general idea is to create an intermediate process between the parent and the child, that has one purpose: Notice when the parent dies, and explicitly kill the child.

    This type of solution is useful when the code in the child can't be modified.

    int p[2];
    pipe(p);
    pid_t child = fork();
    if (child == 0) {
        close(p[1]); // close write end of pipe
        setpgid(0, 0); // prevent ^C in parent from stopping this process
        child = fork();
        if (child == 0) {
            close(p[0]); // close read end of pipe (don't need it here)
            exec(...child process here...);
            exit(1);
        }
        read(p[0], 1); // returns when parent exits for any reason
        kill(child, 9);
        exit(1);
    }
    

    There are two small caveats with this method:

    • If you deliberately kill the intermediate process, then the child won't be killed when the parent dies.
    • If the child exits before the parent, then the intermediate process will try to kill the original child pid, which could now refer to a different process. (This could be fixed with more code in the intermediate process.)

    As an aside, the actual code I'm using is in Python. Here it is for completeness:

    def run(*args):
        (r, w) = os.pipe()
        child = os.fork()
        if child == 0:
            os.close(w)
            os.setpgid(0, 0)
            child = os.fork()
            if child == 0:
                os.close(r)
                os.execl(args[0], *args)
                os._exit(1)
            os.read(r, 1)
            os.kill(child, 9)
            os._exit(1)
        os.close(r)
    
    0 讨论(0)
  • 2020-11-22 06:29

    Some posters have already mentioned pipes and kqueue. In fact you can also create a pair of connected Unix domain sockets by the socketpair() call. The socket type should be SOCK_STREAM.

    Let us suppose you have the two socket file descriptors fd1, fd2. Now fork() to create the child process, which will inherit the fds. In the parent you close fd2 and in the child you close fd1. Now each process can poll() the remaining open fd on its own end for the POLLIN event. As long as each side doesn't explicitly close() its fd during normal lifetime, you can be fairly sure that a POLLHUP flag should indicate the other's termination (no matter clean or not). Upon notified of this event, the child can decide what to do (e.g. to die).

    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <poll.h>
    #include <stdio.h>
    
    int main(int argc, char ** argv)
    {
        int sv[2];        /* sv[0] for parent, sv[1] for child */
        socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
    
        pid_t pid = fork();
    
        if ( pid > 0 ) {  /* parent */
            close(sv[1]);
            fprintf(stderr, "parent: pid = %d\n", getpid());
            sleep(100);
            exit(0);
    
        } else {          /* child */
            close(sv[0]);
            fprintf(stderr, "child: pid = %d\n", getpid());
    
            struct pollfd mon;
            mon.fd = sv[1];
            mon.events = POLLIN;
    
            poll(&mon, 1, -1);
            if ( mon.revents & POLLHUP )
                fprintf(stderr, "child: parent hung up\n");
            exit(0);
        }
    }
    

    You can try compiling the above proof-of-concept code, and run it in a terminal like ./a.out &. You have roughly 100 seconds to experiment with killing the parent PID by various signals, or it will simply exit. In either case, you should see the message "child: parent hung up".

    Compared with the method using SIGPIPE handler, this method doesn't require trying the write() call.

    This method is also symmetric, i.e. the processes can use the same channel to monitor each other's existence.

    This solution calls only the POSIX functions. I tried this in Linux and FreeBSD. I think it should work on other Unixes but I haven't really tested.

    See also:

    • unix(7) of Linux man pages, unix(4) for FreeBSD, poll(2), socketpair(2), socket(7) on Linux.
    0 讨论(0)
  • 2020-11-22 06:30

    Install a trap handler to catch SIGINT, which kills off your child process if it's still alive, though other posters are correct that it won't catch SIGKILL.

    Open a .lockfile with exclusive access and have the child poll on it trying to open it - if the open succeeds, the child process should exit

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