DEV Community

Satoru Takeuchi
Satoru Takeuchi

Posted on • Edited on

How Linux Works: Chapter2 Process Management (Part2)

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)
...
$
Enter fullscreen mode Exit fullscreen mode

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
...
Enter fullscreen mode Exit fullscreen mode

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.

States of a process

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).

Memory reclaim on process termination

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 to exit(), 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.
Enter fullscreen mode Exit fullscreen mode

"

$ ./wait-ret
The false command has terminated: 1
Enter fullscreen mode Exit fullscreen mode

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 call wait() within this signal handler.
  • SIGSTOP: Temporarily suspends the execution of a process. Pressing Ctrl+z on bash 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 by SIGSTOP 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.

Behavior of a process upon receipt of a 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
Enter fullscreen mode Exit fullscreen mode

When you run this program, it will look like this:

$ ./intignore.py
^C^C^C
Enter fullscreen mode Exit fullscreen mode

^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.

previous part
next part

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)