Processes
A process is a running program — a pid, an address space, open file descriptors, a parent, a state. Every process on Linux descends from PID 1 (init/systemd) through fork() — the whole system is one family tree. This chapter teaches you to see it: ps, top, signals, job control, and the /proc filesystem where the kernel exposes every process's innards as readable files.
The lifecycle — fork, exec, wait, exit
pid = fork(); // clone — parent and child both continue
if (pid == 0) // child branch
execvp("prog", args); // replace this process's image
else
wait(&status); // parent collects the child's exit code
fork() duplicates; exec() replaces the copy with the new program; wait() reaps. Every process is born a clone of its parent, then re-skinned as its real program. The exit code (0–255) the child leaves is the contract the parent collects — $? in the shell.
When a child dies before its parent waits, it becomes a zombie — Z in ps, dead but unreaped, holding only its exit status. An orphan whose parent dies is re-parented to PID 1.
Seeing them — ps and friends
ps aux # the classic full listing
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ps -ef # SysV style — shows PPID
ps -ejH # the family tree
ps --forest -e # visual tree
pgrep -l sshd # pids by name
pidof bash # single name → pids
top # the live view — sort by M(em), P(cpu), T(ime)
htop # the better live view
pstree -p # the tree with pids
STAT codes — R running, S sleeping, D uninterruptible (usually I/O), T stopped, Z zombie, s session leader, + foreground, l multithreaded. D-state processes can't be killed — they're mid-syscall in the kernel.
Signals — the messaging system
kill PID # SIGTERM (15) — please exit politely
kill -9 PID # SIGKILL — the kernel kills it NOW (no cleanup)
kill -HUP PID # SIGHUP — hangup → reload config for daemons
kill -INT PID # SIGINT (2) — Ctrl-C
kill -TSTP PID # SIGTSTP — Ctrl-Z
kill -CONT PID # resume a stopped one
kill -USR1 PID # user-defined — daemons use as triggers
kill -l # all 31 signals
killall name # by name
pkill -f pattern # by cmdline pattern
The two uncatchables: SIGKILL and SIGSTOP — the kernel's guarantees that nothing can refuse death or suspension. Everything else a process may trap/handle/ignore.
Job control — the shell's own processes
cmd & # background
jobs # the job table — %1 %2 ...
fg %1 # bring to foreground
bg %1 # resume suspended in background
Ctrl-Z; bg # the suspend-and-background combo
disown %1 # remove from job table — survives shell exit
nohup cmd & # immune to SIGHUP — survives logout
Ctrl-C = SIGINT to the foreground group; Ctrl-Z = SIGTSTP; Ctrl-\ = SIGQUIT (with core dump). jobs -l shows pids.
Sessions and process groups
A session groups process groups under a controlling terminal — setsid starts a new one (how daemons detach from terminals). The login shell leads a session; pkill -u alice kills all her processes. Daemonization = fork, setsid, detach tty, chdir /, close fds, redirect stdio to /dev/null — the classic dance systemd now does for services.
/proc — the process's innards
ls /proc/$$ # YOUR shell's process, as files
cat /proc/$$/status # state, memory, uids
cat /proc/$$/cmdline | tr '\0' ' ' # its argv
ls -l /proc/$$/fd # its open files — the fds!
cat /proc/$$/environ | tr '\0' '\n' # its environment
cat /proc/loadavg # 1/5/15-min load + running/total
cat /proc/meminfo # the memory ledger
cat /proc/cpuinfo # the CPUs
/proc/PID/fd/ alone is worth the chapter — every open file, socket, and pipe as a symlink. lsof and ls -l /proc/*/fd show who holds what.
Priorities — nice and the scheduler
nice -n 10 cmd # run it nicer (lower priority, -20..19)
renice +5 -p PID # change a running one
ps -o pid,ni,comm # see nice values
Higher nice = lower priority — "nicer" to others. chrt, ionice, taskset reach the deeper schedulers (RT classes, I/O priority, CPU pinning).
Resource limits
ulimit -a # this shell's limits — fds, core size, procs, memory
ulimit -n 4096 # raise fd ceiling
cat /proc/PID/limits # a running process's limits
timeout 30 cmd # kill after 30s — the blunt instrument
systemd's LimitNPROC/LimitNOFILE — the service-level equivalents
ulimit -n (max open files) is the one you'll actually hit — busy servers bump it constantly.
Monitoring beyond top
vmstat 1 # memory + IO + CPU, per second
iostat -x 1 # disk I/O
mpstat -P ALL 1 # per-CPU
dstat # the everything-monitor
free -h # memory now
watch -n1 'ps aux --sort=-%cpu | head' # your own live monitor
Load average (uptime) = runnable processes averaged over 1/5/15 min — 4.00 on 4 CPUs = saturated; on 8 CPUs = half-loaded. Interpret relative to core count.
Where process work goes wrong
| Symptom | Cause |
|---|---|
kill didn't kill it | Process ignored SIGTERM — escalate to -9 |
kill -9 didn't kill it | D state — mid-syscall, often dead NFS/disk I/O |
| Zombie piling up | Parent not wait()ing — a parent bug; kill the parent |
| Daemon dies at logout | Needed nohup/setsid/disown — SIGHUP |
Ctrl-C doesn't work | Process traps or ignores SIGINT |
| Can't find the process | ps -ef | grep name; pgrep -f for cmdline |
| "Too many open files" | ulimit -n — the fd ceiling |
Exercises
sleep 300 &— job control: jobs, fg, Ctrl-Z, bg, kill.ls -l /proc/$$/fd— inspect your shell's own open files.bash -c 'sleep 99'thenps -ejH— find the fork relationship.kill -TSTPa process then-CONT— freeze and thaw it.watch -n1 cat /proc/loadavg— watch load breathe.ulimit -n— your fd ceiling; then raise it and note where it matters.
Timeline
| Concept | Mechanism |
|---|---|
| Birth | fork() → exec() |
| Death | exit() → zombie → wait() |
| PID 1 | The ancestor of all — systemd/SysVinit |
| Signals | Inter-process interrupts |
| /proc | The kernel's process window |