It is well known that the current time-sharing operating system can run multiple programs on a single CPU, making these programs appear to be running concurrently on the surface. Linux is one such operating system.
In a Linux system, each running instance of a program corresponds to one or more processes. The Linux kernel needs to manage these processes so that they run "simultaneously" in the system. The Linux kernel has two aspects of this management of processes: process state management, and process scheduling.
process Status
Under Linux, the PS command allows us to see the processes that exist in the system and their status:
R(task_running), executable state.
Only processes in that state are likely to run on the CPU. At the same time, multiple processes may be in the executable state, and the TASK_STRUCT structure (Process Control block) of those processes is placed in the corresponding CPU's executable queue (a process can only appear in the executable queue of one CPU). The task of the Process scheduler is to select a process from each CPU's executable queue to run on that CPU separately.
As long as the executable queue is not empty, its corresponding CPU can not be lazy, it is necessary to execute one of the processes. The CPU is generally called "busy" at this time. Correspondingly, the CPU "idle" means that its corresponding executable queue is empty, so that the CPU has nothing to do.
Someone asked, why does the Dead loop program cause the CPU to occupy high? Because the Dead loop program is basically always in the task_running state (the process is in the executable queue). Unless there are some very extreme situations (such as a severe shortage of system memory, some of the pages of the process need to be swapped out and cannot be allocated to memory when the page needs to be swapped ...). ), otherwise this process will not sleep. So the CPU's executable queue is always not empty (at least one process exists) and the CPU is not "idle".
Many operating system textbooks define a process that is executing on the CPU as a running state, while a process that is executable but not yet scheduled to execute is defined as a ready state, both of which are unified to the Task_running state under Linux.
S (task_interruptible), an interruptible sleep state.
A process that is in this state is suspended because it waits for a certain event to occur (such as waiting for a socket connection, waiting for a semaphore). The TASK_STRUCT structure of these processes is placed in the waiting queue for the corresponding event. When these events occur (triggered by an external interrupt or triggered by another process), one or more processes in the corresponding wait queue will be awakened.
with the PS command, we see that in general, most processes in the process list are in the Task_interruptible state (unless the machine's load is high). After all, the CPU is so one or two, the process is almost dozens of hundred, if not most of the process is in sleep, the CPU how to respond to come over.
D (task_uninterruptible), non-disruptive sleep state.
Similar to the task_interruptible state, the process is asleep, but the process is now non-disruptive. Non-interruptible means that the CPU does not respond to interrupts from external hardware, but rather that the process does not respond to asynchronous signals.
In most cases, the process should always be able to respond to an asynchronous signal when it is in a sleep state. Otherwise you will be surprised to find that kill-9 unexpectedly kill a sleeping process! So we also understand why the process of PS command sees almost no task_uninterruptible state, but always task_interruptible state.
The significance of the task_uninterruptible state is that certain processing processes of the kernel cannot be interrupted. In response to an asynchronous signal, the program's execution process is inserted into a process to process the asynchronous signal (the inserted process may exist only in the kernel state, or may extend to the user state), and the original process is interrupted.
When a process is operating on some hardware, such as when a process calls a read system call for a device file, and the read system call eventually executes to the corresponding device-driven code and interacts with the corresponding physical device, it may be necessary to use the Task_ The uninterruptible State protects the process from interruption in the process of interacting with the device, causing the device to fall into an uncontrolled state. (for example, the read system calls the DMA that triggers a disk-to-user-space memory, and if the process exits due to a response signal, the memory that is being accessed by the DMA may be released.) In this case, the task_uninterruptible state is always very short-lived, which is largely impossible to capture via the PS command. There is also an easily captured task_uninterruptible state in the
Linux system. After the vfork system call is executed, the parent process enters the task_uninterruptible state until the child process calls exit or exec.
The
can get the process in the Task_uninterruptible state with the following code:
#include <unistd.h>
Void Main () {
if (!vfork ()) Sleep (100 );
}
Compile and then PS:
[email protected]:~/test$ ps-ax | grep a\.out
4371 pts/0 d+ 0:00./a.out
4372 pts/0 s+ 0:00./a.out
4374 PTS/1 s+ 0:00 grep a.out
Then we can experiment with the power of the task_uninterruptible state. Regardless of kill or kill-9, this task_uninterruptible state of the parent process is still standing.
T (task_stopped or task_traced), pausing state or tracking state.
sends a sigstop signal to the process that enters the task_stopped state as it responds to the signal (unless the process itself is in a task_uninterruptible state and does not respond to the signal). (Sigstop is very mandatory, as is the Sigkill signal.) The user process is not allowed to reset the corresponding signal handler function through the system call of the signal series. The
sends a sigcont signal to the process, allowing it to recover from the task_stopped state to the task_running state.
When a process is being traced, it is in the special state of task_traced. "Being traced" refers to a process that pauses and waits for the process that tracks it to operate on it. For example, in GdB, the next breakpoint on the tracked process, the process stops at the breakpoint at the time of the task_traced state. At other times, the tracked process is still in the States mentioned earlier.
For the process itself, the task_stopped and task_traced states are similar, indicating that the process is paused.
While the task_traced state is equivalent to a layer of protection above the task_stopped, the process in task_traced state cannot respond to the sigcont signal and is awakened. The debugged process can only restore the task_running state until the debug process executes Ptrace_cont, Ptrace_detach, and so on through the PTRACE system call (by specifying the action by PTRACE the system call's parameters), or the debug process exits.
Z(Task_dead-exit_zombie), exiting the status, the process becomes a zombie process.
The process is in the Task_dead state during the exit process.
In this exit process, all the resources that the process occupies will be recycled, in addition to the TASK_STRUCT structure (and a few resources). So the process only left task_struct such an empty shell, so called zombies.
The reason for the retention of task_struct is that the exit code of the process, as well as some statistical information, are stored in task_struct. And its parent process is likely to be concerned about this information. In the shell, for example, the $ variable saves the exit code for the last exiting foreground process, and this exit code is often used as a condition for the IF statement.
Of course, the kernel can also store this information elsewhere, freeing the task_struct structure to save some space. However, the use of the TASK_STRUCT structure is more convenient because the kernel has established a relationship between the PID and the Task_struct lookup, as well as the parent-child relationship between processes. Releasing the task_struct, you need to create some new data structures so that the parent process can find the exit information for its child processes.
The parent process can wait for the exit of one or some of the child processes through a system call to the wait series, such as WAIT4, Waitid, and get its exit information. Then the system call of the wait series will also release the Corpse (task_struct) of the child process.
As the child process exits, the kernel sends a signal to its parent process to notify the parent process to "corpse". This signal is SIGCHLD by default, but can be set when a child process is created through the clone system call.
The following code enables the creation of a Exit_zombie State process:
#include <unistd.h>
void Main () {
if (fork ())
while (1) sleep (100);
}
Compile and run, then PS:
[Email protected]:~/test$ ps-ax | grep a\.out
10410 pts/0 s+ 0:00./a.out
10411 pts/0 z+ 0:00 [a.out] <defunct>
10413 pts/1 s+ 0:00 grep a.out
The child process of this zombie state persists as long as the parent process does not exit. So if the parent process exits, who is going to "corpse" the child process?
When the process exits, it will host all its child processes to another process (making it a child of another process). Who's hosting it for? It may be the next process that exits the process group where the process is located, if one exists, or the number 1th process. So every process, every moment, has a parent process present. Unless it is process number 1th.
Process 1th, PID 1, also known as the init process.
After the Linux system is started, the first user-state process created is the INIT process. It has two missions:
1, execute the System initialization script, create a series of processes (they are the descendants of the Init process);
2, in a dead loop waiting for its child process exit event, and call Waitid system call to complete the "corpse" work;
The init process will not be paused and will not be killed (this is guaranteed by the kernel). It is in the task_interruptible state while waiting for the child process to exit, while the "corpse" process is in the task_running state.
X (Task_dead-exit_dead), exit status, process is about to be destroyed.
and the process may not retain its task_struct during the exit process. For example, this process is a process that has been detach in multithreaded programs. or the parent process explicitly ignores the SIGCHLD signal by setting the handler of the SIGCHLD signal to sig_ign. (This is the POSIX rule, although the exit signal for a child process can be set to a signal other than SIGCHLD.)
At this point, the process is placed in the Exit_dead exit state, which means that the next code immediately releases the process completely. So the Exit_dead state is very short and almost impossible to capture via the PS command. The initial state of the
process
Process is created by the system call of the Fork series (fork, clone, Vfork), and the kernel (or kernel module) can also be kernel_ The thread function creates a kernel process. The functions that create child processes essentially do the same thing-copying the calling process to get the child process. (You can use option parameters to determine whether a variety of resources is shared or private.)
so since the calling process is in the task_running state (otherwise, it is not running, how can it be called?). ), the child process is also in the Task_running state by default.
In addition, calling clone and kernel function Kernel_thread on the system call also accepts the clone_stopped option, which resets the initial state of the child process to task_stopped.
Process State Change
Process since its creation, the state may change a series of changes until the process exits. While there are several process states, the process state changes in only two directions-from the task_running state to a non-task_running state, or from a non-task_running state to a task_running state.
that is, if a sigkill signal is sent to a task_interruptible state, the process is awakened (into the task_running state) and then exited (into a task_dead state) in response to the sigkill signal. does not exit directly from the task_interruptible state.
The process changes from a non-task_running state to a task_running state, and is implemented by a wake-up operation from another process (or possibly an interrupt handler). The process setting that wakes up is task_running the state of the wake process, and then joins its task_struct structure to the executable queue of a CPU. The awakened process will then have the opportunity to be dispatched for execution.
When a process changes from a task_running state to a non-task_running state, there are two ways:
1, the response signal and enter the task_stoped state, or Task_dead state;
2. Perform a system call to actively enter the Task_interruptible state (such as a nanosleep system call), or Task_dead state (such as an exit system call), or to enter task_ because the resources required to execute the system call are not met A interruptible state or task_uninterruptible state (such as a select system call).
Obviously, both of these situations can only occur if the process is executing on the CPU.
Turn Linux process State analysis