The Life Cycle of Processes
From the time a process is created with a
fork() until it has completed its job and disappears
from the process table,
it goes through many different states. The state a
process is in changes many times during its “life.” These changes can
occur, for example, when the process makes a system call,
it is someone else’s
turn to run, an interrupt
occurs, or the process asks for a resource that is
currently not available.
A commonly used model shows processes operating
in one of six separate states, which you can find in sched.h:
- executing in user mode
- executing in kernel mode
- ready to run
- sleeping
- newly created, not ready to run, and not
sleeping
- issued exit system call
(zombie)
The
states listed here describe what is happening conceptually and do not indicate
what “official” state a process is in. The official states are listed below:
| TASK_RUNNING | task (process) currently running |
| TASK_INTERRUPTABLE | process is sleeping but can be woken up (interrupted) |
| TASK_UNINTERRUPTABLE | process is sleeping but can not be woken up (interrupted) |
| TASK_ZOMBIE | process terminated but its status was not collected (it was not waited for) |
| TASK_STOPPED | process stopped by a debugger or job control |
| TASK_SWAPPING | (removed in 2.3.x kernel) |
Table – Process States in sched.h
In my list of
states, there was no mention of a process actually being on the processor
(TASK_RUNNING). Processes that are running in kernel
mode or in user mode are both in the
TASK_RUNNING state. Although there is no 1:1
match-up, I hope you’ll see what each state means as we go through the following
description. You can see how this all looks graphically in the figure
below.
Image – Process states(interactive)
A newly created process enters the system in state 5.
If the process is simply a copy of the original process (a
fork but no exec), it then begins to run in the state that the original process was in (1
or 2). (Why none of the other states? It has to be running to fork
a new process.) If an exec() is made, then this process will end up in
kernel mode (2). It is possible that the fork()-exec() was done in
system mode and the process goes into state 1. However, this highly unlikely.
When a process is running, an interrupt
may be generated (more
often than not, this is the system clock) and the currently running process is
pre-empted (3). This is the same state as state 3 because it is still ready to
run and in main memory. The only difference is that the process was just kicked
off the processor.
When the process makes a system call
while in user mode
(1), it moves into state 2 where it begins to run in kernel
mode. Assume at this
point that the system call
made was to read a file on the hard disk. Because the
read is not carried out immediately, the process goes to sleep, waiting on the
event that the system has read the disk and the data is ready. It is now
in state 4. When the data is ready, the process is awakened. This does not mean
it runs immediately, but rather it is once again ready to run in main memory
(3).
If a process that was asleep is awakened (perhaps when the data is
ready), it moves from state 4 (sleeping) to state 3 (ready to run). This can be
in either user mode
(1) or kernel
mode (2).
A process can end its life by
either explicitly calling the exit()
system call or having it called for them. The
exit() system call
releases all the data structures
that the process was using. One exception is the slot in the process table,
which is the responsibility of the init
process. The reason for hanging around is that the slot in the process table
is
used for the exit code of the exiting process. This can be used by the parent
process to determine whether the process did what it was supposed to do or
whether it ran into problems. The process shows that it has terminated by
putting itself into state 8, and it becomes a “zombie.” Once here, it
can never run again because nothing exists other than the entry in the process
table.
This is why you cannot “kill” a zombie
process. There is nothing there to kill. To kill a process, you need to send it
a signal
(more on signals later). Because there is nothing there to receive or
process that signal,
trying to kill it makes little sense. The only thing to do
is to let the system clean it up.
If the exiting process has any children,
they are “inherited” by init.
One value stored in the process structure is the PID
of that process’ parent
process. This value is (logically) referred to as the parent process ID
or PPID.
When a process is inherited by init, the value of its PPID
is changed to 1 (the PID
of
init).
A process’ state change can cause a
context switch in several different cases. One case is when the process
voluntarily goes to sleep, which can happen when the process needs a resource
that is not immediately available. A very common example is your login
shell.
You type in a command, the command is executed, and you are back to a shell
prompt. Between the time the command is finished and you input your next
command, a very long time could pass – at least two or three seconds.
Rather than constantly checking the keyboard for input, the shell
puts itself to sleep while waiting on an event.
That event might be an interrupt from the
keyboard to say “Hey! I have input for you.” When a process puts
itself to sleep, it sleeps on a particular wait channel (WCHAN). When the
event that is associated with that wait channel
occurs, every process
waiting on that wait channel
is woken up. To find out what wait channels processes are waiting on for your system
see the section on system monitoring.
There is probably only one
process waiting on input from your keyboard at any given time. However, many
processes could be waiting for data from the hard disk. If so, there might be
dozens of processes all waiting on the same wait channel.
All are woken up when
the hard disk is ready. It may be that the hard disk has read only the data for
a subset of the processes waiting. Therefore, if the program is correctly
written, the processes check to see whether their data is ready for them. If
not, they put themselves to sleep on the same wait channel.
When a
process puts itself to sleep, it is voluntarily giving up the CPU.
It may
be that this process had just started its turn when it noticed that it didn’t
have some resource it needed. Rather than forcing other processes to wait
until the first one gets its “fair share” of the CPU,
that process is
nice and lets some other process have a turn on the CPU.
Because the
process is being so nice to let others have a turn, the kernel
will be nice to
the process. One thing the kernel
allows is that a process that puts itself to
sleep can set the priority at which it will run when it wakes. Normally, the
kernel process scheduling algorithm calculates the priorities of all the
processes. In exchange for voluntarily giving up the CPU,
however, the process
is allowed to choose its own priority.