Computing Foundations › Inside the Machine › Day 7
Hands-on lab — Day 7: Processes, Threads, and Scheduling
- ← Back to the Day 7 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/computing-foundations/day-007-processes-threads-and-scheduling/
Commands
Setup
cd labs/sections/computing-foundations/day-007-processes-threads-and-scheduling Run
bash examples/process_playground.sh
bash starter/process_playground.sh Test
bash tests/run_tests.sh File tree
examples/process_playground.sh expected-output/FIELDS.md expected-output/sample-run-macos.txt metadata.yml README.md requirements/README.md security.md starter/process_playground.sh starter/process-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 007 lab — Processes in the Wild: Spawn, Watch, Signal
Lesson
- Lesson title: Processes, Threads, and Scheduling
- Day number: 7 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-007-processes-threads-and-scheduling
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-007-processes-threads-and-schedulingwhen the site is running.
Purpose
Day 7's lesson explains processes, threads, and the scheduler. This lab puts your hands on the controls: you count the process population on your own machine, trace your shell's ancestry through the process tree, spawn a background process, watch it through both the shell's eyes (jobs) and the system's (ps), terminate it politely with a signal, and verify its death by exit code — the exact spawn-observe-signal-verify cycle you will use on real training jobs later in the course.
Learning objectives
- Start a background process with
&and capture its PID from$!immediately. - Observe a process with
jobs -landps -o pid,ppid,stat,command, and read its state. - Trace a parent chain: your script's PID, its parent shell, and upward toward PID 1.
- Terminate a process politely with SIGTERM, collect its exit status with
wait, and explain why it is 143. - Observe the difference between
kill(SIGTERM) andkill -9(SIGKILL) on processes you started. - Complete a working shell script by filling in five well-specified exercises and pass an automated test suite.
Prerequisites
- The Day 7 lesson (read it first — it explains every concept this lab exercises).
- The Day 1 lab (comfort running commands in a terminal).
- A terminal: Terminal.app (macOS), any terminal (Linux), or WSL (Windows).
Supported operating systems
- macOS — fully supported (tested on macOS with Apple Silicon).
- Linux — fully supported (any distribution; only standard
ps,kill,sleepare used). - Windows — run the scripts unmodified inside WSL; native PowerShell equivalents (
Get-Process,Stop-Process) exist but are not covered by the tests.
Hardware requirements
Any computer that runs a shell. The lab spawns nothing heavier than a sleep, which uses effectively zero CPU and memory.
Required software
bash(3.2 or newer — preinstalled on macOS and Linux).- Standard OS utilities only:
ps,kill,sleep,wc,tail,tr,sed,date, plus the shell built-insjobs,wait, andtrap. All preinstalled.
Free and open-source options
Everything in this lab is free: bash and every command used ship with your operating system. No account, API key, or purchase is needed — this is true of every lab in the course wherever possible, and any exception is labelled.
Installation
None. Clone the repository (or copy this directory) and you are ready:
cd labs/sections/computing-foundations/day-007-processes-threads-and-scheduling
File structure
day-007-processes-threads-and-scheduling/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── process_playground.sh ← YOUR working file (5 exercises)
│ └── process-worksheet.md ← worksheet for the practice assignment
├── examples/
│ └── process_playground.sh ← completed reference implementation
├── tests/
│ └── run_tests.sh ← automated checks
├── expected-output/
│ ├── sample-run-macos.txt ← real captured run (macOS, Apple Silicon)
│ └── FIELDS.md ← required fields; why your PIDs will differ
├── requirements/
│ └── README.md ← dependency statement (none beyond the OS)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. See the finished result first
bash examples/process_playground.sh
## 2. Your task: complete the five exercises in the starter, then run it
bash starter/process_playground.sh
## 3. Check your work
bash tests/run_tests.sh
What the commands do
bash examples/process_playground.sh— runs the reference script: counts every process (ps -e | tail -n +2 | wc -l), prints its own PID ($$) and walks its parent chain withps -o pid,ppid,command, startssleep 300 &, captures the worker's PID from$!, shows the worker viajobs -landps -o pid,ppid,stat,command, sends SIGTERM withkill, collects exit status 143 withwait, and proves the PID is gone withps -p.bash starter/process_playground.sh— the same skeleton with five exercises left for you; each exercise comment names the exact command to use. The script runs safely even before you start (the spawn/kill steps activate once Exercise 3 is done).bash tests/run_tests.sh— runs both scripts and checks: exit code 0, header/footer, a real numeric process count, that the script spawned its own worker and captured its PID, that the worker died to SIGTERM (exit status 143), that the script verified the death, and — independently — that the worker's PID no longer exists afterward (no orphaned sleeps). The tests only read the process table; they never signal anything themselves.
Expected output
See expected-output/sample-run-macos.txt — a real captured run:
=== Process Playground ===
Generated on: 2026-07-12
Processes running right now: 912
This script's PID: 25751
Parent chain (this script and up to 5 ancestors):
25751 1 bash examples/process_playground.sh
1 0 /sbin/launchd
Started background worker: sleep 300 (PID 25776)
The worker as seen by jobs:
[1]+ 25776 Running sleep 300 &
The worker as seen by ps:
PID PPID STAT COMMAND
25776 25751 SN sleep 300
Sent SIGTERM to 25776; wait reported exit status 143 (143 = 128 + 15, death by SIGTERM)
Verified: PID 25776 is gone
=== End of playground ===
Every PID (and the process count) will differ on your run — the kernel assigns PIDs at spawn time. What must match is the shape: the worker's PPID equals the script's PID, the exit status is exactly 143, and the verified-gone line names the spawned PID. Run from an interactive terminal, your parent chain will be longer than the sample's (your shell, your terminal app, up toward PID 1); the sample was captured from a detached run, so its chain ends at /sbin/launchd. expected-output/FIELDS.md lists exactly which fields must appear.
Validation steps
- Run
bash starter/process_playground.sh— it must exit without errors. - Confirm no line contains
unknown. - Confirm the worker's PPID in the
psoutput equals the script's own PID — parent and child. - Confirm the exit status line reports 143, and the final line reads
Verified: PID <yours> is gone. - Run
pgrep -l sleep— no sleeper of yours should remain. - Run the tests (next section) — all checks must pass.
Tests
bash tests/run_tests.sh
Expected final line: 17 checks, 0 failure(s). (12 strict checks against the reference script, and 5 structural checks against your starter — which become 12 strict checks once you have replaced every unknown, for 24 checks, 0 failure(s). in total). The command exits 0 on success, non-zero on any failure, so it can run in CI.
Cleanup
The scripts clean up after themselves: each sets a trap so its own worker is terminated even if the script fails midway, and the tests verify no sleeper survives. If you experimented manually and lost track of a sleeper, see the orphaned-sleeps entry in troubleshooting.md — or simply wait: a sleep 300 exits by itself within five minutes. To reset your work: git checkout -- starter/process_playground.sh.
Troubleshooting
See troubleshooting.md for the full list (stale PIDs, wait refusing foreign children, per-shell jobs, orphaned sleeps and cleanup, 137-versus-143 confusion, WSL notes).
Security notes
See security.md. Short version: the scripts only ever signal the one process they started themselves, captured from $! — never signal a process you did not start, never sudo kill, and treat kill -9 as a last resort because it forbids cleanup.
Extension exercises
- Add a second worker to your completed script (
sleep 300 &again, capturing a second PID), terminate one withkilland the other withkill -9, and print both exit statuses (143 versus 137) with a line explaining the 128 + signal-number arithmetic. - Freeze and thaw: send your worker
kill -STOP <PID>, show itsTstate inps -o stat, resume it withkill -CONT <PID>, then terminate it politely. - Make the scheduler visible: run
yes > /dev/null &(a pure CPU burner), watch it withtop, add more burners than you have cores, observe the sharing, then terminate every burner you started — politely, by the PIDs you captured.
Navigation
- Previous day: Day 6 — Operating Systems: What They Do and Why (
labs/sections/computing-foundations/day-006-operating-systems-what-they-do-and/). - Next day: Day 8 — the first day of Week 2, The Command Line (
labs/sections/computing-foundations/command-line/week-02/, to be written). - Weekly project: Annotated Machine Teardown (
labs/sections/computing-foundations/project/).
Expected output
FIELDS.md
# Required output fields (all platforms)
A correct run of `process_playground.sh` prints, in order:
1. `=== Process Playground ===`
2. `Generated on: YYYY-MM-DD`
3. `Processes running right now: <positive integer>`
4. `This script's PID: <positive integer>`
5. A parent chain of one or more `pid ppid command` rows
6. `Started background worker: sleep 300 (PID <positive integer>)`
7. The worker in `jobs` output (`... Running ... sleep 300 &`)
8. The worker in `ps` output (a row with PID, PPID, STAT, and `sleep 300`;
the worker's PPID equals the script's PID from line 4)
9. `Sent SIGTERM to <PID>; wait reported exit status 143 (143 = 128 + 15, death by SIGTERM)`
10. `Verified: PID <PID> is gone`
11. `=== End of playground ===`
`sample-run-macos.txt` in this directory is a real captured run (macOS,
Apple Silicon, 2026-07-12). **Every PID differs on every run** — PIDs are
assigned by the kernel at spawn time, so your numbers (and your process
count) will not match the sample, and that is expected. What must match is
the *shape*: the worker's PPID equals the script's PID, the exit status is
exactly 143, and the final verification line names the same PID that was
spawned. The parent chain also differs by how you launch the script: from
an interactive terminal it climbs through your shell and terminal app; the
sample was captured from a detached run, so its chain is short and ends at
the system's ancestral process (`/sbin/launchd` on macOS; on Linux you
would see `systemd` or `init` as PID 1). The `STAT` column may read `S` or
`SN` on macOS and `S` on Linux — both are the sleeping/waiting state.
sample-run-macos.txt
=== Process Playground ===
Generated on: 2026-07-12
Processes running right now: 912
This script's PID: 25751
Parent chain (this script and up to 5 ancestors):
25751 1 bash examples/process_playground.sh
1 0 /sbin/launchd
Started background worker: sleep 300 (PID 25776)
The worker as seen by jobs:
[1]+ 25776 Running sleep 300 &
The worker as seen by ps:
PID PPID STAT COMMAND
25776 25751 SN sleep 300
Sent SIGTERM to 25776; wait reported exit status 143 (143 = 128 + 15, death by SIGTERM)
Verified: PID 25776 is gone
=== End of playground ===
Source files
examples/process_playground.sh (2203 bytes)
#!/usr/bin/env bash
# Day 007 lab — completed reference implementation.
# Spawns a background process, observes it with jobs and ps, terminates it
# politely with SIGTERM, and verifies it is gone. Also counts the machine's
# processes and prints this script's own PID and parent chain.
#
# Safety rule (see security.md): this script only ever signals the one
# process it started itself, whose PID it captured from $!.
set -u
echo "=== Process Playground ==="
echo "Generated on: $(date '+%Y-%m-%d')"
# --- 1. How many processes exist right now? ---------------------------------
process_count="$(ps -e | tail -n +2 | wc -l | tr -d ' ')"
echo "Processes running right now: ${process_count}"
# --- 2. Who am I, and who spawned me? ----------------------------------------
echo "This script's PID: $$"
echo "Parent chain (this script and up to 5 ancestors):"
pid=$$
depth=0
while [ -n "${pid}" ] && [ "${pid}" -gt 0 ] && [ "${depth}" -lt 6 ]; do
ps -o pid=,ppid=,command= -p "${pid}" 2>/dev/null | sed 's/^/ /'
next="$(ps -o ppid= -p "${pid}" 2>/dev/null | tr -d ' ')"
if [ -z "${next}" ] || [ "${next}" = "${pid}" ] || [ "${next}" -eq 0 ]; then
break
fi
pid="${next}"
depth=$((depth + 1))
done
# --- 3. Spawn a background worker and capture its PID from $! ----------------
sleep 300 &
worker_pid=$!
# Safety net: if this script dies early for any reason, take the worker along.
trap 'kill "${worker_pid}" 2>/dev/null' EXIT
echo "Started background worker: sleep 300 (PID ${worker_pid})"
echo "The worker as seen by jobs:"
jobs -l | sed 's/^/ /'
echo "The worker as seen by ps:"
ps -o pid,ppid,stat,command -p "${worker_pid}" | sed 's/^/ /'
# --- 4. Terminate it politely (SIGTERM, the kill default) --------------------
kill "${worker_pid}"
wait "${worker_pid}" 2>/dev/null
exit_status=$?
echo "Sent SIGTERM to ${worker_pid}; wait reported exit status ${exit_status} (143 = 128 + 15, death by SIGTERM)"
# --- 5. Verify the worker is really gone -------------------------------------
if ps -p "${worker_pid}" > /dev/null 2>&1; then
echo "ERROR: worker ${worker_pid} is still alive" >&2
exit 1
fi
echo "Verified: PID ${worker_pid} is gone"
echo "=== End of playground ==="
metadata.yml (673 bytes)
lesson_id: D007
day: 7
kind: process-tracing
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-007-processes-threads-and-scheduling
run_commands:
- bash examples/process_playground.sh
- bash starter/process_playground.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'pgrep -l sleep # check for orphaned sleeps YOU started, then kill only those PIDs'
- 'git checkout -- starter/process_playground.sh # optional: reset your work'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (Apple Silicon), bash tests/run_tests.sh → 17 checks, 0 failures'
requirements/README.md (704 bytes)
# Dependencies — Day 007 lab
**None beyond a POSIX shell.** This lab intentionally has zero installable
dependencies:
- `bash` ≥ 3.2 (preinstalled on macOS and every mainstream Linux distribution)
- Standard OS utilities only: `ps`, `kill`, `sleep`, `wc`, `tail`, `tr`,
`sed`, `date` — all part of the base system on macOS and Linux, and the
shell built-ins `jobs`, `wait`, `trap`, and the `$!`/`$$` variables.
Windows users: run everything inside WSL (any distribution) — the scripts
use portable `ps -o` column syntax that works identically on macOS and
Linux. There is deliberately no `requirements.txt`/`package.json` here;
process control is an operating-system skill, not a library.
starter/process_playground.sh (2656 bytes)
#!/usr/bin/env bash
# Day 007 lab — spawn, watch, and signal a process of your own.
#
# This starter script runs as-is, but five steps are left for you. Each
# exercise comment names the exact command to use — replace the `unknown`
# assignment (or follow the instruction) and run the script again. The
# completed reference version is in examples/process_playground.sh — try
# it yourself first.
#
# Safety rule (see security.md): only ever signal the process YOU started,
# whose PID you captured from $!. Never type PIDs you found elsewhere.
set -u
echo "=== Process Playground ==="
echo "Generated on: $(date '+%Y-%m-%d')"
# Exercise 1: count the processes on this machine using:
# ps -e | tail -n +2 | wc -l | tr -d ' '
# (ps -e lists every process; tail -n +2 drops the header; wc -l counts lines)
process_count="unknown"
echo "Processes running right now: ${process_count}"
# Exercise 2: print this script's own PID (the special variable $$), then
# show its process-table row and its parent's row using:
# ps -o pid,ppid,command -p $$
echo "This script's PID: $$"
echo "Parent chain (this script and its parent):"
# --- add your ps command below this line ---
# Exercise 3: start a background worker with `sleep 300 &` and IMMEDIATELY
# capture its PID from $! into worker_pid (worker_pid=$!). The skeleton
# below only runs the remaining steps once you have done so.
worker_pid="unknown"
if [ "${worker_pid}" != "unknown" ]; then
# Safety net: if this script dies early, take the worker along.
trap 'kill "${worker_pid}" 2>/dev/null' EXIT
echo "Started background worker: sleep 300 (PID ${worker_pid})"
echo "The worker as seen by jobs:"
jobs -l | sed 's/^/ /'
echo "The worker as seen by ps:"
# Exercise 4a: find your worker in the process table using:
# ps -o pid,ppid,stat,command -p "${worker_pid}"
# --- add your ps command below this line ---
# Exercise 4b: terminate the worker POLITELY (SIGTERM, the default) using:
# kill "${worker_pid}"
# then collect its exit status with:
# wait "${worker_pid}" 2>/dev/null; exit_status=$?
# --- add your kill and wait commands below this line ---
exit_status="unknown"
echo "Sent SIGTERM to ${worker_pid}; wait reported exit status ${exit_status}"
# Exercise 5: verify the worker is gone using:
# ps -p "${worker_pid}"
# (it should find nothing — test with: if ! ps -p "${worker_pid}" > /dev/null 2>&1; then ...)
# Print exactly: Verified: PID ${worker_pid} is gone
# --- add your verification below this line ---
else
echo "Exercise 3 not completed yet: no background worker was started."
fi
echo "=== End of playground ==="
starter/process-worksheet.md (2178 bytes)
# Process worksheet — Day 007
Fill this in from a real session in one terminal on your own machine.
Commands to use are shown with each field; record what *your* system printed.
## 1. The process population
- Command used: `ps -e | tail -n +2 | wc -l`
- Total processes right now: `____`
- Roughly how many cores does your machine have (from your Day 1 worksheet)? `____`
- One sentence: how can that many processes share that few cores?
> _your answer here_
## 2. Your shell and its ancestors
- Your shell's PID (`echo $$`): `____`
- Your shell's parent (`ps -o pid,ppid,command -p $$` — the PPID column): `____`
- The parent's own row (`ps -o pid,ppid,command -p <that PPID>`):
```text
(paste the line here)
```
- In words: what program spawned your shell (a terminal app? another shell? a login manager?)
> _your answer here_
## 3. One background-job lifecycle, observed
Run each step and record the evidence:
| Step | Command | What your system printed |
| --- | --- | --- |
| Start | `sleep 300 &` | job number and PID: `____` |
| Capture | `echo $!` | `____` |
| Shell's view | `jobs -l` | `____` |
| System's view | `ps -o pid,ppid,stat,command -p <PID>` | `____` |
| Signal | `kill <PID>` | (usually silent) |
| Exit status | `wait <PID>; echo $?` | `____` |
| Verify | `ps -p <PID>` | `____` |
- The `stat` column showed: `____` — which lifecycle state is that?
- The PPID of your sleep matched: `____` (compare with your `$$` above)
## 4. Polite versus forced: kill versus kill -9
Start two fresh sleepers (`sleep 300 &` twice, capturing each PID from `$!`
right away). Terminate the first with `kill <PID1>` and the second with
`kill -9 <PID2>`, collecting each exit status with `wait <PID>; echo $?`.
- Exit status after plain `kill` (SIGTERM): `____`
- Exit status after `kill -9` (SIGKILL): `____`
- What the shell printed for each (e.g. `Terminated` vs `Killed`):
```text
(paste both lines here)
```
- Two or three sentences: what is the difference between the two signals,
why does the exit-status arithmetic (128 + signal number) explain your
two numbers, and when is each signal appropriate?
> _your answer here_
tests/run_tests.sh (4145 bytes)
#!/usr/bin/env bash
# Tests for the Day 007 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Verifies that the completed reference script spawns its OWN background
# worker, finds it, terminates it with SIGTERM, and verifies its death —
# and, if the learner has finished the starter script, checks their version
# the same way. The test suite never signals any process the scripts did
# not start themselves.
set -u
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
failures=0
checks=0
check() {
local label="$1" ok="$2"
checks=$((checks + 1))
if [ "${ok}" = "yes" ]; then
echo " ok: ${label}"
else
echo " FAIL: ${label}"
failures=$((failures + 1))
fi
}
run_playground_checks() {
local script="$1" strict="$2" output
echo "Testing ${script} ..."
if ! output="$(bash "${script}" 2>&1)"; then
check "script exits successfully" "no"
echo "${output}" | sed 's/^/ /'
return
fi
check "script exits successfully" "yes"
echo "${output}" | grep -q '^=== Process Playground ===$' && check "prints playground header" "yes" || check "prints playground header" "no"
echo "${output}" | grep -q '^=== End of playground ===$' && check "prints playground footer" "yes" || check "prints playground footer" "no"
echo "${output}" | grep -q '^Processes running right now:' && check "prints a process count line" "yes" || check "prints a process count line" "no"
echo "${output}" | grep -q '^This script'"'"'s PID:' && check "prints its own PID" "yes" || check "prints its own PID" "no"
if [ "${strict}" = "strict" ]; then
# The process count must be a real positive integer, not 'unknown'.
count="$(echo "${output}" | sed -n 's/^Processes running right now: //p' | head -n 1)"
case "${count}" in
'' | *[!0-9]*) check "process count is a positive integer" "no" ;;
*) check "process count is a positive integer" "yes" ;;
esac
if echo "${output}" | grep -q "unknown"; then
check "no value is left 'unknown'" "no"
else
check "no value is left 'unknown'" "yes"
fi
# The script must have spawned its own worker and captured a numeric PID.
worker_pid="$(echo "${output}" | sed -n 's/^Started background worker: sleep 300 (PID \([0-9][0-9]*\)).*/\1/p' | head -n 1)"
if [ -n "${worker_pid}" ]; then
check "spawned its own background worker and captured its PID" "yes"
else
check "spawned its own background worker and captured its PID" "no"
fi
echo "${output}" | grep -q 'sleep 300' && check "found the worker with ps/jobs (sleep 300 visible)" "yes" || check "found the worker with ps/jobs (sleep 300 visible)" "no"
# Polite termination: exit status 143 = 128 + 15 (SIGTERM).
echo "${output}" | grep -q 'wait reported exit status 143' && check "worker died to SIGTERM (exit status 143)" "yes" || check "worker died to SIGTERM (exit status 143)" "no"
echo "${output}" | grep -Eq '^Verified: PID [0-9]+ is gone$' && check "script verified its worker is gone" "yes" || check "script verified its worker is gone" "no"
# Independent verification: the worker PID must no longer exist. (We only
# *read* the process table here — the test signals nothing itself.)
if [ -n "${worker_pid}" ]; then
if ps -p "${worker_pid}" > /dev/null 2>&1; then
check "worker PID ${worker_pid} really is gone (no orphaned sleep)" "no"
else
check "worker PID ${worker_pid} really is gone (no orphaned sleep)" "yes"
fi
fi
fi
}
run_playground_checks "${lab_dir}/examples/process_playground.sh" strict
# The starter ships with 'unknown' values on purpose; once the learner has
# replaced them all, hold their script to the same strict standard.
if grep -q '"unknown"' "${lab_dir}/starter/process_playground.sh"; then
echo "Note: starter/process_playground.sh still has unfilled exercises — testing structure only."
run_playground_checks "${lab_dir}/starter/process_playground.sh" lenient
else
run_playground_checks "${lab_dir}/starter/process_playground.sh" strict
fi
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 007 lab
kill: (12345) - No such process
The PID is stale or mistyped. sleep 300 exits on its own after five
minutes, and every run gets fresh PIDs — never reuse a PID from an earlier
run or from the sample output. Start a new sleeper and capture $!
immediately: sleep 300 & worker_pid=$!.
wait: pid 12345 is not a child of this shell
wait only collects children of the shell you call it from. If you started
the sleeper in one terminal and typed wait in another (or inside a script,
which is its own shell), it is not your child there. Run the whole
lifecycle — spawn, observe, kill, wait — in one shell, start to finish.
jobs prints nothing even though my sleep is running
jobs is per-shell: it lists only the background jobs of the shell you
type it in. Your sleeper still exists — find it system-wide with
ps -p <PID>. Same rule as above: one terminal for the whole exercise.
Orphaned sleeps: I started sleepers and lost track of them
It happens — a closed terminal or a script that died before its cleanup.
First, look: pgrep -l sleep lists every sleep process you can see,
with PIDs. Identify the ones you started (on a shared machine, other
users and system tools may legitimately run sleeps — check ownership with
ps -o pid,user,command -p <PID>), then terminate only those, politely:
kill <PID>. An orphaned sleep 300 is harmless — it uses no CPU and
exits by itself within five minutes — so when in doubt, just wait it out.
Both lab scripts set a trap ... EXIT so their own worker is taken down
even if the script fails partway; that pattern is worth stealing.
The starter script says "Exercise 3 not completed yet"
Working as designed: the spawn/kill/verify steps only run after you replace
worker_pid="unknown" with a real spawn (sleep 300 & then
worker_pid=$! on the next line, inside the script).
Exit status is 0, not 143
Your wait collected the wrong thing, or the sleeper finished naturally
before you killed it (five minutes pass quickly when reading). Rerun and
kill promptly. If you see 137 instead of 143, you sent SIGKILL (kill -9)
rather than SIGTERM — reread the lesson's signals table.
ps: illegal option or missing columns
Use the exact portable form ps -o pid,ppid,stat,command -p <PID>. Other
spellings (ps aux variants, --forest) differ between macOS and Linux.
My parent chain looks different from the sample
Expected. The sample was captured from a detached run, so its chain ends at
/sbin/launchd. Run from a normal terminal and you will see your shell,
your terminal app or editor, and eventually PID 1. That difference is the
lesson: every process has a parent, and the chain depends on who spawned
whom.
Windows: bash is not recognized
Use WSL (wsl --install, then open Ubuntu). Native PowerShell has its own
process cmdlets (Get-Process, Stop-Process), but this lab's scripts and
tests assume a Unix-style shell.
Security notes
Security notes — Day 007 lab
- The iron rule: never signal a process you did not start. Everything
in this lab signals only the one
sleepthe script itself spawned, whose PID it captured from$!at the moment of creation. Typingkillwith a PID you found lying around — in a tutorial, in old output, or by guessing — can terminate someone else's work or a system service. Before any manualkill, confirm withps -o pid,user,command -p <PID>that the process is yours and is the one you think it is. - What the scripts do: read the process table (
ps), spawn onesleep 300, send it SIGTERM, and verify it exited. They make no network connections, write no files, and change no settings. The test suite only reads the process table; it never signals anything itself. - Privileges: everything runs as your normal user, and that is itself a
safety net — the kernel refuses to let you signal processes owned by
other users. Nothing here needs
sudo, and runningkillwithsudoremoves that safety net; don't. kill -9is a last resort, not a habit. SIGKILL gives the target no chance to clean up — half-written files and stale locks are the typical wreckage. The lab teaches the professional order: SIGTERM, wait, then escalate only if truly necessary — and here the only target is ever asleepyou started seconds earlier.- PID reuse: PIDs are recycled by the kernel. A PID you wrote down
minutes ago may now belong to a different process — one more reason to
capture
$!immediately and act on it promptly, never on stale numbers. - Reading before running: both scripts are short and commented — read them before executing, as with every lab in this course. A script that sends signals deserves an especially careful read.