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
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.
This solution worked for me:
This was for a worker-type process whose existence only made sense when the parent was alive.
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:
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.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 an
exit(2)call, so this should be enough to check if the
getppid(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 of
init(8)`, but you can assume safely these processes as having no parent either (except when you substitute in a system the init process)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:
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)
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.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