Signal9Signal9 — home
online
all lessons

Processes & Job Control: Running, Backgrounding & Signaling

Beginner30 min5 steps
What you'll learn
  • Explain what a process is — a running program with a PID, a parent, and its own memory — and list them with ps
  • Run a command in the background with &, list shell jobs with jobs, and move work between foreground and background with fg, bg, and Ctrl-Z
  • Send signals to control a process: Ctrl-C (SIGINT), kill (SIGTERM), and kill -9 (SIGKILL), and target by PID or %job
  • Connect a process's fate to its exit status: 130 from Ctrl-C, 137 from a forced kill
  1. A process is a program that's actually running

    See that every command you run becomes a process with an identity the system tracks.

    A program is a file on disk (/usr/bin/sleep). A process is what exists while that program is running — the live thing, with its own slice of memory, its own open files, and an identity the kernel tracks. Every command you type becomes a process, if only for an instant.

    Each process has:

    • a PID (process ID) — a unique number naming it while it runs,
    • a parent (the PPID) — the process that started it; when you launch a command, your shell is the parent,
    • and its own memory and file descriptors (that's why one crashing program doesn't take the others down).

    The tool for seeing processes is `ps`. Job control — running things in the background, suspending them, signaling them — is just you managing these processes from your shell instead of watching one run to completion.

    ps -o pid,ppid,stat,cmd --no-headers $$
       4123    4108 Ss   -bash
    -o pid,ppid,stat,cmd
    choose exactly which columns to show
    $$
    the shell's own PID (a handy self-reference)
    $$ is your shell's own PID, so this asks ps to describe the shell you're typing in: its PID (4123), its parent (4108, the terminal/login process that started it), a state code, and the command. Every command you run will have THIS shell as its parent.
  2. Foreground, background, and the & operator

    Run work in the background and list your shell's jobs.

    By default a command runs in the foreground: the shell starts it and waits, handing it your keyboard until it exits. Add `&` at the end and the command runs in the background instead — the shell starts it and immediately gives you the prompt back, so you can keep working.

    Background processes started from a shell are its jobs, numbered %1, %2, … The `jobs` builtin lists them with their state (Running / Stopped). This job number is a convenient handle — you can fg %1, kill %1, and so on without looking up a PID.

    When you do need the PID (to write it to a file, or to stop the job from a script), the shell has already saved it: right after command &, the special variable `$!` holds the PID of the job you just backgrounded, so echo $! prints it.

    sleep 300 &
    [1] 4210
    sleep 300 would normally freeze your terminal for five minutes. With & it runs in the background: the shell prints [1] (job number 1) and 4210 (its PID) and hands the prompt straight back. The work continues; you're free.
    jobs -l
    [1]+  4210 Running                 sleep 300 &
    -l
    also show each job's PID
    jobs lists this shell's jobs; -l adds the PID. [1] is the job number, + marks the 'current' job (the default target of fg/bg), Running is its state. This is your shell's private view of the work it launched — another shell won't see these jobs.

    Check yourself

    You append & to a command. What does the shell do differently?

  3. Suspend and resume: Ctrl-Z, bg, fg

    Move a running job between the foreground and background at will.

    What if a command is already running in the foreground and you wish you'd backgrounded it? Press `Ctrl-Z`. That suspends (pauses) the foreground job — it stops running and the prompt returns. From there:

    • `bg` resumes the suspended job in the background (it keeps running, you keep the prompt),
    • `fg` brings a job back to the foreground (you wait on it again).

    Both take an optional job spec: fg %2, bg %1. With no argument they act on the current job (the one marked + in jobs). The cycle Ctrl-Z → bg is the everyday move for "oops, this is taking a while — let me keep working."

    fg %1
    sleep 300
    Bring job 1 back to the foreground. The shell reprints the command and now waits on it again — your terminal belongs to sleep until it finishes or you suspend it. (Press Ctrl-Z now and you'd suspend it; type bg and it resumes running in the background.)
  4. Signals: asking a process to stop (nicely, then not)

    Use signals to interrupt, terminate, and force-kill processes, and read the resulting exit status.

    You don't reach into a process and stop it directly — you send it a signal, a small standard message the kernel delivers. The ones you'll use constantly:

    • `SIGINT` (2) — “please interrupt.” This is what `Ctrl-C` sends to the foreground job. A process that exits from it reports status 130 (128 + 2).
    • `SIGTERM` (15) — “please terminate.” The polite default of the kill command. The process can catch it and clean up first.
    • `SIGKILL` (9) — “stop now.” Sent by `kill -9`; it cannot be caught or ignored, so it's the last resort for something stuck. A process killed this way reports status 137 (128 + 9).

    So kill isn't really about violence — it just sends a signal (SIGTERM by default). Reach for -9 only when a normal kill doesn't work, because the process gets no chance to clean up.

    kill %1; echo "signalled job 1"
    signalled job 1
    [1]+  Terminated              sleep 300
    kill %1 sends SIGTERM (the default) to job 1 by its job number — no PID lookup needed. sleep can't do cleanup but it obeys and the shell reports 'Terminated'. You could equally write kill 4210 to target it by PID. Escalate to kill -9 %1 only if a plain kill is ignored.
    pgrep -a sleep
    (no output — nothing to kill)
    -a
    also print each match's full command line, not just the PID
    -f
    match the pattern against the full command line, not only the process name
    pgrep finds processes by name and prints their PIDs (-a adds the command line); its companion pkill signals them by name. 'pkill sleep' would SIGTERM every sleep at once. Here there's no output because job 1 is already gone — pgrep exits non-zero when nothing matches, which is itself a useful 'is it running?' test. Add -f to match against the whole command line instead of just the process name: pgrep -f backup.sh finds a script by its path even when the process name is just the interpreter.

    Check yourself

    A process is wedged and ignores a normal kill. Which signal is guaranteed to stop it because it can't be caught?

  5. Now do it — drive a job through its whole life

    Background, list, foreground, suspend, and kill a job so the cycle becomes muscle memory.

    One continuous exercise. Start a long-running job in the background, watch the shell track it, then take control of it. Do these in order in your live terminal.

    sleep 600 & sleep 600 & jobs
    Launch two background jobs and list them. You'll see [1] and [2], both Running, with [2] marked + as the current job. One shell is now supervising two processes at once — exactly what job control is for.
    fg %1
    Pull job 1 to the foreground so it owns your terminal. Now press Ctrl-Z: the shell says 'Stopped' and the prompt returns — you've suspended it. Type bg and it resumes in the background, back to Running. You just moved one job foreground → suspended → background by hand.
    kill %1 %2; jobs
    Send SIGTERM to both jobs and list again — they report Terminated and the job table empties. You started, inspected, moved, and cleanly stopped background work without ever leaving this one shell. That's the whole of everyday job control.
Recap
  • A process is a running program with a PID, a parent (PPID), and its own memory; your shell is the parent of the commands you launch. ps lists them; $$ is the shell's own PID.
  • & runs a command in the background and returns the prompt; jobs lists a shell's background jobs as %1, %2, …
  • Ctrl-Z suspends (pauses) the foreground job; bg resumes it in the background; fg brings a job to the foreground. Suspended ≠ backgrounded — bg is what gets a paused job running again.
  • Signals control processes: Ctrl-C = SIGINT (exit 130), kill = SIGTERM (polite default), kill -9 = SIGKILL (uncatchable, exit 137). Target by PID or %job; pgrep/pkill work by name; use nohup/tmux to survive shell close.
Job control & signals cheat-sheet
You do / pressEffect
cmd &run cmd in the background; prompt returns
jobslist this shell's background/suspended jobs
Ctrl-Zsuspend (pause) the foreground job
bg %n / fg %nresume job n in background / foreground
Ctrl-Csend SIGINT to the foreground job (exit 130)
kill %n / kill PIDsend SIGTERM (polite) to a job or PID
kill -9 …send SIGKILL (uncatchable, last resort; exit 137)
pgrep -a name / pkill namefind / signal processes by name