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
| Code | State | What happens with signals |
|---|---|---|
R | running or ready | delivered |
S | interruptible sleep | wakes the process |
D | uninterruptible sleep (I/O) | wait for the operation to finish |
T | stopped | SIGCONT wakes it, SIGKILL terminates it |
Z | zombie | useless, 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
- Docker 20.10+ and bash. The tasks run in an
ubuntu:24.04container, wherepscomes from procps, just like on a server. docker exec <container> ps -eo pid,ppid,stat,comm,argsandcat /proc/<PID>/status: read the state and the parent.docker exec <container> kill -<SIGNAL> <PID>: send a signal to a process inside the container.- Lab 1: use the states to find which process is spinning the CPU, which one is paused, and which one became a zombie. Lab 2: find the queue worker that got stuck and bring it back to work without restarting it.
Interview-style questions
Interview questions on this topic: 1
Included with practice access, $49 one-time. Get access
Answer out loud or in writing, then reveal the answer and honestly mark whether you knew it.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
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.
Tell the agent: use MCP izi, give me lesson linux-processes-02-states
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress