Processes & Signals
What runs, how it's controlled, and how to stop it.
Processes & Signals
Every program you run is a process — a running instance of a program with its own memory space, file descriptors, and state. The kernel tracks processes by PID (process ID) and gives you tools to inspect and control them.
Analogy
Processes are like kitchen staff, each wearing a numbered badge (the PID). Signals are the manager tapping someone on the shoulder with different messages. A SIGTERM tap means "please finish the plate you're on, wash your station, and clock out" — the cook gets to close out cleanly. A SIGKILL is the manager pulling the cook's badge off mid-chop and shoving them out the back door — whatever was on the cutting board is lost, raw, unplated. SIGSTOP is "freeze, stand exactly where you are" and SIGCONT is "okay, carry on". The manager always tries a tap first; yanking the badge is the move you regret when the half-finished sauce spills.
Foreground and background
When you run a command, it normally runs in the foreground: it owns the terminal, you can't type anything else, and Ctrl-C stops it.
Add & to push it to the background:
sleep 60 &
[1] 12345 # job number, PID
The shell prints a job number and PID. The process keeps running while you use the terminal.
Job control
| Command | What it does |
|---|---|
jobs |
List background and stopped jobs in this shell session |
fg |
Bring the most recent job to the foreground |
fg %2 |
Bring job 2 to the foreground |
bg |
Resume a stopped job in the background |
Ctrl-Z |
Suspend (stop) the foreground process |
disown %1 |
Detach job 1 so SIGHUP doesn't kill it when you close the terminal |
nohup cmd & |
Run cmd immune to SIGHUP, output to nohup.out |
Signals
A signal is an asynchronous notification sent to a process. The process either handles it, ignores it, or is terminated by it.
| Signal | Number | Default action | Notes |
|---|---|---|---|
| SIGHUP | 1 | Terminate | Terminal closed; daemons use it as "reload config" |
| SIGINT | 2 | Terminate | Ctrl-C — polite interrupt |
| SIGTERM | 15 | Terminate | Default kill — process can catch and clean up |
| SIGKILL | 9 | Terminate (forced) | Cannot be caught or ignored — nuclear option |
| SIGSTOP | 19 | Stop | Pause; cannot be caught |
| SIGCONT | 18 | Continue | Resume a stopped process |
Why SIGKILL is the nuclear option
SIGTERM gives the process a chance to flush buffers, close connections, and write checkpoints. SIGKILL doesn't. The kernel terminates the process immediately. Data in flight is lost.
The right order is always: try SIGTERM → wait a moment → only then use SIGKILL.
kill 12345 # sends SIGTERM (15)
sleep 5
kill -9 12345 # SIGKILL — if it still hasn't stopped
Inspecting processes
ps aux # all processes, BSD style
ps -ef # all processes, System V style
top # live view (press q to quit)
htop # nicer live view (if installed)
lsof -p 12345 # all files open by PID 12345
lsof -i :3000 # which process is listening on port 3000
The signal state machine
A process moves through states as signals arrive:
running --SIGSTOP--> stopped
stopped --SIGCONT--> running
running --SIGTERM--> terminated
running --SIGKILL--> terminated (no chance to clean up)
A caught SIGTERM lets the process run a handler first; SIGKILL skips the handler entirely.
Writing signal handlers
Never do significant work inside a signal handler. Signal handlers run asynchronously and many library functions are not async-signal-safe. The pattern: set a flag in the handler, check the flag in your main loop.
#!/bin/sh
cleanup() { echo "Caught SIGTERM, cleaning up"; exit 0; }
trap cleanup TERM INT
while true; do sleep 1; done