How to make child process die after parent exits?

2018-12-31 07:43发布

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 or anything else) I want the child process to die. How to do that correctly?


Some similar question on stackoverflow:


Some similar question on stackoverflow for Windows:

23条回答
妖精总统
2楼-- · 2018-12-31 08:41

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

#include <sys/prctl.h>
#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).

查看更多
琉璃瓶的回忆
3楼-- · 2018-12-31 08:42

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

查看更多
其实,你不懂
4楼-- · 2018-12-31 08:43

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

查看更多
明月照影归
5楼-- · 2018-12-31 08:46

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.
查看更多
流年柔荑漫光年
6楼-- · 2018-12-31 08:47

Another way to do this that is Linux specific is to have the parent be created in a new PID namespace. It will then be PID 1 in that namespace, and when it exits it all of it's children will be immediately killed with SIGKILL.

Unfortunately, in order to create a new PID namespace you have to have CAP_SYS_ADMIN. But, this method is very effective and requires no real change to the parent or the children beyond the initial launch of the parent.

See clone(2), pid_namespaces(7), and unshare(2).

查看更多
登录 后发表回答