Skip to content

Process Monitoring & Control

Every running program is a process, and diagnosing "the box is slow" or "my job is stuck" almost always starts with looking at what's actually running, how much of the machine it's using, and whether it will respond to a request to stop. This page covers inspecting processes and controlling them — including the difference between asking a process to stop and forcing it to.

TL;DR — in 30 seconds:

  • ps aux is a snapshot of every process right now; top is the same information, live and continuously refreshing.
  • kill doesn't "kill" by default — it sends a signal; SIGTERM (the default) asks a process to shut down cleanly, SIGKILL (-9) terminates it immediately with no cleanup.
  • A command ending in & runs in the background; jobs lists your shell's background jobs, fg/bg move one to the foreground/background.

1. Inspecting processes

  • ps aux — a snapshot of every process on the system: a (all users' processes), u (user-oriented format: owner, CPU%, memory%), x (include processes with no controlling terminal). Key columns: PID (process ID, the number every other command below needs), %CPU, %MEM, STAT (process state), CMD (the command that launched it).
  • top (or htop where installed) — the same kind of information as ps aux, but live: it refreshes continuously and sorts by resource usage by default, so it's the tool to reach for when you need to watch what's happening right now, not just a one-time snapshot.
  • $? — after any command finishes, this shell variable holds its exit code: 0 means success, any non-zero value means failure (the specific non-zero value is program-defined and often documented as meaning something specific, e.g. "config error" vs "network error").
stateDiagram-v2
    [*] --> Running
    Running --> Sleeping: waiting on I/O
    Sleeping --> Running: I/O ready
    Running --> Stopped: SIGSTOP / Ctrl+Z
    Stopped --> Running: SIGCONT / fg
    Running --> Zombie: process exits,<br/>parent hasn't reaped it
    Zombie --> [*]
    Running --> [*]: SIGTERM/SIGKILL handled,<br/>process exits

2. Foreground, background, and jobs

Every command you run occupies your terminal (foreground) until it finishes — unless you tell the shell otherwise:

Action Command
Start a command in the background append & (e.g. long_task.sh &)
Suspend the current foreground job Ctrl+Z (sends SIGSTOP)
List this shell's background/suspended jobs jobs
Bring a job back to the foreground fg %<job-number> (or just fg for the most recent)
Resume a suspended job in the background bg %<job-number>
Keep a background job running after you log out nohup command &

A background job started with plain & is still tied to your terminal session — if the session ends, the job normally gets a SIGHUP too. nohup explicitly detaches it from that signal so it keeps running.

3. Signals and kill

kill is misleadingly named: it doesn't inherently terminate anything — it sends a signal to a process, and what happens next depends on which signal and how (or whether) the process handles it.

Signal Number Meaning
SIGTERM 15 Default for kill — politely ask the process to terminate; a well-behaved program catches this and cleans up (closes files, flushes buffers) before exiting.
SIGKILL 9 Immediate, unconditional termination — the process cannot catch, block, or clean up on this one; the kernel just ends it.
SIGHUP 1 Originally "the terminal hung up"; many long-running services (daemons) treat it as "reload your config" instead.
SIGINT 2 What Ctrl+C sends — interrupt the foreground process.
SIGSTOP Suspend the process (like Ctrl+Z); cannot be caught or ignored.
  • kill <PID> — sends SIGTERM (the default; give it a chance to clean up first).
  • kill -9 <PID> (or kill -SIGKILL <PID>) — sends SIGKILL; use only once SIGTERM has failed to stop it, since it skips any cleanup the process would otherwise have done.
  • kill -l — list every signal name and number.

Common gotchas

  • Reaching for kill -9 first. Fix: try plain kill (SIGTERM) first and give the process a moment to exit cleanly — SIGKILL skips cleanup entirely (open files, temp state, in-flight writes), which is fine for a truly stuck process but unnecessarily destructive for one that would have exited fine on its own.
  • Confusing a process's exit code with its output. Fix: $? only tells you success/failure as a number — it says nothing about what happened; read the process's actual stdout/stderr (or logs) to diagnose why it failed.
  • Assuming & alone survives you logging out. Fix: a plain background job is still attached to your terminal session and typically receives SIGHUP when it ends; use nohup (or a proper service manager) for anything that must outlive your session.
  • Not knowing the difference between "high CPU" and "stuck." Fix: top's STAT column tells you the actual state — R (running, genuinely using CPU) versus D (uninterruptible sleep, usually waiting on disk/network I/O) look identical in "the process is using resources" terms but need completely different fixes.
Check yourself — why doesn't kill -9 let a process save its state or close open files before exiting?

SIGKILL is handled directly by the kernel, not delivered to the process for it to catch or respond to — the process has no opportunity to run any cleanup code at all. It is immediate and unconditional, which is exactly why it's a last resort, not a first move.

Check yourself — you start backup.sh &, then close your terminal. What likely happens to it, and how would you have prevented that?

It likely receives SIGHUP when the session ends and stops, because a plain background job is still attached to the terminal session. Running it as nohup backup.sh & instead would have detached it from that signal so it kept running after logout.

You can defend this when you can read a ps aux/top snapshot to find a specific process's PID and resource usage, explain the difference between SIGTERM and SIGKILL and why you'd choose one over the other, and move a job between foreground and background without losing it.