UNIX: child processes upon parent termination

UNIX: child processes upon parent termination

Hello there!

Let’s stop talking about development for now. Today, I want to talk about UNIX processes, more specifically, how processes behave when their parent processes are killed or exit. But first, a little reminder about how processes are organised on a typical UNIX system.

The process tree

UNIX organises its processes in a big tree. Each process can create child processes, which will also be able to do so, and so on… Actually, when a UNIX system boots, it builds a basic process tree:

  • Process 0 (swapper or sched) which is the very first process. It does not handle any swapping operation (at least, not anymore, but it kept the name), but has some interesting characteristics:
    • This process runs only when absolutely no other process runs.
    • It is the only process allowed to use the idle system call. When it does, its memory pages are marked as swappable (which makes sense since this process won’t run much), its priority is lowered and it starts scheduling other processes.
    • It is not a user-mode process, but more like a kernel component.
  • Process 1 (init), which is the first “user-mode” process.
    • This process runs non-stop, from boot to shutdown.
    • When a process dies somewhere in the process tree, all its child process are automatically given init as a parent (so it’s a bit like the UNIX foster care system).
    • The kernel really loves init, so if it cannot find it, it’ll panic.
    • It is the root of the process tree.

Once init has been spawned, it reads its configuration regarding run levels, and determines which process it should start to complete the boot sequence (daemons, servers, …). These are configured by the system administration, using /etc/rc* directories and files. Once the system is completely booted, you can visualise the process tree by running pstree (usually not installed by default), or just by reading PID/PPID numbers in ps -ef.

Creating a process

There are usually 2 cases in which you actually create a process (there are more possibilities of course):

  1. You ask your shell to run a program (e.g. lspwd, chmod, …)
  2. You use the fork system call in a C/C++ program, hence creating a new process based on the parent.

This article is going to explain how child processes are handled when their parent dies, in these two situations.

Using fork to start a simple process

The fork system call allows you to create a child process. This new process will be a copy of the calling (parent) process, and its execution will begin right after the fork call you just made. The only difference in the execution of the two processes is the return value of fork:

  • In the child process, fork returned 0.
  • In the parent process, fork returned the PID of the newly created process.

Now let’s consider the following program:

Now, if you run this, you’ll see the following output (the first two lines may be in a different order according to how your kernel scheduled both processes):

As you can see, since the parent exited before the child, the latter became an orphan process. It was therefore attached to init and eventually exited. If you are familiar enough with UNIX/Linux, this will probably seem logical to you. Yet… when you open a terminal, a shell (terminal’s child), and execute a process in it (shell’s child)… closing the terminal actually kills the process… Weird isn’t it? Let’s see about that.

Parent-to-child relationship in terminals and interactive shells

You can try it if you have a Linux system nearby, actually: open a terminal with an interactive shell in it (let’s say, typical bash), execute “sleep 20 &” and violently close the terminal. Check ps and… Gone! No more sleep process running! Yet, when a parent dies, its children are not killed by the kernel (as we saw above)… So… what is happening?!

I actually asked a question about this on Unix & Linux recently (which is why I’m writing this article now), because someone tried to convince me that killing a process meant killing all its children (which would be barbaric, right?). Gilles gave me quite an interesting answer, so let’s see about that.

Nowadays, every terminal we use is emulated: you can open/close it without too much trouble, and most of the time, that’s an insignificant operation. However, back in the days, terminals were much more… physical. They were connected thanks to a serial line and a modem, which could hang up at any time, putting an end to our user session. Because of that, we had to make sure that the controlling process in this terminal (basically, the shell) got properly killed along with its children. For this reason, the SIGHUP (SIGnal Hang UP) signal was created. Whenever a controlling terminal gets closed (hung up), it sends a SIGHUP to its controlling process, its shell.

When the interactive shell receives this SIGHUP, it is expected to resend another SIGHUP to all the jobs it started. This way, shutting the terminal down sent a warning to the shell, which could then put an end to all its remaining children processes. Neat, isn’t it?

Now, as it so happens… the default handler for a SIGHUP signal is… termination. For this reason, when a process receives a SIGHUP from its parent shell, it dies. This is why closing the terminal actually kills everything you started in it: because the shell detects the closing, and kills its children on purpose. There is no kernel-related, automated mechanism there. Of course, you can avoid this little twist by having your processes ignore the SIGHUP signal (or handle it differently, like bash does). This way, when your terminal is closed, the process will not react to the signal emitted by the controlling shell:

Now, if your run this in your terminal and close it, you’ll see your process attached to init, and still running (at least, for… what? you didn’t count? 15 seconds!). Similarly, you can use the nohup utility to start a program in a SIGHUP-free environment:

Now close your terminal… And there’s your sleep process, still running!

I hope you enjoyed this little article. As far as I’m concerned, that’s precisely the kind of little things that makes me find UNIX amazing… Anyway, see you later!