Processesprocess statesreadyrunningblockedwaiting

Process States

A process is always in exactly one state — new, ready, running, waiting or terminated — and the transitions are driven by four things: the scheduler taking the CPU away, the process asking for something that is not ready, that thing arriving, and exit.

ConceptualUnix-styleLinux
▶ InteractiveInterview question
Progress

The problem

A machine with 8 cores has 400 processes. At most 8 can be executing. What are the other 392 doing, why does most of the machine sit at 2% CPU while responding to everything instantly, and what does the D or Z in top actually mean?

Progressive depth

The same mechanism at different altitudes — start where you are.

Running, ready, or waiting

At any moment a process is either on a core, queued for a core, or waiting for something (a disk, a packet, a timer, a lock). Most are waiting. When the thing arrives it joins the queue; when the scheduler picks it, it runs.

The state machine

Conceptual

The textbook model has five states. New: the process is being created — the kernel record exists but the address space is not ready. Ready: it could run right now, but no core is free; it sits on a ready queue. Running: it is executing on a core. Waiting (blocked): it asked for something that is not available yet and has been taken off the ready queue until it arrives. Terminated: it has exited; its record lingers only until the parent collects the exit status.

Only four transitions matter. Ready → Running when the scheduler dispatches it. Running → Ready when the scheduler preempts it (time slice over, or a higher-priority process woke up) or it yields. Running → Waiting when it blocks. Waiting → Ready when the event happens — a disk interrupt, a packet arrival, a timer, a lock release, a signal. A process never goes Waiting → Running directly: it must earn a core again through the ready queue.

Process state transitions
admitteddispatchedpreempted / yieldblocks on I/O, lock, sleepevent arrivesexitNewReadyRunningWaitingTerminated
UserLLMAgentToolDataDecisionHumanGuardrail

Why a process leaves the CPU

A running process stops running for exactly one of a small set of reasons, and diagnosing a slow system is mostly working out which one dominates. The first three are voluntary: the process itself made a call that cannot complete yet. The last two are involuntary: the kernel took the core away.

  • Waiting for disk: read() on a file whose pages are not cached. The kernel issues the I/O and puts the process to sleep; an SSD read takes ~100 µs, a spinning disk ~5–10 ms — thousands of time slices’ worth.
  • Waiting for the network: recv() on a socket with an empty receive buffer, accept() with no pending connection, connect() waiting for the SYN-ACK. The wait may be milliseconds (a local service) or forever (a peer that vanished).
  • Sleeping / waiting for a lock or another process: sleep(), nanosleep, a futex wait on a contended mutex, wait() on a child, poll/epoll_wait with nothing ready. The process is asleep until a timer or another process wakes it.
  • Preemption: the time slice expired (a few milliseconds under Linux’s default scheduler) or a higher-priority process became ready. The process is still ready to run; it just lost its turn — Context Switching.
  • Explicit yield: sched_yield(), std::this_thread::yield(). Rare in application code, common in spin-lock loops that give up the core after a few spins.

Blocked is the normal state

The intuition that "a process is running" is wrong for almost every process almost all of the time. On a typical server, top shows one or two processes in R and hundreds in S. A web server handling 1,000 requests per second with 5 ms of CPU per request is running 5% of the time; the other 95% its workers are asleep in epoll_wait or accept. A database backend spends most of its life waiting for the client to send the next query. An editor waits for keystrokes.

This is the whole reason the OS can host hundreds of processes on eight cores: the ready queue is short because almost everyone is waiting. It is also why "the process is blocked" is not a bug report — every useful process blocks — and why the interesting question is *what* it is blocked on and *for how long*. A backend blocked on recv for a client is fine; blocked 30 s on a disk write is an incident.

The opposite pathology is a process that never blocks: a spin loop, a busy-wait polling a flag, or a CPU-bound computation. It sits in R at 100% of one core, gets preempted every few milliseconds, and immediately returns to the ready queue. The scheduler keeps the machine responsive, but that core is gone.

Unix extras: zombie, stopped and uninterruptible

Unix-style

Unix-style systems refine the model. A zombie (Z) is a process that has exited but whose parent has not yet called wait(). It holds no memory and no descriptors — only the kernel record with the exit status. A zombie is harmless in ones and a problem in thousands: each still consumes a PID, and a parent that never reaps its children eventually exhausts the PID space or the process limit. The fix is always in the parent (handle SIGCHLD, call waitpid), never in the zombie, which cannot be killed because it is already dead.

A stopped process (T) has been paused by SIGSTOP or SIGTSTP (Ctrl-Z) or by a debugger (t); it holds all its resources and resumes on SIGCONT. Linux additionally distinguishes interruptible sleep (S, wakeable by a signal — most waits) from uninterruptible sleep (D, typically mid-disk-I/O or mid-NFS call, where the kernel cannot safely abort). A process stuck in D does not respond to kill -9 until the I/O completes or fails, and a machine with many processes in D is telling you that storage is stalled — see Memory Pressure, Swap and the OOM Killer.

Linux `ps` STAT letters
R  running or runnable (on a CPU or on a ready queue)
S  interruptible sleep (waiting for an event; wakeable by a signal)
D  uninterruptible sleep (usually disk or network file system I/O)
T  stopped by a job-control signal      t  stopped by a debugger
Z  zombie: exited, not yet reaped by its parent
modifiers: s session leader · l multi-threaded · + foreground · < high priority · N low priority

Key points

  • Five states: new, ready, running, waiting, terminated; four transitions: dispatch, preempt, block, wake.
  • A process leaves the CPU voluntarily (disk, network, sleep, lock, wait on a child) or involuntarily (preemption); it never goes from waiting straight to running.
  • Most processes are blocked most of the time; that is why eight cores serve hundreds of processes, and why "blocked" alone is not a symptom.
  • A process at 100% of one core in state R is the pathology, not the blocked one.
  • Unix-style: zombies are exited-but-unreaped records (fix the parent), stopped processes hold resources, and D state means the kernel is mid-I/O and kill -9 must wait.

Why does this exist?

Mechanisms are answers to constraints. Open each question before reading the answer.

Why does a blocked process leave the ready queue instead of being skipped?

The scheduler must be O(log n) or better per decision; scanning blocked processes would make every switch slower as the machine fills up. Removing them means the ready queue holds only genuine candidates.

Why do zombies exist at all?

The exit status has to be stored somewhere until the parent asks for it; the smallest safe place is the process record itself, kept alive without any of its resources.

Why is there an uninterruptible sleep?

Some kernel operations — a DMA in flight into a page, a filesystem transaction half-written — cannot be abandoned without corrupting state; the process is held until they finish.

Process states

Process states
Only three things take a process off the CPU: it blocks, it is preempted, or it exits.
AdmitScheduler dispatchTime slice endsread() from diskI/O completesexit()parent wait()NEWREADYRUNNINGWAITINGZOMBIETERMINATEDUnix-style: PCB kept until wait()
Current state
new
Process created (NEW).
Unix-style Linux reports these as R (ready or running), S/D (waiting), Z (zombie). A zombie holds no memory — only a PID and an exit code. Many zombies mean a parent that never calls wait().

How it fails

What the failure looks like from inside real software.

  • top shows load average 40 on an 8-core box with 5% CPU: dozens of processes in D waiting on a stalled disk or NFS mount.
  • Thousands of Z entries named <defunct>: a supervisor or a container’s PID 1 (often a shell script or a Node process) never calls wait; PIDs run out and fork fails with EAGAIN.
  • A single-threaded server is "blocked" 30 s on one slow client: every other client is waiting behind it, because blocking is per-thread — The Blocking Server.
  • A process spins at 100% waiting for a flag another thread will set, starving that thread on a single core — the flag is never set until preemption happens.
  • A process in D ignores kill -9: the signal is queued and delivered only when the uninterruptible I/O completes.