Phase 1 · FOUNDATIONS
Processes & signals — ps, top, kill, nice
By the end of today
- Explain PIDs, parent/child processes, and the states a process moves through
- Run ps aux and top to find the process eating a server's CPU
- Know when to send SIGTERM (kill) versus SIGKILL (kill -9)
What a running program actually is
Anything doing work on a Linux box is a process: a program the kernel has loaded into memory and is running. Your editor, a build, a web server — each is one or more processes. When a program launches, the kernel hands it a PID, a unique process ID. That number is how you point at one exact process for the rest of its life.
Processes are born from other processes. Every one has a parent (its PPID) that started it, forming a tree that climbs up to PID 1 — the first process the kernel starts at boot (systemd on Ubuntu 24.04) and the ancestor of all the rest. Your shell is a process; the commands you run are its children.
A process also has a state: usually running/runnable (R) or sleeping (S), waiting on disk or network; sometimes stopped (T); briefly zombie (Z) — finished, but still listed until its parent reads the exit code. Reading states is how you tell “busy” from “stuck”.
Viewing them. Two tools cover most of the job. ps aux prints a one-time snapshot of every process — user, PID, %CPU, %MEM, command — ideal for scripts and grep. top is the live view, refreshing every few seconds so you can watch CPU and memory move and catch the PID spiking right now. Press capital P to sort by CPU, q to quit.
Real world: Picture processes as hotel staff. Each has a badge number (the PID) and a manager who hired them (the parent).
psis the roster taped to the wall — true the instant it printed.topis the CCTV feed, showing who’s actually sprinting around now. When one employee goes berserk and hogs every lift, you don’t argue with the roster — you read the CCTV, get the badge number, and deal with that one person.
Signals are how you deal with them — small messages the kernel delivers to a process. Two matter today. kill <PID> sends SIGTERM (15): a polite “please shut down” a well-written process catches so it can flush data and exit cleanly. kill -9 <PID> sends SIGKILL (9): an unstoppable “die now” the kernel enforces; the process gets no chance to clean up. Always try SIGTERM first — keep SIGKILL for something truly stuck, since forcing a kill can leave half-written files behind.
Priority. Not every process deserves equal CPU. nice sets how generous one is to others, from −20 (greedy) to 19 (yields to everyone). Launch a heavy batch job with nice -n 10 ./report.sh so it never starves the web server, and change a running one with renice.
Netflix built a whole practice on this vocabulary: its Chaos Monkey tool deliberately kills running instances in production so teams are forced to write services that survive a sudden death — the SIGKILL, not the courteous SIGTERM. If your app can’t take a kill -9, better to learn that on a Tuesday than at 2 a.m.
That 2 a.m. call is tomorrow’s mission. The move you’re rehearsing is small and exact: a server is crawling, you open top, spot the runaway PID, and end it with kill — reaching for kill -9 only if it refuses to go.
Hands-On Lab
Budget about 25 minutes in your WSL2 Ubuntu 24.04 terminal. Type every command yourself and read the output before moving on — PIDs and times will differ on your box, but the shape of each result won’t. The whole point today is the find-then-act loop: list processes, pick one you own, end it.
# 1. Snapshot every process running right now. ps aux is the classic full listing.
ps aux | head
# Output — a header row, then one row per process (head caps it at 10 lines; the first several shown):
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# root 1 0.0 0.1 167800 11704 ? Ss 09:12 0:00 /sbin/init
# root 2 0.0 0.0 0 0 ? S 09:12 0:00 [kthreadd]
# root 130 0.0 0.2 33792 16640 ? Ss 09:12 0:00 /usr/lib/systemd/systemd-journald
# root 160 0.0 0.1 21500 9600 ? Ss 09:12 0:00 /usr/lib/systemd/systemd-udevd
# pushkar 412 0.0 0.1 17600 9200 pts/0 Ss 09:13 0:00 -bash
# pushkar 530 0.0 0.0 10068 1664 pts/0 R+ 09:20 0:00 ps aux
# 2. Who's using the most CPU? Sort descending on %cpu (the leading - means reverse).
ps aux --sort=-%cpu | head
# Output — on an idle box the command you just ran is often the busiest:
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# pushkar 533 0.5 0.0 10068 1664 pts/0 R+ 09:21 0:00 ps aux --sort=-%cpu
# root 1 0.1 0.1 167800 11704 ? Ss 09:12 0:00 /sbin/init
# root 130 0.0 0.2 33792 16640 ? Ss 09:12 0:00 /usr/lib/systemd/systemd-journald
# 3. Start a background process you own. The trailing & runs it and gives the prompt back.
sleep 300 &
# Output — the shell prints [job number] then the PID:
# [1] 612
jobs
# Output — your own background jobs, by job number:
# [1]+ Running sleep 300 &
# 4. Find that process by name. grep also matches itself — that's the second line.
ps aux | grep sleep
# Output:
# pushkar 612 0.0 0.0 7232 1664 pts/0 S 09:22 0:00 sleep 300
# pushkar 640 0.0 0.0 9040 2176 pts/0 S+ 09:22 0:00 grep --color=auto sleep
# 5. Ask it to stop politely. Plain kill sends SIGTERM (15) — a request, delivered silently.
kill 612
# (no output — SIGTERM was delivered)
# Press Enter once more and the shell reports the job finished:
# [1]+ Terminated sleep 300
ps aux | grep sleep
# Output — only the grep itself is left; the sleep is gone:
# pushkar 655 0.0 0.0 9040 2176 pts/0 S+ 09:23 0:00 grep --color=auto sleep
# 6. Now force one. Start a fresh sleep, then send SIGKILL (9) with kill -9.
sleep 300 &
# Output:
# [1] 690
kill -9 690
# Press Enter once more — note it says "Killed", not "Terminated":
# [1]+ Killed sleep 300
# 7. Watch processes live instead of one snapshot. Press capital P to sort by CPU, q to quit.
top
# Output — a live screen that refreshes every few seconds (numbers vary):
# top - 09:24:01 up 12 min, 1 user, load average: 0.05, 0.09, 0.08
# Tasks: 28 total, 1 running, 27 sleeping, 0 stopped, 0 zombie
# %Cpu(s): 0.7 us, 0.3 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
# MiB Mem : 7900.5 total, 6800.2 free, 600.1 used, 500.2 buff/cache
#
# PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
# 742 pushkar 20 0 12388 4608 3456 R 0.7 0.1 0:00.03 top
# 1 root 20 0 167800 11704 8400 S 0.0 0.1 0:00.90 systemd
# 8. Priority in action. Launch a job that yields to others (nice 10), then read its NI value.
nice -n 10 sleep 300 &
# Output:
# [1] 770
ps -o pid,ni,cmd -p 770
# Output — NI (nice value) is 10, not the default 0:
# PID NI CMD
# 770 10 sleep 300
kill 770 # tidy up the last background job
Before you close the terminal, read it back: you listed every process, sorted them to find the busiest, started jobs you owned, and ended them two ways — a polite kill that printed Terminated and a forced kill -9 that printed Killed. That find-then-act loop is exactly tomorrow’s 2 a.m. drill.
Common Errors & Fixes
These are the three failures almost everyone hits in their first hour with kill. Read the error text slowly — learning to parse it is the actual skill.
Common error: Copying a PID from an old
pslisting and runningkill 612after that process has already exited prints:bash: kill: (612) - No such processWhy: PIDs are a moving target. By the time you act, that process may have finished and the kernel may have handed the number to something else — or nothing.
killrefuses to signal a PID that isn’t currently alive rather than guess.Fix: Re-list before you act. Run
ps aux | grep <name>(orpgrep <name>) fresh, read the current PID, then signal that. Never trust a PID you copied minutes ago.How you’d spot it in prod: A restart script that fails intermittently with No such process usually raced the process — it exited on its own before the script’s
killfired. The fix is to check the PID is live (or usekill -0 <PID>to test) before signalling, not to add a blind retry.
Common error: Curiosity gets the better of you and you try to signal PID 1, or a process owned by another user, as your normal account:
bash: kill: (1) - Operation not permittedWhy: Signals respect ownership. A non-root user can only signal processes they own. PID 1 (
systemd) and other users’ processes belong toroot, so the kernel blocks the request outright — this is a guardrail, not a bug.Fix: For a process you legitimately need to stop but don’t own, borrow privilege for that one command:
sudo kill <PID>. Never signal init:systemdas PID 1 ignores a plainsudo kill 1(SIGTERM), the kernel refuses to deliver SIGKILL to it at all, and sending the wrong signal to PID 1 can trigger a reboot — leave it alone. Confirm the owner in theUSERcolumn ofps auxfirst.How you’d spot it in prod: Operation not permitted in a deploy log almost always means the CI or service account is trying to stop a process running as a different user. The fix is aligning the user the job runs as with the user that owns the target — not scattering
sudoaround.
Common error: A stuck process ignores your polite
kill. You send SIGTERM, get no error, and yetpsstill shows it seconds later:$ kill 812 $ ps -p 812 -o pid,stat,cmd PID STAT CMD 812 S ./stuck-workerWhy: SIGTERM is catchable. A process can install a handler for it (or be wedged in a way that never processes it), so
killdelivered the signal but the process chose — or failed — to act on it.killreturning silently means “signal sent”, not “process dead”.Fix: Give it a moment to shut down gracefully, then escalate:
kill -9 812sends SIGKILL, which the kernel enforces and no process can catch. Reserve this for the genuinely stuck — it skips all cleanup.How you’d spot it in prod: A service that “won’t stop” on
systemctl stopand gets force-killed after a timeout is this exact pattern. Chronic SIGTERM timeouts mean the app never wired up graceful shutdown — the durable fix is in the app’s signal handling, not a fasterkill -9.
Processes & Signals Interview Questions
The five questions below are the process-and-signal fundamentals that open almost every Linux screening round, and this day’s answer bank renders right after this note. Cover each answer, say your own version out loud first, then compare — recalling before revealing is what makes it stick for interview day.
Go Deeper
Optional extras if you have ~30 more minutes today:
- 10 min — Run
man 7 signalto see the full signal table. Beyond SIGTERM and SIGKILL, learn SIGINT (what Ctrl-C sends) and SIGHUP (often “reload your config”) — the four you’ll actually use. - 10 min — Install
htop(sudo apt install htop) and run it: a colour, scrollabletopwhere you can select a process and send it a signal from a menu (F9) without typing a PID. - 10 min — Peek at
/proc:cat /proc/1/commnames PID 1, andls /proc/<PID>/shows the kernel’s live view of any process. Then read the process section of the Linux for DevOps guide.
What is a PID? Both
A PID is a process ID — the unique number the Linux kernel assigns to every running process when it starts. It's how you refer to a specific process to inspect or control it: kill, renice, and top all take a PID. PID 1 is special — it's the first process the kernel starts (systemd on a modern Ubuntu box) and it's the ancestor of everything else, so you never kill it. PIDs aren't permanent: when a process exits its number is eventually reused. In practice you rarely memorise a PID — you find it with ps or top, then act on it. That find-then-act loop is the whole skill.
What's the difference between SIGTERM and SIGKILL — kill versus kill -9? Both
kill by default sends SIGTERM, signal 15 — a polite request to shut down. A well-behaved process catches it, finishes what it's doing, flushes buffers, closes files and sockets, and exits cleanly. kill -9 sends SIGKILL, signal 9, which the kernel enforces directly — the process can't catch, block, or ignore it, so it dies instantly with no cleanup. Always try SIGTERM first; reach for kill -9 only when a process is truly stuck and ignoring the polite ask. The tradeoff is that SIGKILL can leave corrupt files, orphaned locks, or half-written state behind. In an interview, the one-liner is: SIGTERM asks, SIGKILL forces.
How do you find the process that's eating all the CPU? Both
I start with top — it shows a live, sorted view, and by default the busiest process floats to the top of the %CPU column. Press capital P to force a CPU sort, note the PID and command, and press q to quit. For a non-interactive snapshot, especially in a script or over SSH, I use ps aux --sort=-%cpu | head, which prints the top offenders once and exits. Once I have the PID I decide whether it's legitimate work or a runaway. If it's runaway I send SIGTERM first, then SIGKILL if it won't budge. On a modern box htop makes the same job friendlier, but top and ps are always installed.
What is a zombie or defunct process? Both
A zombie — shown as defunct or state Z in ps — is a process that has already finished but whose entry lingers in the process table because its parent hasn't collected its exit status yet. That collection is called reaping, done via the wait system call. A zombie uses no CPU and almost no memory; it's just a bookkeeping stub holding a PID. You can't kill a zombie — it's already dead. The real fix is the parent: if it's buggy and never reaps, you restart or fix the parent, and when the parent dies, PID 1 adopts the orphaned zombies and reaps them. A handful is harmless; thousands means a broken parent.
When would you use ps versus top? Both
They answer different questions. ps takes a one-time snapshot and exits — perfect for scripts, logs, and grepping for a specific process, as in ps aux | grep nginx. top is interactive and live: it refreshes every couple of seconds so you can watch CPU and memory move in real time, sort columns, and spot a spike as it happens. Rule of thumb: ps to capture a moment or feed another command, top to observe behaviour over time. In an incident I open top first to see what's happening now, then use ps to grab the exact line I want to paste into the ticket.
Mark Day 6 complete
Tomorrow you play: Server Down!
Stuck on today’s lab? Ask in Mission 90 Q&A