COMP 3150Operating Systems

01 Introduction

A running program is not always running. It might be waiting for input from a user, blocked on a disk read, or simply queued behind another program that currently has the CPU. From the process's point of view, time passes without progress; from the operating system's point of view, the process is in a specific state that determines what can happen to it next.

The state model is the operating system's way of classifying every process in the system at every moment. It is the vocabulary the scheduler uses to decide which process runs next. It is the vocabulary the memory manager uses to decide which pages can be swapped out. It is the vocabulary you will use for the rest of the course when reasoning about what a program is doing.

This lesson presents the five-state model that describes almost every process on a modern general-purpose operating system. More elaborate models exist — the seven-state model adds Suspended states for processes swapped out to disk — but the five-state model captures the essential structure and is what the course will use throughout. By the end you will be able to read a state diagram, identify the transition that caused any particular state change, and observe process states directly on a live Linux system.

02 Learning objectives

By the end of this lesson you will be able to

  • Describe process states.
  • Explain state transitions.
  • Interpret state diagrams.
  • Analyse process behaviour.
  • Relate states to scheduling.

This lesson contributes to U3.2 and to course learning outcomes CLO2 and CLO6.

03 Required reading

  • Operating System Concepts — Chapter 3, Section 3.2 (Process States). The formal presentation of the five-state model and its transitions.
  • OSTEP — Chapter 4, the discussion of process states and the OS's decision about which process to run. Pay particular attention to the diagram showing how a process moves between states during its lifetime.
  • Linux man pages: man 1 ps (see the PROCESS STATE CODES section), man 5 proc (the stat file and its state field), man 2 wait (the blocking behaviour that produces the Blocked state).
  • Love, R. — Linux Kernel Development, 3rd ed., Chapter 3, the section on process state. How Linux concretely implements the state model and what the state flags mean.

Reading strategy

Read Silberschatz Section 3.2 first for the formal model, then the OSTEP chapter for the practical framing. Keep man 1 ps open as you read section 4.7 — the Linux state codes are a concrete realisation of the abstract states, and seeing them side by side will make both clearer.

04 Core content

4.1 The five states

At any moment, a process is in exactly one of five states.

New
The process is being created. The kernel has allocated a process control block (PCB) but has not yet admitted the process to the set of runnable processes. In some systems, the program image is being loaded from disk. On Linux, this state exists only briefly, during the few microseconds of fork before the child is ready to run.
Ready
The process is able to run. All resources it needs for immediate execution are available, but the CPU is currently serving another process. A ready process is waiting in a queue for its turn. On a system with a single CPU core, only one process can be Running at a time; every other runnable process is Ready.
Running
The process has the CPU and its instructions are being executed. On a system with N CPUs, at most N processes can be Running at any instant — but many more may be Ready, waiting for a core.
Blocked
The process cannot proceed until some event occurs — typically the completion of an I/O operation, the arrival of a signal, or the acquisition of a lock. Even if a CPU were available, the process would not be able to use it. Blocked is sometimes called "waiting" or "sleeping" in different textbooks and in Linux itself.
Terminated
The process has finished executing. Its exit status may be held for the parent, but it will never run again. On Linux, this corresponds to the "zombie" state from Lesson 3.1 — the process has terminated but its parent has not yet reaped it.

The distinction between Ready and Blocked is subtle but fundamental. A Ready process could run if the scheduler chose it. A Blocked process could not, no matter what the scheduler chose. This is why the two states have different queues and are managed by different parts of the kernel. It is the single most important conceptual distinction in the model.

4.2 The state diagram

The relationships between states are conventionally drawn as a directed graph. Each state is a node; each transition is an arrow labelled with the event that triggers it.

The five-state process model A state diagram showing the five process states — New, Ready, Running, Blocked, and Terminated — and the seven transitions between them: admit, dispatch, preempt, I/O wait, I/O complete, exit, and kill-while-blocked. Legend at bottom identifies the transition categories. THE FIVE-STATE PROCESS MODEL New Ready Running Termi- nated Blocked admit dispatch exit preempt · time slice expires I/O wait · lock wait I/O complete · event killed while blocked (some systems) Legend creation, dispatch, termination yielding the CPU or blocking becoming runnable again transition that terminates without running again Every arrow represents an event the kernel recognises and acts on.
Figure 3.2 — The five-state process model. Solid blue arrows are the creation-to-termination path; red arrows are the process yielding the CPU or blocking; green arrows are the process becoming runnable again.

4.3 The seven transitions

The model defines exactly seven transitions. Each is triggered by a specific event, and each requires the kernel to update the process's PCB and its position in the relevant queue.

Transitions in the five-state model
Transition Trigger Initiated by
New → Ready The process has been fully created and admitted Kernel (long-term scheduler)
Ready → Running The scheduler selects this process for the CPU Kernel (short-term scheduler)
Running → Ready The process is preempted (time slice expires, higher-priority process arrives) Kernel
Running → Blocked The process issues a blocking system call or waits on a lock The process itself (voluntary)
Blocked → Ready The awaited event occurs (I/O completes, lock acquired, signal arrives) Kernel (upon interrupt or signal)
Running → Terminated The process exits or is killed Process or kernel
Blocked → Terminated The process is killed while waiting (some systems) Kernel

Not every transition is allowed

A Blocked process cannot be dispatched directly to Running. It must first return to Ready. Similarly, a process cannot go from Ready to Blocked — a process can only block while it is actually executing, since blocking is a request from the running code. These constraints are important when reasoning about what the kernel can and cannot do. If you see a diagram in which Blocked → Running is drawn as a single arrow, it is a simplification, not a correct state model.

4.4 Voluntary versus involuntary transitions

The transitions differ in who initiates them. This distinction matters because it determines whether the OS can guarantee fairness.

Voluntary transitions
The process itself gives up the CPU. Running → Blocked is the clearest example: the process calls read, or wait, or tries to acquire a lock that is held. The process is saying "I cannot proceed right now." Cooperative multitasking and early time-sharing systems relied entirely on voluntary yielding; if a process never yielded, the system stopped.
Involuntary transitions
The kernel takes the CPU away from a process that would prefer to keep running. Running → Ready is the primary example: a timer interrupt fires, or a higher-priority process becomes ready. This is preemption, and it is what makes modern systems responsive.

The transition Running → Ready is the only one that requires preemption. Without it, a process that never issues a blocking call and never exits could monopolise the CPU indefinitely. A hardware timer is therefore essential: it lets the kernel interrupt any running process at a known maximum interval and reclaim control. This is why the timer interrupt appeared as a prerequisite in the historical discussion in Lesson 1.2 — it is what makes preemptive multitasking possible.

4.5 Queues associated with states

The kernel maintains a queue for every state that can hold multiple processes simultaneously. Understanding the queues clarifies what "moving a process from state A to state B" actually means operationally: the kernel removes the process from one queue and inserts it into another.

  • Job queue. All processes in the system, including those in New and Terminated. On Linux, this is not a single literal queue but the collection of all task_structs the kernel maintains.
  • Ready queue. Processes in the Ready state, waiting for the CPU. On Linux, this is a per-CPU run queue, implemented as a red-black tree of runnable tasks keyed by their virtual runtime.
  • Device queues. Processes blocked on each device or on other events. Linux maintains wait queues per event: one per open file, one per socket, one per lock, and so on. When the event occurs, the kernel walks the corresponding wait queue and wakes the blocked processes.

When a process changes state, the kernel moves it from one queue to another. On a busy system, these moves happen hundreds or thousands of times per second. The efficiency of the queue operations therefore matters directly to system performance; this is one reason the kernel uses data structures such as red-black trees and per-CPU queues rather than a single global list.

4.6 Why the Ready/Blocked distinction is fundamental

The most important conceptual takeaway from this lesson is the distinction between Ready and Blocked. It is worth restating in concrete terms.

Consider two processes on the same machine. Process A is CPU-bound: it is running a long computation with no I/O, and the scheduler has just preempted it. Process B has just called read on a file and is waiting for the disk to respond.

If the scheduler were forced to choose between these two — say, because the currently running process has just exited — it would be absurd to consider B. B cannot run until the disk responds; dispatching B would be a wasted context switch and the CPU would be idle for the duration of the disk operation. Process A, on the other hand, is perfectly ready: it can resume immediately.

This is exactly what the Ready queue encodes. It is the set of processes that could actually make progress if given the CPU. Blocked processes are not in it, because they cannot. The distinction is not a classification of convenience; it reflects the physical reality that a process waiting on a device cannot use a CPU even if one is offered.

4.7 Process states on Linux

Linux implements the five-state model but with additional detail. The STAT column of ps and the state field in /proc/<pid>/stat encode the current state of each process. The common codes are:

Linux process state codes
Code Name Corresponds to Description
R Running or runnable Running or Ready The process is executing or is on the run queue, ready to execute.
S Interruptible sleep Blocked Waiting on an event; can be woken by a signal. The most common state for ordinary processes.
D Uninterruptible sleep Blocked Waiting on I/O, and cannot be woken by a signal. Usually short-lived; processes in D cannot be killed until the I/O completes.
T Stopped — Suspended by a job-control signal (Ctrl+Z or SIGSTOP). Not one of the five canonical states; a form of suspension.
Z Zombie Terminated The process has exited but the parent has not reaped it.
I Idle kernel thread — An idle kernel thread; used only for kernel-internal threads on some systems.

Note that Linux merges Running and Ready into a single R code. This is because Linux's scheduler does not maintain a strict distinction: a task on the run queue is considered "running" from the kernel's point of view even if it is currently not executing. The distinction that matters for the scheduler — is it runnable, or is it blocked? — is captured by the difference between R and S/D.

Linux also distinguishes interruptible from uninterruptible sleep. The former can be interrupted by a signal, allowing the process to handle the signal and then continue or exit. The latter — used for short periods during critical I/O operations — cannot be interrupted by a signal, because doing so would leave the kernel in an inconsistent state. Processes in the D state are the ones that appear unkillable; kill -9 has no effect until the I/O completes.

4.8 What the diagram does not show

The five-state model is a simplification. It omits several things that real systems do:

  • Suspended states. A process that has been swapped out to disk, or is otherwise not in physical memory, is in a state the five-state model does not name. The seven-state model adds "Suspended Ready" and "Suspended Blocked" for these cases.
  • Multiple blocked reasons. A process may be blocked waiting for one of several events; the model shows a single Blocked state regardless.
  • Threads. A process in the five-state model is a single thread of control. Once we introduce threads in Unit 4, each thread has its own state, and a process may be Ready, Blocked, and Running simultaneously in its different threads.
  • Real-time states. Real-time processes have additional scheduling states that the general model does not cover.

None of these omissions change the fundamentals. The five-state model is the right level of abstraction for reasoning about general-purpose process behaviour, and everything else is a refinement.

4.9 How a process moves between states: a concrete walkthrough

To make the model concrete, consider what happens to a single process — an instance of a text editor — as you use it:

  1. You type vim notes.txt at the shell. The shell forks a child. The child begins in the New state while its address space is being set up.
  2. Admission. The kernel confirms the child has its resources. Transition: New → Ready. The process joins the ready queue behind any other runnable work.
  3. Dispatch. The scheduler selects the process. Transition: Ready → Running. The kernel performs a context switch and vim begins executing.
  4. vim reads notes.txt. It calls open, which may or may not block (usually the directory and inode are in cache). Then read. Assume the file is on disk and not cached. The read system call cannot return immediately. Transition: Running → Blocked. The process is moved to the wait queue for the storage device.
  5. Timer preempts the editor? No — the process is Blocked, so no preemption is involved. It is simply not on the ready queue anymore.
  6. The disk completes. The device controller raises an interrupt. The kernel's interrupt handler moves the editor's process back to the Ready state. Transition: Blocked → Ready.
  7. The editor is dispatched again. Transition: Ready → Running. Its read call returns, and vim continues as if nothing had happened.
  8. You type a character. vim reads from its terminal via read on stdin, blocks again, and the cycle repeats. This pattern repeats hundreds of times per minute while you edit.
  9. You save and quit. vim writes the file with write and calls exit. Transition: Running → Terminated. The exit status is held for the shell.
  10. The shell reaps. It calls wait, receives the exit status, and prints the next prompt.

If you were to watch this process with ps during the run, you would see the STAT column changing between S (when waiting for input), R (during the brief bursts of processing the input), and possibly D (during the initial file read). This is what the state model looks like in practice.

Interactive programs spend most of their time blocked

The editor in the walkthrough spends almost all its time in the Blocked state — waiting for input, waiting for the disk, waiting for something. This is normal and healthy. It means the CPU was free to run other work while the editor waited. A process that spends most of its time Ready or Running is CPU-bound; a process that spends most of its time Blocked is I/O-bound. The distinction matters to the scheduler, as Lesson 3.4 will explain.

05 Worked example

A small program that drives a process through several states on demand, so that you can watch the transitions happen.

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        /* Child: block for a while, then exit */
        printf("child: pid = %d — about to sleep\n", getpid());
        sleep(30);
        printf("child: waking up and exiting\n");
        return 0;
    } else {
        /* Parent: sleep briefly, then check child's state */
        printf("parent: pid = %d, child = %d\n", getpid(), pid);
        sleep(2);
        printf("parent: waking up, will wait for child\n");
        wait(NULL);
        printf("parent: child reaped, exiting\n");
    }
    return 0;
}

Following the child through states on a live system

  1. Compile and run.
    $ gcc -Wall -Wextra -g -o states states.c
    $ ./states &
    parent: pid = 1234, child = 1235
    child: pid = 1235 — about to sleep
    parent: waking up, will wait for child
    # the shell prompt returns because we ran in background
  2. Check the child's state. Because the child called sleep(30), it is in the Blocked state. On Linux, sleep is implemented with a timer wait, so the child appears in interruptible sleep:
    $ ps -p 1235 -o pid,stat,cmd
      PID STAT CMD
     1235 S    ./states
    The S confirms the child is blocked, not running. The parent is also sleeping, and would show S as well.
  3. Watch the state field directly. The kernel writes the current state character to /proc/<pid>/stat. The third field is the state:
    $ cat /proc/1235/stat | awk '{print "state:", $3}'
    state: S
    The S character will remain until the sleep completes.
  4. Force a state change with a signal. Send SIGSTOP to the child:
    $ kill -STOP 1235
    $ ps -p 1235 -o pid,stat,cmd
      PID STAT CMD
     1235 T    ./states
    The state is now T (stopped). The process is still in the system, still consuming a PID, but it is not competing for the CPU and its sleep will not expire.
  5. Resume it.
    $ kill -CONT 1235
    $ ps -p 1235 -o pid,stat,cmd
      PID STAT CMD
     1235 S    ./states
    The process returns to S, resuming its sleep where it left off.
  6. Watch the transition to termination. Wait for the child's 30 seconds to expire (or send SIGTERM to speed it up). The child exits and its parent reaps it. After reaping, the PID no longer exists:
    $ ps -p 1235 -o pid,stat,cmd
      PID STAT CMD
    # no output — the process is gone
    The wait in the parent collected the exit status, and the kernel removed the process-table entry.
  7. Observe a zombie (variant). Modify the program so that the parent does not call wait. Run it again and observe the child after it exits but before the parent exits:
    $ ps -p 1235 -o pid,stat,cmd
      PID STAT CMD
     1235 Z    [states] <defunct>
    The Z confirms the child is a zombie — the Terminated state in the five-state model, held until the parent reaps it.

The full lifecycle is: New (brief) → Ready (on the run queue) → Running (dispatched) → Blocked (called sleep) → Ready (sleep expires) → Running (resumed) → Terminated (exited) → gone (reaped). The ps and /proc observations correspond to whichever state the process was in when you looked.

06 Interactive activity

Activity · observe process states on your VM

Open a terminal in your course VM and work through the following. The goal is to connect the abstract state model to the state codes you can see on a running system.

  1. Read the state codes. Run man ps and search for the "PROCESS STATE CODES" section (press / and type PROCESS STATE, then Enter). Note the meaning of each code from the table in section 4.7.
  2. See a running process. In one terminal, run yes > /dev/null &. This is a pure CPU loop. In another terminal, run ps aux | grep yes repeatedly and watch the STAT column. Is it always R, or does it change?
  3. See a sleeping process. Kill the yes process. Now run sleep 60 &. Watch it with ps. It will be in S state — interruptible sleep.
  4. See a stopped process. Start sleep 300 in the foreground in one terminal. In another, run kill -STOP <PID>. Check ps. The state will be T. Restart it with kill -CONT <PID>.
  5. See a zombie. Compile the variant from the worked example in which the parent does not call wait. Run it in the background. Use ps aux | grep Z to find the zombie. What does the CMD column say for it?
  6. Watch the state field directly. Pick any process's PID and repeatedly read the state from /proc:
    for i in $(seq 1 20); do
        awk '{print $3}' /proc/<PID>/stat
        sleep 0.1
    done
    Run this against a CPU-bound process and against a sleeping process. Does the state change? Why or why not?
  7. Follow a process through its whole life. Write a script that forks a child, has the child do work for a few seconds, and has the parent watch the child's state change. Record the sequence of states you observe. (The worked example above is a good starting point.)

Reflect: which states were easiest to observe? Which were so brief that you could only catch them by luck? What does the difficulty of observing the Running state tell you about how much of a process's life is spent actually executing?

07 Knowledge check

Check your understanding

Question 1

A process is currently in the Blocked state. List every state it can transition to next, and describe the event that causes each transition.

Show answer
A Blocked process can go to two possible next states: Ready — when the awaited event occurs (I/O completes, lock acquired, signal delivered) — or Terminated — if the process is killed while waiting. It cannot go directly to Running, because the scheduler only chooses from the Ready queue. This is a fundamental constraint of the model, and a diagram in which Blocked → Running appears as a single arrow is incorrect.

Question 2

Why is the Running → Ready transition impossible without hardware support?

Show answer
Because the process currently holds the CPU, and the CPU is executing its instructions. For the kernel to take the CPU away, the kernel must run. But the kernel can only run when it has control. The only way to interrupt a running process without its cooperation is a hardware timer interrupt, which the CPU raises at a fixed interval and which vectors into a kernel handler. Without a timer, the kernel could never preempt a process that did not yield voluntarily. This is why a timer is a prerequisite for preemptive multitasking — a point established historically in Lesson 1.2.

Question 3

Distinguish a Blocked process from a Ready process in one sentence, and explain why the distinction matters for the scheduler.

Show answer
A Ready process could run immediately if given the CPU; a Blocked process could not, no matter how much CPU it were given, because it is waiting for an external event. The distinction matters because it determines the contents of the ready queue: the scheduler only considers Ready processes, and dispatching a Blocked process would waste a context switch and gain nothing. The Ready queue is the set of processes that can actually make progress if given the CPU.

Question 4

On Linux, what is the difference between the S and D state codes, and why does a process in the D state sometimes appear to be unkillable?

Show answer
Both S and D mean the process is blocked, but they differ in whether a signal can interrupt the wait. S is interruptible sleep: a signal (such as SIGTERM) will wake the process, letting it handle the signal and then either continue or exit. D is uninterruptible sleep: signals are held until the wait completes. The D state is used during short critical operations — typically hardware I/O that must complete before the kernel can restore consistency — where interrupting the wait would leave the kernel or the device in an inconsistent state. A process in D will respond to kill -9 only after the I/O completes; until then it appears unkillable.

Question 5

In the five-state model, a process cannot transition from Ready directly to Blocked. Why not?

Show answer
Because blocking is a request that only the running process can make. To enter the Blocked state, a process must issue a blocking system call (read, wait, pause, and so on) or wait on a lock. Those are actions that only make sense from code that is actually executing. A process that is Ready is sitting on the run queue, not executing; it has no opportunity to issue a blocking call, and the kernel has no reason to move it to Blocked. The only way for a ready process to become blocked would be if the kernel itself decided to block it, which would be a fundamentally different operation — and would model a kernel-initiated suspension, not a process-initiated wait.

08 Unit discussion

Discussion prompt

The five-state model assumes that a process is either interruptible or it is not — that once a process is blocked, it stays blocked until its event completes. Real systems complicate this: a process in interruptible sleep can be woken by a signal even before its I/O completes, and must handle that case explicitly.

Find an example of code that must handle interrupted blocking calls correctly. What happens if it does not? Support your answer with a concrete reference — a man page, a section of source, or a programming idiom you have seen. Then consider: does the need to handle signals break the state model, or does it merely refine what "Blocked" means?

Post your response to the Unit 3 discussion forum and reply to at least two classmates. Supports CLO2 and CLO6.

09 Practice exercise

This ungraded exercise consolidates the lesson and prepares you for the state-tracking aspects of Lab 2. Aim to spend 45–60 minutes on it.

  1. Draw the state model from memory. Without looking at Figure 3.2, draw the five-state model and label all seven transitions. Then check against the lesson. Which transitions did you miss, and why?
  2. Classify the transitions. For each of the seven transitions, decide whether it is voluntary (initiated by the process) or involuntary (initiated by the kernel). Are any ambiguous? Explain your reasoning.
  3. Write a state-tracking program. Write a C program that:
    • Forks a child.
    • Has the child print its own state by reading /proc/self/stat and extracting the third field.
    • Has the child enter a sleep and then wake, printing its state before and after.
    • Has the child exit, and the parent wait and report the exit status.
    Compile with the course flags and run under strace -f -e trace=sleep,nanosleep,clone,wait4. Identify the system calls that trigger the state transitions.
  4. Trace a shell command. Run strace -T -tt ls /tmp. Look for the read and openat calls. Which of them blocked? The -T flag shows the duration of each call; long durations indicate actual blocking.
  5. Observe a long-running process. Run a background job that mixes CPU and I/O — for example, find / -type f > /dev/null 2>&1 &. Poll the process's state every 100 milliseconds for 10 seconds using a loop that reads /proc/<PID>/stat. Record how many times the state was R, how many S, and how many D. What does the distribution tell you about the workload?
  6. Reflect. In your own words, describe one concrete situation in which a process that is Ready is not Running, and one in which a process that is Blocked would be a bad choice for the scheduler even though the CPU is idle. Use the two-process example from section 4.6 as a starting point.

10 Summary

  • A process occupies one of five states at every moment: New, Ready, Running, Blocked, Terminated.
  • Ready means "could run if given the CPU"; Blocked means "cannot proceed until an event occurs." The distinction is the most important in the model.
  • Seven transitions connect the states. Of these, Running → Ready is the only one that requires preemption, and preemption requires hardware timer support.
  • Transitions are either voluntary (the process gives up the CPU, e.g., Running → Blocked) or involuntary (the kernel takes the CPU away, e.g., Running → Ready).
  • The kernel maintains queues for the multi-process states: the job queue (all processes), the ready queue (Ready processes), and device queues (blocked processes waiting on specific events).
  • On Linux, the state is exposed through the STAT column of ps and the third field of /proc/<pid>/stat. Common codes are R (running/runnable), S (interruptible sleep), D (uninterruptible sleep), T (stopped), and Z (zombie).
  • The five-state model omits some details — suspended states, multiple blocked reasons, per-thread states, and real-time extensions — but captures the essential structure that all of these refine.

Looking ahead

Lesson 3.3 examines the data structure the kernel uses to track each process's state — the process control block. The PCB is where the current state is recorded and where every other piece of process metadata lives.

11 References

  • Silberschatz, A., Galvin, P. B., & Gagne, G. Operating System Concepts, 10th ed., Chapter 3, Section 3.2.
  • Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C. Operating Systems: Three Easy Pieces, Chapter 4. Available online at ostep.org.
  • Love, R. Linux Kernel Development, 3rd ed., Chapter 3 (Process Management), especially the process-state discussion. Addison-Wesley, 2010.
  • Linux man-pages: man 1 ps (PROCESS STATE CODES), man 5 proc (the /proc/<pid>/stat format), man 2 wait, man 7 signal.
  • Bach, M. J. The Design of the UNIX Operating System, Chapter 6. Prentice Hall, 1986. (Classical presentation of the process state model.)
👤