Skip to content
ALB Wiki

Processes

This is an old revision of this page — revision 2 (2026-10-25 14:55:26 UTC). It may differ from the current revision.

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.

Signals — the complete taxonomy

kill -l                    # all 31
SIGHUP  (1)   # hangup — terminal closed → daemons use as "reload config"
SIGINT  (2)   # Ctrl-C — interrupt
SIGQUIT (3)   # Ctrl-\ — quit + core dump
SIGILL  (4)   # illegal instruction
SIGTRAP (5)   # debugger breakpoint
SIGABRT (6)   # abort() — the core dump
SIGFPE  (8)   # arithmetic fault — div by zero
SIGKILL (9)   # UNCATCHABLE — kernel kills it now
SIGSEGV (11)  # segfault — bad memory access
SIGPIPE (13)  # wrote to a closed pipe — the "broken pipe"
SIGALRM (14)  # alarm() timer
SIGTERM (15)  # polite terminate — the default kill
SIGCHLD (17)  # a child died — parent reaps via wait()
SIGCONT (18)  # resume a stopped process
SIGSTOP (19)  # UNCATCHABLE suspend
SIGTSTP (20)  # Ctrl-Z — terminal stop (catchable)
SIGUSR1/2 (10/12)  # user-defined — daemons' private triggers

The semantics: SIGTERM asks (catchable — a process can trap it and clean up); SIGKILL executes (uncatchable — no cleanup possible); SIGHUP is overloaded as "reload your config" for daemons; SIGSTOP/SIGCONT are the debugger's and job control's pause/resume. trap catches the catchables — trap 'cleanup' TERM INT is the daemon-shutdown pattern.

Sessions, groups, and the controlling terminal

ps -o pid,ppid,pgid,sid,tty,cmd -e | head   # the whole hierarchy
setsid cmd                 # new session — detach from the terminal
disown %1                  # drop from job table — survive exit
nohup cmd &                # SIGHUP-immune

A session = a login or detached context; a process group = a pipeline's jobs — kill -PID targets a pid, kill -- -PGID a group. The controlling terminal (TTY in ps) is what Ctrl-C signals hit — daemons shed it via setsid.

cgroups + namespaces — the container primitives

ls /sys/fs/cgroup/           # the cgroup hierarchy — the real container layer
cat /proc/PID/cgroup         # a process's cgroup
systemd-cgls                 # systemd's cgroup tree
unshare -u -n -p bash        # a bash in fresh namespaces — a proto-container
lsns                         # list namespaces

Namespaces (mnt, pid, net, ipc, uts, user) isolate; cgroups meter (cpu, memory, io limits). Containers are these two primitives arranged — docker run = namespaces + cgroups + an image, nothing more.

The scheduler's deeper knobs

nice -n 10 cmd               # -20..19 — higher = LOWER priority
renice -n 5 -p PID           # live-tune
chrt -f 50 cmd               # SCHED_FIFO realtime
chrt -r 50 cmd               # SCHED_RR
ionice -c3 cmd               # io priority — idle-class
taskset -c 0,1 cmd           # CPU pinning

Realtime classes beat nice — SCHED_FIFO/RR preempt everything else; ionice and taskset shape I/O and CPU affinity.

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

SymptomCause
kill didn't kill itProcess ignored SIGTERM — escalate to -9
kill -9 didn't kill itD state — mid-syscall, often dead NFS/disk I/O
Zombie piling upParent not wait()ing — a parent bug; kill the parent
Daemon dies at logoutNeeded nohup/setsid/disown — SIGHUP
Ctrl-C doesn't workProcess traps or ignores SIGINT
Can't find the processps -ef | grep name; pgrep -f for cmdline
"Too many open files"ulimit -n — the fd ceiling

Exercises

  1. sleep 300 & — job control: jobs, fg, Ctrl-Z, bg, kill.
  2. ls -l /proc/$$/fd — inspect your shell's own open files.
  3. bash -c 'sleep 99' then ps -ejH — find the fork relationship.
  4. kill -TSTP a process then -CONT — freeze and thaw it.
  5. watch -n1 cat /proc/loadavg — watch load breathe.
  6. ulimit -n — your fd ceiling; then raise it and note where it matters.

Timeline

ConceptMechanism
Birthfork() → exec()
Deathexit() → zombie → wait()
PID 1The ancestor of all — systemd/SysVinit
SignalsInter-process interrupts
/procThe kernel's process window

See also

Categories: Handbook · Linux · Linux Handbook
This page was last edited 2026-10-25 14:55:26 UTC by ALB Team. What links here