Phase 2: Process State & System Calls
Phase 2: Process State & System Calls
Target: Weeks 4-7 (May 19 - Jun 8, 2026)
Your colleague flagged this. He’s right. This is where the competition separates engineers who USE Linux from engineers who UNDERSTAND Linux. System calls are the kernel API — every command you run translates to syscalls.
Man Pages to Master
| Reference | What It Teaches |
|---|---|
|
Overview of ALL system calls — the kernel interface |
|
Opening files — flags (O_RDONLY, O_CREAT, O_TRUNC), file descriptors returned |
|
Reading/writing bytes — fd, buffer, count |
|
Closing file descriptors — when the kernel reclaims resources |
|
Process creation — parent gets child PID, child gets 0 |
|
Replace process image — this is how programs actually run |
|
Parent waits for child — zombie prevention |
|
Creating pipes — fd[0] read end, fd[1] write end |
|
Redirect file descriptors — how shell redirection works under the hood |
|
Network socket lifecycle |
|
Sending signals — not just termination |
|
All signals: SIGTERM, SIGKILL, SIGSTOP, SIGCONT, SIGHUP, SIGUSR1/2 |
|
Trace system calls of a running process — |
|
List open files — |
|
Process listing — |
|
Process hierarchy — |
|
|
Concepts to Internalize
How a Command Actually Runs
You type: cat /etc/hostname What the kernel does: 1. fork() — shell creates a child process (copy of itself) 2. execve() — child replaces itself with /usr/bin/cat 3. open() — cat opens /etc/hostname (returns fd 3) 4. read() — cat reads bytes from fd 3 into a buffer 5. write() — cat writes buffer to fd 1 (stdout) 6. close() — cat closes fd 3 7. exit() — cat exits with status 0 8. wait() — shell reaps child, gets exit status Every command follows this pattern. strace shows you each step.
File Descriptors
Every process starts with 3 file descriptors: fd 0 = stdin (keyboard or pipe input) fd 1 = stdout (terminal or pipe output) fd 2 = stderr (terminal, error messages) New files open at the next available fd (usually 3, 4, 5...) Shell redirection is fd manipulation: cmd > file → open(file, O_WRONLY|O_CREAT|O_TRUNC) → dup2(fd, 1) cmd 2>&1 → dup2(1, 2) — stderr points to same place as stdout cmd < file → open(file, O_RDONLY) → dup2(fd, 0) cmd | cmd2 → pipe() creates fd pair, fork(), dup2() connects them Verify with: ls -la /proc/$$/fd — your shell's open fds ls -la /proc/<pid>/fd — any process's open fds
Signals
Signals are asynchronous notifications to a process. Critical signals to know: SIGTERM (15) — polite termination request (kill default) SIGKILL (9) — immediate death, cannot be caught or ignored SIGSTOP (19) — pause process, cannot be caught SIGCONT (18) — resume stopped process SIGHUP (1) — terminal hangup, often used to reload config SIGINT (2) — Ctrl+C SIGQUIT (3) — Ctrl+\ (core dump) SIGUSR1 (10) — user-defined SIGUSR2 (12) — user-defined SIGCHLD (17) — child process state changed Trap in bash: trap 'echo caught' SIGTERM trap cleanup EXIT — runs on ANY exit (normal or error) Send: kill -SIGTERM <pid> — or just: kill <pid> kill -9 <pid> — SIGKILL (last resort) kill -0 <pid> — test if process exists (no signal sent)
Process States
STAT column in ps aux: R = Running or runnable S = Sleeping (interruptible — waiting for I/O) D = Uninterruptible sleep (usually disk I/O — cannot be killed) T = Stopped (SIGSTOP or Ctrl+Z) Z = Zombie (exited but parent hasn't called wait()) I = Idle (kernel thread) Modifiers: < = High priority N = Low priority (nice) s = Session leader l = Multi-threaded + = Foreground process group Competition scenario: "Why can't I kill this process?" D state → waiting for I/O → SIGKILL won't work until I/O completes Z state → already dead → parent needs to wait() or parent must die
Drills
# Trace cat reading a file. Identify the syscall sequence:
strace -e trace=openat,read,write,close cat /etc/hostname 2>&1
# Expected sequence: openat() → read() → write() → close()
# man strace → -e trace=SET
# Why does this fail? Use strace to find the actual error:
strace -e trace=openat cat /nonexistent/file 2>&1
# Look for: openat() returning -1 ENOENT
# man 2 open → ERRORS section
# Start a background process, inspect its fds:
sleep 300 &
PID=$!
ls -la /proc/$PID/fd/
cat /proc/$PID/status | head -10
cat /proc/$PID/cmdline | tr '\0' ' ' && echo
kill $PID
# Create a zombie:
bash -c 'sleep 1 & exec sleep 300' &
PARENT=$!
sleep 2
# Find the zombie:
ps aux | grep Z
# The sleep 1 child exited but parent (sleep 300) hasn't wait()ed.
# Kill the parent to reparent zombie to init (PID 1), which reaps it:
kill $PARENT
sleep 1
ps aux | grep Z # zombie should be gone
# Write a script that catches SIGTERM and cleans up:
cat << 'SCRIPT' > /tmp/drill-signal.sh
#!/usr/bin/env bash
set -euo pipefail
TMPFILE=$(mktemp)
trap 'echo "Caught signal, cleaning up $TMPFILE"; rm -f "$TMPFILE"; exit 0' SIGTERM SIGINT
trap 'rm -f "$TMPFILE"' EXIT
echo "PID: $$, tmpfile: $TMPFILE"
echo "Send: kill -SIGTERM $$"
while true; do
date >> "$TMPFILE"
sleep 1
done
SCRIPT
chmod +x /tmp/drill-signal.sh
/tmp/drill-signal.sh &
# Then: kill -SIGTERM <pid>
# Trace what happens in a pipe:
strace -f -e trace=pipe,fork,dup2,read,write sh -c 'echo hello | cat' 2>&1 | head -30
# -f follows forks (pipe creates child processes)
# You'll see: pipe() → fork() → dup2() → write() → read()
# man 2 pipe → DESCRIPTION
# What files does sshd have open?
lsof -c sshd | head -20
# What process is listening on port 22?
lsof -i :22
# What processes have files open in /etc/?
lsof +D /etc/ 2>/dev/null | head -20
# What deleted files are still held open?
lsof +L1
# man lsof → OUTPUT section (understand every column)