LinuxVI · ProcessesProcess lifecycle
Process lifecycle — fork, exec, exit, and wait
What you'll learn
- Trace the lifecycle of a process from fork to wait
- Distinguish fork() from exec() and clone()
- Explain what happens when a process receives SIGTERM, SIGKILL, or exits normally
- Diagnose stuck processes using the lifecycle state
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
The process lifecycle is the foundation of every program that runs. Every daemon, every shell pipeline, every container process follows the same pattern: fork, exec, run, exit, wait.
The four phases
sequenceDiagram
participant P as Parent
participant K as Kernel
participant C as Child
P->>K: fork()
K-->>P: return child PID (in parent)
K-->>C: return 0 (in child)
Note over C: New process is a copy<br/>(or clone with shared state)
C->>K: execve(new-program)
K->>K: replace memory<br/>with new program
Note over C: Same PID, new code
C->>K: _exit(status)
K-->>P: SIGCHLD
P->>K: wait(&status)
K-->>P: child\'s exit status
1. fork() — create a copy
fork() (or clone() for threads and namespaces) creates a new
process that is a near-copy of the parent. The two processes share
most state at the moment of fork; subsequent writes diverge.
$ strace -e trace=fork,clone,execve ls /tmp 2>&1 | head -5execve(/bin/ls, [ls, /tmp], 0x7ffd...) = 0
clone(...) = 1234
wait4(-1, ...) = 1234
...Illustrative output
2. execve() — replace the program
The new process is initially a copy of the parent. execve(path, argv, envp) replaces the process’s memory with a new program. The
PID stays the same; only the program and memory change.
$ cat /proc/self/exe/usr/bin/catIllustrative output
3. Exit — terminate normally
A process exits by calling exit(status) (C library) or _exit(status)
(kernel). The kernel:
- Closes file descriptors (releasing locks and resources).
- Sends SIGCHLD to the parent.
- Releases most process state, but keeps the PID, exit status, and resource usage for the parent’s
wait(). - The process is now a zombie (state Z).
4. Wait — parent reaps the child
The parent calls wait() or waitpid(). The kernel releases the
remaining zombie state and returns the exit status to the parent.
If the parent never calls wait(), the zombie persists until
the parent itself exits (at which point PID 1 reaps the orphan).
What happens on signals
Processes rarely exit cleanly on their own. Most production exits are triggered by a signal.
| Signal | Default effect | Useful for |
|---|---|---|
| SIGTERM (15) | Terminate after cleanup | Graceful shutdown — services receive this from systemctl stop |
| SIGINT (2) | Terminate | Ctrl-C in a terminal |
| SIGKILL (9) | Terminate immediately | Unresponsive processes; SIGTERM timeout |
| SIGHUP (1) | Terminate (or reload for daemons) | “Hang up” — many daemons reload config |
| SIGQUIT (3) | Core dump | Debugging |
| SIGSTOP (19) | Suspend | Job control |
| SIGCONT (18) | Resume | After SIGSTOP |
| SIGCHLD (17) | Notify parent | Child exited |
$ systemctl stop myapp.serviceIllustrative output
For a process that no unit owns, escalate by hand — but never with
a bare sleep between the two signals:
pid=1234
kill -TERM "$pid" || exit 0 # already gone: nothing to do
for _ in $(seq 30); do
kill -0 "$pid" 2>/dev/null || { echo "exited cleanly"; exit 0; }
sleep 1
done
# Re-check immediately before escalating, not 30 seconds ago.
kill -0 "$pid" 2>/dev/null && kill -KILL "$pid"
Production shutdown order
A well-designed systemd dependency graph ensures shutdown order reverses start order:
A Requires=B → no ordering at all; B is pulled in, but both may
start and stop simultaneously
A After=B → A starts AFTER B, and A stops BEFORE B
A Before=B → A starts BEFORE B, and A stops AFTER B
Two directives, two separate jobs. Requires= answers “must B be
running for A to make sense?”. After=/Before= answer “in which
order?”. Neither implies the other, and a unit that declares only
Requires= gets no ordering at all.
Shutdown is derived, not declared. systemd inverts the start order
to produce the stop order, so After=B means A is torn down
before B. You never write a separate shutdown dependency.
A web application with Requires=postgresql.service plus
After=postgresql.service therefore starts after postgres and is
stopped before it — which is exactly what you want, because the
application must drain its in-flight transactions before the
database goes away. The same logic chains upward: a load balancer
with After= on the web application is stopped first, so it drains
traffic before the application it fronts disappears.
Knowledge check
Knowledge check · 4 questions
Q1. After a child process exits, what reaps its zombie state?
Q2. After a successful execve(), the process keeps the same PID and the same open file descriptors.
Q3. Which of the following are correct signals-related practices? Select all that apply.
Q4. myapp.service has Requires=postgresql.service and After=postgresql.service. During a host reboot, which is torn down first, and why does it matter?
Passing score: 75%. Answers are checked in this browser.