Sign in
← Linux: processes in depth
12 min · medium · Lesson 2 of 7

Process states: R, S, D, T, Z

Process states R/S/D/T/Z and where to find them.

What the letters R, S, D, T, Z mean in the STAT column, and where to look for them.

Why this matters

A service is "stuck", a queue isn't draining, kill -9 doesn't help. The first thing worth doing in that situation is to check the process's state. One letter immediately splits these into different stories: the process is burning CPU, sleeping while it waits, paused, or already dead and waiting on its parent. That letter decides what to do next: look for an infinite loop, check the disk or the network, wake the process with a signal, or deal with its parent.

This lesson continues "Process lifecycle: fork, exec, exit". There, a process was born and terminated. Here we'll look at the states it can be in between those two events.

Why a process needs a state

The kernel keeps all processes in one big table. Each entry has a state field. It tells the scheduler what to do with the process right now: give it the CPU, leave it sleeping, or leave it alone entirely.

A process state is its current status in the kernel's table. There aren't many values, and almost all of them fit in a single letter code. We'll go through the five main ones: R, S, D, T, Z. There's also I for idle kernel threads and X for a process at the moment it's being removed. You won't catch X in the wild, but you'll see I in ps aux on any Linux server, on lines like [kworker/...].

R — running or ready to run

R (running/runnable) means one of two things: the process is running on a CPU core right now, or it's sitting in the ready queue waiting for its turn. The scheduler switches between such processes many times a second. That's why, on an idle machine, R is usually seen on the ps command itself, since it's the one actually running.

$ ps aux | grep -w R
deploy 1455  0.2  0.0  12100  3200 pts/0    R+   10:14   0:00 ps aux

If a process holds R for a long time and its %CPU column is close to 100, it's computing nonstop. That's either heavy work or an infinite loop.

S — interruptible sleep

S (interruptible sleep): the process is asleep and waiting for an event, such as an incoming packet, a keypress or a reply from another process. This is the normal state for most programs. A web server waiting for a request sits in S almost all the time and burns no CPU.

$ ps aux
USER   PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
www    812  0.0  0.1  12400  5600 ?        S    09:01   0:21 nginx: worker process

This kind of sleep can be interrupted by a signal. A SIGTERM arrives, and the process wakes up to handle it.

D — uninterruptible sleep

D (uninterruptible sleep): the process is waiting for an I/O operation to finish, usually to disk or over the network. Until the operation completes, the kernel won't wake the process or deliver it any signals.

Which leads to a fact that breaks a lot of people's mental model: a process in D won't even take kill -9. SIGKILL gets queued and only fires once the operation finishes and the process leaves D.

$ ps aux | grep -w D
deploy 9021  0.0  0.0   8900  3100 ?        D    11:02   0:00 tar czf /mnt/nfs/backup.tgz /srv

A short D is normal during heavy disk reads. A process stuck in D for a long time usually means a failing disk or a hung network filesystem (NFS).

There's a caveat. The kernel makes some waits "killable" (TASK_KILLABLE, since kernel 2.6.25): such a process still shows up as D in ps, but it does respond to SIGKILL. So it's worth trying kill -9 on a process in D. If it doesn't go away, that's not your fault: it's waiting for an answer from the device.

T — stopped

T (stopped): the process is paused and not running. This happens after Ctrl+Z in a terminal (the terminal sends SIGTSTP) or after a SIGSTOP signal. The process hasn't gone anywhere: its memory, open files and PID are all still there. It's just frozen, waiting for the command to continue.

$ sleep 300
^Z
[1]+  Stopped                 sleep 300
$ ps aux | grep sleep
user  2043  0.0  0.0   8300   900 pts/0    T    11:10   0:00 sleep 300

You can wake it with SIGCONT: fg or bg in the same terminal, kill -CONT <PID> from anywhere. The pause lifts without a restart, and the process continues from the same place with the same PID. A stopped process also receives SIGKILL and terminates right away. There's no need to wait for SIGCONT first.

A lowercase t (tracing stop) means something else: a debugger, such as strace or gdb, stopped the process.

Z — zombie

Z (zombie): the process has already finished, but its parent hasn't yet collected its exit code with a wait() call. An entry like this holds only the PID in the process table; its memory and files have already been freed.

$ ps aux | grep defunct
user  3301  0.0  0.0      0     0 ?        Z    11:15   0:00 [worker] <defunct>

Zombies get a lesson of their own. For now, remember two things. Z doesn't eat memory or CPU, it only takes up a row in the table. And you can't kill it, it's already dead: kill -9 on a zombie changes nothing. The entry disappears once the parent calls wait() or terminates itself.

Where to look for the state

The first place to look is the STAT column in ps aux. The main letter sometimes gets modifiers added: s (session leader), l (multi-threaded), + (in the terminal's foreground group), < (high priority), N (low priority).

$ ps aux
USER   PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     1  0.0  0.1 168240 11500 ?        Ss   09:00   0:02 /sbin/init
root   640  0.3  2.0 2310000 82000 ?       Ssl  09:00   0:40 /usr/bin/containerd
deploy 1455  0.0  0.0  12100  3200 pts/0    R+   10:14   0:00 ps aux

To avoid scrolling through the whole list, ask ps for just the columns you need:

$ ps -eo pid,ppid,stat,comm
  PID  PPID STAT COMMAND
    1     0 Ss   systemd
  812   811 S    nginx
 2043  1990 T    sleep

The second place is the file /proc/<PID>/status, where the state is spelled out as a word:

$ grep State /proc/812/status
State:	S (sleeping)

The same file has a PPid: line with the parent's PID. This is handy in minimal containers where ps isn't installed at all: /proc is always there.

macOS has its own ps. The letters R, S, T, Z mean the same thing there, but U (uninterruptible wait) and I (asleep for more than 20 seconds) are macOS's own. That's why this lesson's tasks run inside a Linux container.

State summary

CodeStateWhat happens with signals
Rrunning or readydelivered
Sinterruptible sleepwakes the process
Duninterruptible sleep (I/O)wait for the operation to finish
TstoppedSIGCONT wakes it, SIGKILL terminates it
Zzombieuseless, the process is already dead

Common mistakes

First: "a process is either running or dead." Between R and termination there's sleep (S, D) and a pause (T). A program in S is a healthy program, not a hung one.

Second: restarting a stopped process. If a service is in T, it hasn't broken anything, it was just paused. kill -CONT <PID> is enough. A restart would lose whatever the process held in memory and would change its PID.

Third: "a process in D will definitely go away with kill -9." Not necessarily. SIGKILL will wait until the I/O operation finishes:

$ kill -9 9021
$ ps aux | grep 9021
deploy 9021  0.0  0.0   8900  3100 ?        D    11:02   0:00 tar czf /mnt/nfs/backup.tgz /srv

Fourth: "I'll kill the zombie with kill -9." There's nothing to kill. Look at the parent: calling wait() is its job.

About signals, which will come up later

States are closely tied to signals, so let's pin down a few facts in advance.

SIGKILL (9) and SIGSTOP (19 on x86 and ARM) are the only two signals that can't be caught, blocked or ignored. A process can handle all the others its own way. SIGTSTP from Ctrl+Z can in fact be caught, which is why some programs don't stop on Ctrl+Z.

Every signal has a default action, five in total: Term (terminate), Ign (ignore), Core (terminate and dump core), Stop (stop), Cont (continue).

SIGCHLD stands apart: the kernel sends it to the parent when a child terminates or stops. By default SIGCHLD is ignored, so the parent is responsible for calling wait() itself to clear a zombie.

If the parent dies before the child, the child becomes orphaned. It gets adopted by init (PID 1), which calls wait() regularly and cleans up anything that has finished. Since kernel 3.4, this collector role can be handed to a specially designated subreaper process, not only PID 1.

What to remember

R means running or ready to run, S means a healthy sleep while waiting for an event.

D means waiting on I/O, kill -9 only takes effect once the operation finishes.

T means paused, lifted by SIGCONT without a restart. Z means finished, waiting on the parent's wait(), and no signal can kill it.

The state is visible in the STAT column (ps aux) and in /proc/<PID>/status.

SIGKILL and SIGSTOP can't be caught or blocked.

The next lesson covers signals in more detail: which ones can be caught, and what follows from that.

What you'll need for the tasks

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson linux-processes-02-states