LinuxVI · ProcessesProcess model
The Linux process model — PIDs, PPIDs, sessions, and groups
What you'll learn
- Explain PID, PPID, PGID, SID, and TID and the relationships between them
- Use ps to read process state and ancestry
- Distinguish a process, a thread, and a cgroup view
- Identify zombie and orphan processes and what they mean
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
Every running program on Linux is a process, an instance of an executable under execution. The kernel tracks each process with a set of identifiers; understanding them is the vocabulary for every later troubleshooting lesson.
The identifiers
| Identifier | Meaning |
|---|---|
| PID | Process ID — unique on the host, allocated sequentially (with reuse) by the kernel |
| PPID | Parent PID — the PID of the process that created this one |
| PGID | Process Group ID — used for job control and signal delivery |
| SID | Session ID — the leader of a session; usually the login shell or service manager |
| TID | Thread ID — within a process, the kernel also tracks each thread |
| TGID | Thread Group ID — same value as the leader thread’s TID, used to find all threads in a process |
Every schedulable entity the kernel knows about has a TID. Threads that belong to the same process all share one TGID, and that TGID is the TID of the group’s leader thread — it is also the number userspace calls “the PID”. So the rule reads in one direction only: same TGID with different TIDs means threads of one process; different TGIDs means different processes.
From the shell’s perspective, ps -eLf prints one row per TID
(every thread); ps -ef prints one row per TGID (leader threads
only). Per-thread state lives under /proc/<TGID>/task/<TID>/.
This matters when you signal something: kill acts on the whole
thread group, not on the individual thread whose TID you read out
of a stack trace.
$ ps -eo pid,ppid,pgid,sid,comm | head PID PPID PGID SID COMM
1 0 1 1 systemd
124 1 124 124 systemd-journal
135 1 135 135 systemd-udevd
234 1 234 234 sshd
12345 234 12345 12345 bash
12346 12345 12346 12345 psIllustrative output
Process states
The kernel tracks a per-process state in /proc/<pid>/status and
in ps output:
| State | Meaning |
|---|---|
R | Running (on a CPU or in the run queue) |
S | Sleeping (interruptible — waiting for an event) |
D | Disk sleep (uninterruptible — typically waiting for I/O) |
Z | Zombie (terminated but not reaped by parent) |
T | Stopped (suspended by signal or debugger) |
I | Idle (kernel thread, waiting for work) |
X | Dead (very transient) |
$ ps -eo pid,stat,comm | grep -E '\b(D|Z)\b' | head PID STAT COMM
5678 D kworker/1:2
Illustrative output
Zombie processes
A zombie is a process that has finished execution but whose parent
has not yet reaped it. The kernel keeps the PID and a minimal
record so the parent can read the exit status. Once the parent
calls wait() (or waitpid()), the zombie is released.
$ ps -eo pid,ppid,stat,comm | grep 'Z' | head PID PPID STAT COMM
12345 1 Z myapp <defunct>Illustrative output
A few zombies are normal. Hundreds are a sign that something is not
calling wait(). Zombies do not consume memory beyond a small
kernel struct; they do consume a PID. The PID space is finite:
kernel.pid_max defaults to 32768 on older kernels but is
commonly 4194304 on modern 64-bit systems, so read
cat /proc/sys/kernel/pid_max on the host in front of you rather
than assuming a number. If a process leaks zombies faster than
they are reaped, eventually fork() returns EAGAIN and new
processes cannot start.
Orphan processes
A process whose parent exits before the child is orphaned. The
kernel reparents it immediately — synchronously, inside the
exiting parent’s own teardown — so there is no window in which
ps shows a stale PPID.
The new parent is not always PID 1. The kernel walks up the
ancestry looking for the nearest process marked as a child
subreaper (prctl(PR_SET_CHILD_SUBREAPER)), and only falls back
to PID 1 if there is none. On a systemd host that ancestor is
frequently something else: systemd --user is a subreaper for
every user session, and container runtimes and application
supervisors set the flag too.
That makes an orphan with PPID != 1 a signal rather than noise.
It tells you which supervisor adopted the process and is therefore
the one responsible for reaping it — which is where you go looking
when the zombies start piling up.
$ ps -eo pid,ppid,comm | head -10 PID PPID COMM
1 0 systemd
567 1 cron
12345 12344 myappIllustrative output
Process ancestry with pstree
$ pstree -p | head -10systemd(1)-+-accounts-daemon(567)-+-{accounts-daemon}(568)
| `--{accounts-daemon}(569)
|-chronyd(890)
|-cron(910)
|-dbus-daemon(945)-+-dbus-daemon}(946)
| `-{dbus-daemon}(947)
|-irqbalance(1056)-+-{irqbalance}(1057)
| `-{irqbalance}(1058)
|-networkd-dispat(1102)-+-{networkd-dispat}(1103)
| `-{networkd-dispat}(1104)
|-nginx(12345)-+-nginx(12346)
| |-nginx(12347)
| `-nginx(12348)
|-rsyslogd(1500)-+-{rsyslogd}(1501)
| `--{rsyslogd}(1502)
|-sshd(1700)---sshd(2345)---bash(12340)---pstree(12341)
Illustrative output
Knowledge check
Knowledge check · 3 questions
Q1. What does state D (uninterruptible sleep) mean for a process?
Q2. A zombie process can be killed with SIGKILL.
Q3. Which of the following are true about process ancestry on Linux? Select all that apply.
Passing score: 75%. Answers are checked in this browser.