The Life Cycle of Processes

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:

  1. executing in user mode
  2. executing in kernel mode
  3. ready to run
  4. sleeping
  5. newly created, not ready to run, and not sleeping
  6. 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_RUNNINGtask (process) currently running
TASK_INTERRUPTABLEprocess is sleeping but can be woken up (interrupted)
TASK_UNINTERRUPTABLE  process is sleeping but can not be woken up (interrupted)
TASK_ZOMBIEprocess terminated but its status was not collected (it was not waited for)
TASK_STOPPEDprocess 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.