Module 06: Process Management

🎯 Goal: See what is running, understand its lifecycle, and stop or investigate a program without guessing.

By the end of this module, you should be able to identify a process and its PID, interpret basic process state and resource usage, send a graceful termination signal, distinguish a foreground job from a background job, and trace a listening server back to the process that owns it.


Why this matters

Every command you run, every server you start, and every script that hangs or misbehaves is a process — and eventually you will need to see what's running, figure out why something is eating CPU or won't respond, and stop it safely (or forcefully). This is exactly the skill you'll reach for later when a Docker container's process won't exit cleanly, or a Kubernetes pod is stuck, so building solid intuition here on plain Linux pays off directly in the later tracks.

Concepts

Start graceful; escalate deliberately

When a program is stuck, first identify it and ask it to exit with SIGTERM (the default kill signal). Use SIGKILL only when the process cannot respond: it gives the program no chance to flush data, remove temporary files, or release resources. The production habit is: observe → identify → terminate gracefully → verify → escalate only if necessary.

What a process is. A process is a running instance of a program. When you type htop and press Enter, the shell asks the kernel to load that program into memory and start executing it — that running instance is a process, with its own process ID.

PID and PPID. Every process gets a unique number called a PID (process ID) the moment it starts. Every process (except the very first one at boot) is started by some other process, called its parent, whose PID is recorded as the child's PPID (parent process ID). For example, when you run a command in your terminal, your shell (like bash) is the parent, and the command you launched is its child. This parent-child relationship forms a tree all the way back to the first process the kernel starts at boot.

PID 1 (init, the first process at boot)
  └── PID 842  bash (your login shell)          PPID=1
        ├── PID 1105  htop                       PPID=842
        └── PID 1201  sleep 300                  PPID=842
              (backgrounded with &, still a child of bash)

Every box in that tree traces back to PID 1 — this is why ps aux output has a PPID column at all, and why closing a parent shell (without nohup) tends to take its children down with it.

Foreground vs background. A foreground process has control of your terminal — your prompt won't come back until it finishes, and it can read your keyboard input directly. A background process runs without holding onto your terminal, so you get your prompt back immediately and can keep typing other commands while it runs.

Signals. A signal is a small, standardized message sent to a process asking it to do something — most commonly, to stop. SIGTERM ("terminate") politely asks a process to shut down, giving it a chance to clean up open files or save state before exiting; well-behaved programs honor this. SIGKILL is not a request — the kernel terminates the process immediately, with no chance for it to clean up anything. SIGKILL is a last resort for processes that ignore SIGTERM or are stuck (unresponsive).

Ctrl+C and Ctrl+Z from the keyboard. While a foreground process has your terminal, Ctrl+C sends it SIGINT (interrupt), which by default also ends the process — this is your everyday "stop this program" keystroke. Ctrl+Z is different: it sends SIGTSTP, which merely pauses (suspends) the process and hands your prompt back, without ending it — the paused process is still in memory, just not running, until you resume it.

  kill <PID>          SIGTERM (15)   "please stop" — process can catch it,
                                      clean up, and exit on its own terms

  kill -9 <PID>        SIGKILL (9)   "you're stopped" — kernel ends it
                                      instantly, no cleanup, can't be caught

  Ctrl+C                SIGINT (2)   "interrupt" — default action ends it,
                                      same idea as SIGTERM but from a keypress

  Ctrl+Z               SIGTSTP (20)  "pause" — process frozen in memory,
                                      resume later with fg/bg, not terminated

The escalation path for a stuck process is always: SIGTERM first (kill <PID>), give it a moment, only reach for SIGKILL (kill -9) if it truly won't respond.

Jobs: backgrounding and resuming from your shell. Your shell keeps track of processes you started from it as numbered "jobs." Appending & to a command starts it directly in the background. A process you Ctrl+Z-suspended, or backgrounded with &, can later be resumed in the foreground or background. This is entirely a convenience your interactive shell provides — it's tracking jobs launched in that shell session.

Surviving terminal closure with nohup. Normally, when you close your terminal, every process it started (including backgrounded ones) receives a hangup signal and typically dies. nohup ("no hangup") runs a command in a way that ignores that signal, so it keeps running even after you log out or close the terminal — useful for a long-running task you want to walk away from.

Command reference

Command What it does Example
ps aux Lists running processes system-wide with owner, PID, CPU%, memory%, and command. a shows processes for all users, u gives the user-oriented detailed columns, x includes processes not attached to a terminal. ps aux
top Interactive, live-updating table of processes sorted by resource usage; refreshes continuously until you quit. Press q to quit. top
htop A friendlier, colorized, scrollable version of top (installed via apt in module 05); supports mouse clicks and function-key shortcuts for sorting/killing. Press F10 or q to quit. htop
command & Runs command in the background immediately, returning your prompt right away; prints a job number and PID. sleep 300 &
jobs Lists the background/suspended jobs tracked by your current shell session, with job numbers and status (Running/Stopped). jobs
fg %<n> Brings job number n into the foreground (omit %n to use the most recent job). fg %1
bg %<n> Resumes a suspended (stopped) job, but keeps it running in the background instead of the foreground. bg %1
Ctrl+C Sends SIGINT to the foreground process, normally terminating it. Press while a command is running in your terminal
Ctrl+Z Sends SIGTSTP to the foreground process, suspending (pausing) it and returning your prompt. Press while a command is running in your terminal
kill <PID> Sends a signal (default SIGTERM) to a process by PID, asking it to terminate gracefully. kill 4821
kill -9 <PID> Sends SIGKILL to a process by PID, forcing immediate termination with no cleanup. -9 is the numeric signal for SIGKILL. kill -9 4821
killall <name> Sends a signal (default SIGTERM) to every process matching a program name, instead of needing a PID. killall sleep
pgrep <pattern> Searches running processes by name/pattern and prints matching PIDs, without killing anything. pgrep htop
pkill <pattern> Sends a signal (default SIGTERM) to every process whose name matches a pattern, like pgrep combined with kill. pkill -9 sleep
nohup <command> & Runs command immune to the hangup signal sent when your terminal closes, so it keeps running after logout; output is redirected to a file named nohup.out unless you redirect it elsewhere. nohup sleep 600 &

Hands-on exercises

  1. Open your WSL2 Ubuntu terminal. Run ps aux and look at the output. Find the line for your current shell (look for bash or similar in the command column near the end of the line), and note its PID.

  2. Run ps aux | wc -l (a small pipe preview — it counts lines) to see roughly how many processes are currently running on your system.

  3. Launch htop. Take a moment to look at the layout: CPU/memory bars at the top, and the scrollable process table below. Try pressing F6 (or your version's sort key) to sort by different columns, then press q to quit back to your prompt.

  4. Start a long-running foreground command: sleep 60. Notice your prompt does not return — you're stuck waiting. Press Ctrl+C to interrupt it early, and confirm your prompt returns immediately with no "60 seconds" wait.

  5. Start another sleep 300, and this time press Ctrl+Z instead of Ctrl+C. Your prompt should return, and the shell should print something like [1]+ Stopped sleep 300. Run jobs to confirm it's listed as Stopped. Then run bg %1 to resume it in the background, and jobs again to confirm its status changed to Running.

  6. Start a background job directly with &: sleep 120 &. Note the [job-number] PID the shell prints. Run jobs and separately ps aux | grep sleep (previewing a pipe filter) to see the same process from two different views.

  7. Bring that background job to the foreground: fg %1 (adjust the job number to match what jobs shows). Once it's in the foreground, press Ctrl+C to end it early instead of waiting out the full duration.

  8. Find a process by name instead of by PID: start sleep 400 &, then run pgrep sleep to find its PID without scanning ps aux by eye. Kill it gracefully with kill <PID> (using the actual PID pgrep printed), and confirm it's gone by running pgrep sleep again (it should print nothing).

  9. Break something on purpose: run kill 999999 (a PID that almost certainly doesn't exist on your system). Read the error message carefully — it should say "No such process," which tells you the PID was invalid, distinct from a permissions error. Then start sleep 500 &, find its real PID with pgrep sleep, and try kill -0 <PID> (signal 0 sends nothing but checks whether the process exists and you have permission to signal it) to confirm it's alive before finally killing it properly with kill <PID>.

  10. Test nohup: run nohup sleep 120 &. Confirm a file named nohup.out appeared in your current directory (ls). Then run pgrep sleep to confirm it's running, and clean up with pkill sleep to stop it (and optionally rm nohup.out to tidy up the file it created).

Production practice: diagnose before killing

Key idea

A process is an executing program with an identity, a parent, open resources, and a lifecycle. A PID is only a handle—inspect the command and owner before acting on it.

Mental model

A process tree is a family tree. The parent launches a child; a supervisor such as systemd may restart that child when it exits. Killing only the child may appear to work briefly if the parent is designed to bring it back.

flowchart TD
    I["systemd (PID 1)"] --> S["myapp.service"]
    S --> P["myapp process"]
    P --> W["worker process"]
    S -. restart on failure .-> P

The tree explains who may restart a process. The response flow below explains how to intervene safely.

flowchart TD
    A["Unexpected process or port"] --> B["Inspect PID, owner, command"]
    B --> C{"Should it stop?"}
    C -->|No| D["Find parent / supervisor"]
    C -->|Yes| E["SIGTERM"] --> F{"Exited?"}
    F -->|Yes| G["Verify socket and logs"]
    F -->|No| H["Investigate; SIGKILL only if necessary"]
Example

A development server keeps port 3000 busy after a terminal closes. ss -ltnp identifies the listener and PID; ps -fp <PID> confirms it is the old server; kill <PID> requests a clean exit; a second ss check proves the port is free.

Pitfall

Do not use kill -9 as the first response. SIGKILL cannot be caught, so an application cannot flush buffered data, remove a lock file, or close a transaction cleanly. Use it only after you have identified the process and a graceful signal did not work.

A focused incident loop

ps -fp <PID>              # identity, parent, and command
ps -o pid,ppid,user,stat,%cpu,%mem,command -p <PID>
ss -ltnp                  # listening TCP processes
lsof -p <PID>             # open files, when available
kill -TERM <PID>          # request a clean shutdown

Wait briefly and verify the result. If a process returns, inspect its parent, service manager, or container runtime rather than repeatedly killing it.

Exercise

Start python3 -m http.server 8000 in one terminal. In another, identify its PID from the listening socket, inspect its parent and user, stop it with SIGTERM, and prove that curl http://localhost:8000 now fails because no process owns the port.

Interview

Compare SIGTERM and SIGKILL. Then explain why a process can reappear after being killed and how you would discover what restarted it.

Check your understanding
  • Can you find the process listening on a port without guessing its name?
  • Can you explain the difference between a PID and a process tree?
  • Do you have a verification command to run after stopping a process?

Independent challenge

No commands given here — figure it out yourself using what you know from this module and earlier ones.

Task: Start three separate background sleep processes with three clearly different durations. Without writing any PID down by hand as you launch them, later find and gracefully terminate only the longest-running one, leaving the other two alive and running. Then prove those two survivors are still there. If you'd like a live view of all three at once while you work, use the htop tool you installed back in module 05. Do the whole thing without copying the exact command sequence from the exercises above.

Stuck? One hint

pgrep can list the PIDs of processes by name after the fact, and ps shows you each one's command (including the sleep duration) so you can pick out the longest; then a plain kill on just that one PID asks it to stop gracefully.

Common mistakes & troubleshooting

  • Confusing Ctrl+C and Ctrl+Z: Ctrl+C ends the process; Ctrl+Z only pauses it (the process is still alive, just stopped, and will keep occupying resources like open ports until you fg/bg it or kill it). A "stopped" job left forgotten is a classic source of "why is this port still in use" confusion.
  • Killing the wrong PID: PIDs are reused over time as processes come and go, so a PID you saw five minutes ago in ps aux output may belong to a completely different process now. Always re-check with a fresh ps aux, pgrep, or htop immediately before killing something specific.
  • Reaching for kill -9 first: SIGKILL gives the process zero chance to clean up (e.g. flush a file it was writing, release a lock). Try plain kill (SIGTERM) first, and only escalate to -9 if the process ignores it and won't die.
  • Forgetting a job is only tracked per shell session: jobs only shows jobs started in that terminal/shell session — closing the terminal (without nohup) or opening a new one means those job numbers are no longer visible or backgrounded there.
  • Expecting killall/pkill to match exactly what you typed: these match by process name/pattern, which can unexpectedly hit more processes than intended if the name is generic (e.g. pkill python could kill several unrelated Python scripts at once). Double check with pgrep <pattern> first to see exactly what would be affected.
  • Not redirecting nohup output and being surprised by a nohup.out file: if you don't redirect stdout/stderr yourself, nohup writes them into nohup.out in your current directory by default, which can pile up if forgotten.

Checkpoint quiz

Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.

  1. What's the difference between a process's PID and its PPID?
  2. Why does Ctrl+Z not actually end a running program, and what state is that program left in?
  3. You run kill 4821 and nothing seems to happen — the process is still running a few seconds later. What signal did you just send, and what would be the next reasonable step?
  4. Why is kill -9 considered a last resort rather than a first choice?
  5. What's the practical difference between checking ps aux once versus running top/htop?
  6. If you background a long task with sleep 600 & and then close your terminal, what generally happens to it, and what command would you have used instead to keep it running?
  7. What's the difference between jobs and pgrep, given that both can tell you about running processes?
  8. Why should you re-check a process's PID with a fresh ps aux or pgrep right before killing it, rather than trusting a PID you saw a few minutes earlier?
Show answers
  1. The PID is the process's own unique identifier; the PPID is the PID of the process that started it (its parent). Every process has exactly one PPID but processes can have many children, each with the same PPID pointing back to it.
  2. Ctrl+Z sends SIGTSTP, which merely suspends (pauses) the process rather than terminating it. The program remains loaded in memory in a "Stopped" state and can later be resumed with fg or bg; it isn't running or making progress while stopped, but it isn't gone either.
  3. Plain kill sends SIGTERM by default, which is only a polite request — a process can ignore it (or be too stuck to respond). The next step is to confirm it's still alive (e.g. ps aux or pgrep) and, if it truly won't terminate, escalate to kill -9 (SIGKILL) to force it.
  4. Because SIGKILL terminates a process immediately with no opportunity for it to clean up open files, flush buffered writes, or release locks, which can lead to corrupted data or orphaned resources. It should only be used when a graceful SIGTERM request has failed or the process is unresponsive.
  5. ps aux is a single point-in-time snapshot of processes at the moment you ran it. top/htop continuously refresh, showing live, moving CPU/memory usage, which is much more useful for spotting a process that's currently spiking resource usage.
  6. Without nohup, closing the terminal typically sends a hangup signal to processes it started (including backgrounded ones), ending them. Running it as nohup sleep 600 & instead makes it immune to that hangup, so it keeps running after the terminal closes.
  7. jobs lists only the background/suspended processes tracked by your current shell session, by job number. pgrep searches all processes on the system by name/pattern and returns PIDs, regardless of which shell (if any) started them.
  8. Because PIDs get reused as processes start and finish over time; a PID you noted earlier may since have been recycled and now belong to an entirely different, unrelated process, so killing it "from memory" risks terminating the wrong thing.

Further reading & sources

  • man7.org: signal(7) - the full list of Unix signals (this module only covers the four you'll use daily) and how processes can catch or ignore them.
  • man7.org: ps(1) - ps has dozens of output formats beyond aux; worth a skim once ps aux feels routine.
  • htop explained (official site) - htop's own docs, covering the keyboard shortcuts and columns this module only lightly introduced.
  • man7.org: nohup(1) - the nohup reference; also worth knowing disown and setsid exist as related tools for detaching processes from a shell.

Next

Continue to 07 - I/O Redirection and Pipes to learn how to control where a process's input and output actually go — the foundation for chaining commands together.