The Process Model
A program on disk is just a file: inert bytes. A process is that program running — code loaded into memory, with its own slice of CPU time, its own open files, and an identity the kernel tracks. The gap between “a file exists” and “a process is running” is the gap the whole rest of DevOps operates in. Deploying is, at bottom, the act of turning the right file into the right process on the right machine, reliably. So we start here.
A process has an identity: the PID
Section titled “A process has an identity: the PID”When the kernel starts a process, it hands out a PID (process ID), a unique number. You use the PID to ask about a process or to signal it. Every process also records its parent’s PID (the PPID), because processes don’t appear from nowhere — they are spawned by other processes.
PID 1 (systemd) ── the ancestor of everything │ ├── sshd (PID 812) │ └── bash (PID 1340) ← your shell │ └── python app.py (PID 1502) ← you started this └── nginx (PID 905)That tree is real: run pstree -p and you’ll see it. PID 1 is special — it’s the first process the
kernel starts, and on a modern server that’s systemd (covered later).
It’s the root of the whole tree and the eventual adopter of any orphan.
How processes are born: fork then exec
Section titled “How processes are born: fork then exec”This is one of those ideas that looks strange until it clicks, and then a lot of things make sense. Unix creates a new process in two steps:
fork()— a process clones itself. You now have two near-identical processes (parent and child) that differ mainly in their PID.exec()— the child then replaces its own program with a different one, keeping the same PID.
bash ──fork()──► bash (child copy) ──exec("python")──► python app.py (parent stays) (same PID, now running python)Why split it in two? Because that gap between fork and exec is where the child can adjust its
environment — redirect output to a file, drop privileges, set variables — before the new program
starts. That single design choice is what makes shell redirection and sudo and container startup
possible. Hold onto it; it explains a lot in The Shell and
Containers.
How processes die: exit codes
Section titled “How processes die: exit codes”When a process finishes, it returns an exit code (a.k.a. status): an integer from 0 to 255. The convention is rigid and load-bearing:
0means success.- Any non-zero value means failure (and the number can hint which failure).
ls /etc; echo "exit code: $?" # 0 — the directory existsls /nope; echo "exit code: $?" # 2 — ls couldn't find it$? holds the exit code of the last command. This tiny number is the bedrock of automation: a CI
pipeline decides “did the tests pass?” by checking whether the test command exited 0. Scripts chain
on it (build && deploy only deploys if the build returned 0). Get exit codes right in your own
programs and the entire automation stack above you can trust them.
Talking to a running process: signals
Section titled “Talking to a running process: signals”You don’t usually share memory with a running process — you send it a signal, a small asynchronous nudge from the kernel. The two that matter most for operations:
| Signal | Number | Meaning | Can the process catch it? |
|---|---|---|---|
SIGTERM | 15 | ”Please shut down.” | Yes — clean up, then exit |
SIGKILL | 9 | ”Die now.” | No — kernel kills it instantly |
SIGINT | 2 | Ctrl-C in the terminal | Yes |
SIGHUP | 1 | Terminal closed / “reload config” | Yes |
The distinction between SIGTERM and SIGKILL is one of the most important operational facts in this
book. SIGTERM is polite: it lets the process finish in-flight requests, flush buffers, and close
database connections — a graceful shutdown. SIGKILL is the kill switch: instant, unstoppable, and
it gives the process no chance to clean up, which can mean dropped requests or corrupted state.
kill 1502 # sends SIGTERM (the default) to PID 1502 — politekill -TERM 1502 # the same thing, explicitkill -9 1502 # SIGKILL — last resortpkill -f app.py # signal by matching the command line, not the PIDForeground, background, and the logout problem
Section titled “Foreground, background, and the logout problem”Start a program in a terminal and it runs in the foreground: it owns your terminal until it exits.
Append & and it runs in the background, handing your prompt back.
python app.py # foreground — terminal is busypython app.py & # background — you get your prompt backjobs # list background jobs in this shellBut there’s a trap that bites every beginner: a backgrounded process is still tied to your shell
session. Close the terminal and it usually dies. Running production software with & and nohup
and crossing your fingers is precisely the manual, fragile habit this part exists to kill — it doesn’t
survive logout, doesn’t restart on crash, and doesn’t come back after a reboot. The real fix isn’t a
trick; it’s handing the process to a service manager, which is why
systemd & Services exists.
Watching processes: ps and top
Section titled “Watching processes: ps and top”Two tools you’ll use constantly. ps is a snapshot; top (or the friendlier htop) is a live
view.
ps aux # every process: user, PID, %CPU, %MEM, commandps aux | grep nginx # narrow to what you care aboutps -ef --forest # show the parent/child treetop # live, sorted by CPU; press 'M' to sort by memory, 'q' to quitIn top, the columns that earn their keep are %CPU, %MEM, and RES (resident memory — actual RAM
used). A process pinned at 100% CPU or steadily climbing memory is your first clue in an incident;
we’ll build a full triage routine in Logs & Troubleshooting.
Why this is the foundation
Section titled “Why this is the foundation”Notice what just happened: “deploy my app” decomposes into start a process (exec), supervise it (PID + signals), shut it down gracefully (SIGTERM), and know whether it succeeded (exit code). Every higher-level tool — systemd, Docker, Kubernetes — is an increasingly automated way of doing exactly these four things so a human doesn’t have to do them by hand on a live server. The manual version is slow and forgettable; the automated version is the rest of this book.
The architect’s lens
Section titled “The architect’s lens”The process model isn’t a tool you adopt — it’s the substrate — but the same five questions reveal why it’s shaped the way it is:
- Why does it exist? Because a program on disk is inert bytes; the kernel needs a way to turn a file into a running thing with an identity (a PID), a parent (PPID), CPU time, and open files — and “deploy” is precisely the act of turning the right file into the right process, reliably.
- What problem does it solve? It gives operations one universal vocabulary — start (
exec), supervise (PID + signals), stop gracefully (SIGTERM), and know whether it worked (exit code0) — that every higher tool, systemd through Kubernetes, simply automates. - What are the trade-offs? The model is deliberately minimal: you don’t share memory with a process,
you nudge it with asynchronous signals, and
SIGKILL(9) cannot be caught — so a reflexivekill -9costs in-flight requests, unflushed buffers, and risks corrupt state. - When should I work around it? Running real software with
&/nohupis the trap — it dies on logout, won’t restart on crash, and won’t survive a reboot; hand it to a service manager (systemd & Services) instead. - What breaks if I get it wrong? Exit-code discipline (0 vs non-zero) is load-bearing for
&&chains and CI gates, and cleanSIGTERMhandling is what makes a rolling deploy lossless — botch either and pipelines can’t tell pass from fail and restarts drop requests.
Check your understanding
Section titled “Check your understanding”- What is the difference between a program and a process, and why does that distinction matter when you “deploy”?
- Explain
fork/execin your own words. Why is splitting process creation into two steps useful rather than wasteful? - Your script runs
run-tests.sh && deploy.sh. What exit code mustrun-tests.shreturn fordeploy.shto run, and why is that convention safe? - A teammate kills a stuck app with
kill -9. What did the app lose the chance to do, and what should they have tried first? - Why does
python app.py &fail as a way to run software in production, and which later page solves it properly?
Show answers
- A program is inert bytes on disk (a file); a process is that program running — code in memory with CPU time, open files, and a kernel-tracked identity (a PID). Deploying is exactly the act of turning the right file into the right process on the right machine, reliably — so the gap between file and process is the gap deployment lives in.
fork()clones the current process (two near-identical copies differing mainly in PID);exec()then replaces the child’s program with a new one, keeping the same PID. Splitting it is useful because the gap between fork and exec is where the child can adjust its environment — redirect output, drop privileges, set variables — before the new program starts. That single design makes shell redirection,sudo, and container startup possible.run-tests.shmust return0(success) fordeploy.shto run, because&&runs the right side only if the left exited0. It’s safe because the convention is rigid — 0 = success, non-zero = failure — so the chain deploys only when tests genuinely passed.kill -9sendsSIGKILL, which the kernel applies instantly and the process cannot catch — so it loses the chance to gracefully shut down: finish in-flight requests, flush buffers, close DB connections. They should have sent the defaultSIGTERMfirst, given it a few seconds, and only escalated toSIGKILLif ignored — exactly how Kubernetes behaves.- A backgrounded process is still tied to your shell session, so it usually dies on logout, doesn’t restart on crash, and doesn’t come back after a reboot. The proper fix is handing it to a service manager — systemd & Services.