The Relationship between Parent Process and Child Process
In the previous section, we discussed how a parent process generates child processes to create new processes. So where does one end up when following the parent process of the parent process...? This section will clarify this.
When you boot your computer, the system is initialized in the following order:
1.Turn on the power switch
2.Firmware, such as BIOS or UEFI, boots up and initializes the hardware
3.The firmware launches a bootloader like GRUB
4.The bootloader launches the OS kernel. Here we'll consider the Linux kernel
5.The Linux kernel starts the init process
6.The init process starts child processes, which in turn start their child processes, and so on, forming a tree structure of processes
Let's check if this is actually happening.
The pstree
command displays the parent-child relationship of processes in a tree structure. pstree
displays only command names by default, but it is useful to also display the PID by adding the -p
option. In my environment, it looks like this:
$ pstree -p
systemd(1)-+-ModemManager(688)-+-{ModemManager}(723)
| `-{ModemManager}(728)
...
├─sshd(960)───sshd(19191)───sshd(19261)───bash(19262)───pstree(19638)
...
$
You can see that the ancestor of all processes is the init process with pid=1 (displayed as systemd
on the pstree
command). You can also see, for example, that the pstree(19638)
was run from bash(19262)
.
States of Processes
In this section, we will discuss the concept of process states.
As already mentioned, there are always a large number of processes in a Linux system. Do these processes always use the CPU continuously? The answer is no.
The start time of a process running on the system, as well as the total amount of CPU time used, can be checked with the START
field and TIME
field of ps aux
.
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
...
sat 19262 0.0 0.0 12888 6144 pts/0 Ss 18:24 0:00 -bash
...
From this output, it can be seen that bash(19262)
started at 18:24 and has used almost no CPU time since then. The time at which I am writing this manuscript is around 20:00, so even though it has been over an hour since it was started, you can see that this process has used less than a second of CPU time. The same can be said for many other processes, which I will omit here.
So, what were these processes mainly doing after they started? They were sleeping, waiting for some event to occur without using the CPU. In the case of bash(19262)
, it was waiting for user input, as there was nothing to do until the user input something. This can be seen from the STAT field of the ps output. A process with an S
in the first character of the STAT field is in a sleep state.
On the other hand, a process that wants to use the CPU is said to be in a runnable state. At this time, the first character of STAT becomes R
. When a process is actually using the CPU, it is said to be in a running state. How a process transitions between a running state and a runnable state will be discussed in the "Time Slices" and "Context Switches" sections of Chapter 3.
When a process terminates, it becomes a zombie state (STAT field is Z
), and then it disappears. The meaning of the zombie state will be explained later.
The states of a process are summarized in the following figure.
As can be seen from this figure, a process transitions through various states during its lifetime.
If all processes on the system are in a sleep state, what is happening on the logical CPU? In fact, at this time, a special process called an idle process that "does nothing" is operating on the logical CPU. The idle process is not visible from ps
.
The simplest implementation of an idle process is to perform a wasteful loop until a new process is created or a sleeping process wakes up. However, this wastes power consumption, so it's not usually done. Instead, it uses a special CPU instruction to put the logical CPU into a sleep state, waiting in a state that reduces power consumption until one or more processes become runnable.
One of the main reasons why the battery lasts longer on your notebook PC or smartphone when you are not running any programs is because the logical CPU spends a lot of time in an idle state, which reduces power consumption.
Process Termination
In this section, we'll discuss how a process is terminated by invoking a system call called exit_group()
. Like fork
or fork-and-exec
, when you call the exit()
function, this system call is internally invoked. Even if the program itself doesn't call it, libc or other libraries will do so internally. Within exit_group()
, the kernel reclaims resources such as memory used by the process (see the following figure).
After a process has terminated, the parent process can obtain the following information through system calls such as wait()
or waitpid()
:
- The process's return value. This is equal to the remainder when the argument to the
exit()
function is divided by 256. To make it clearer, if you specify a number between 0 and 255 as the argument toexit()
, the return value will be the same as the argument. - Whether the process was terminated by a signal (discussed later)
- How much CPU time the process used before termination
Through this mechanism, for example, you can handle anomalies such as outputting error logs if a process has terminated abnormally based on its return value.
In bash, you can obtain the termination status of a process that has been run in the background using the wait
built-in command, which internally calls the wait()
system call. Below, we run the wait-ret.sh
program, which retrieves and outputs the return value of the always-return-1 false
command.
#!/bin/bash
false &
wait $! # wait for the termination of "false" process. We can get the PID of this program through `$!` variable.
echo "The false command has terminated: $?" # We can get the exit state of the process from `$?` variable.
"
$ ./wait-ret
The false command has terminated: 1
Zombie Processes and Orphan Processes
The fact that a parent process can obtain the state of a child process through wait()
system calls implies that, conversely, a child process exists in some form on the system from the time it terminates until its parent process invokes these system calls. A process that has terminated but whose parent has not obtained its termination status is called a zombie process. The name probably comes from the state of being 'dead but not dead', which is indeed a quite vivid term.
Generally, a parent process needs to appropriately reclaim the termination status of its child processes to prevent the system from overflowing with zombie processes and squandering resources. If there are a large number of zombie processes on the system during system operation, it may be worthwhile to suspect a bug in the program corresponding to the parent process.
If the parent of a process terminates before wait()
, the process becomes an orphan process. The kernel makes init
the new parent of the orphan process. If the parent of a zombie process terminates, the zombie process attacks init
. This isn't a pleasant situation for init
. However, init
is smart and regularly issues wait()
to reclaim system resources. It's quite a well-implemented system.
Signals
Processes generally run continuously according to a single stream of execution. Although there are conditional branch instructions, these merely shift the flow according to predefined conditional statements. In contrast, a signal is a mechanism for a process to notify another process and forcibly change the flow of execution from the outside.
There are several types of signals, but the most commonly used is undoubtedly SIGINT
. This signal is sent when you press Ctrl+c
in a shell like bash
. By default, a process that receives SIGINT
terminates immediately. Regardless of how the program is structured, the ability to terminate a process the instant a signal is issued is convenient, and many Linux users use this signal, whether they are aware of its effect or not.
Signals can also be sent from outside bash
using the kill
command. For example, if you want to send SIGINT
, execute kill -INT <pid>
. In addition to SIGINT
, there are signals like the following:
-
SIGCHLD
: Sent to the parent process when a child process terminates. It's common to callwait()
within this signal handler. -
SIGSTOP
: Temporarily suspends the execution of a process. PressingCtrl+z
onbash
halts the execution of the running program. At this time,bash
is sending this signal to the process. "-SIGCONT
: Resumes the execution of a process that was stopped bySIGSTOP
or similar.
You can see a list of signals by running the man 7 signal
command.
As I wrote earlier that 'a process that receives SIGINT
will terminate by default', it does not mean that a process will always terminate when it receives the SIGINT
signal. A process can pre-register a signal handler for each signal. If the process receives the corresponding signal during execution, it temporarily interrupts the current operation, activates the signal handler, and then returns to the original location and resumes operation. Alternatively, it can be set to ignore the signal.
By using a signal handler, you can create an annoying program (intignore.py
) that does not terminate even when Ctrl+c
is pressed. For example, you can create it in Python like this.
#!/usr/bin/python3
import signal
# Set to ignore SIGINT
# - 1st arg: The signal to ignore
# - 2nd arg: signal handler
signal.signal(signal.SIGINT, signal.SIG_IGN)
while True:
pass
When you run this program, it will look like this:
$ ./intignore.py
^C^C^C
^C
indicates that you've typed Ctrl+c
. It's really annoying, isn't it?"
If you've tried this command yourself, please send intignore
to the background with Ctrl+z
and then kill it with kill
. At this time, the default SIGTERM
is thrown, so it can terminate.
Column: The Absolutely Lethal SIGKILL Signal and the Absolutely Indestructible Process
SIGKILL
could be considered the last resort to use when a process does not die gracefully by other signals like SIGINT
. This is a special one among all signals, as a process that receives this signal is always terminated. It is not possible to change the behavior through a signal handler. From the signal name KILL
, you can feel the strong intention to definitely kill the process.
However, after writing all this, there are occasionally nefarious processes that won't die, even with SIGKILL
. For some reason, these processes are in a special state called uninterruptible sleep
, where they do not accept signals for a long time. The first character in the STAT
field of ps aux
for these processes is D
. This often occurs when disk I/O takes a long time. It may also be due to some problem with the kernel. In any case, there is often nothing that can be done from the user's side.
NOTE
This article is based on my book written in Japanese. Please contact me via satoru.takeuchi@gmail.com if you're interested in publishing this book's English version.
Top comments (0)