Module 4: Systems-Level Programming: Case Studies
These case studies make the kernel boundary visible: syscalls, file descriptors, pipes, signals, mmap, threads, sockets, and tracing.
Case Study 1: strace Shows The Real Program
Scenario: A C program "hangs" while reading input. The source looks fine. strace shows it blocked on read(0, ...).
Source anchor: strace(1) documents the Linux system-call tracer and the syscall-level evidence it exposes.
Module concepts: syscall, blocking I/O, tracing, kernel boundary.
Wrong Approach
Debug only source code.
Better Approach
Trace syscalls:
openat(...)
read(0, ...)
write(1, ...)
close(...)
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Read source only | Familiar workflow | Misses kernel-boundary behavior |
Trace syscalls with strace | Immediate blocking and I/O evidence | Linux-specific tool and noisy output |
| Add ad hoc print debugging | Easy to start | Can hide timing or ordering issues |
Failure Mode
The program is blamed for a logic bug when it is actually blocked in a kernel call waiting on input or another file descriptor event.
Required Artifact
Run or mock strace output and annotate five syscalls.
Project / Capstone Connection
Use syscall traces as evidence in later debugging reports for shells, servers, and IPC exercises.
Case Study 2: Shell Pipeline With pipe, fork, dup2, exec
Scenario: Implement cat file | grep error. The learner forks two children but forgets to close unused pipe ends, so grep waits forever.
Source anchor: Linux man pages for pipe(2), dup(2), and related process calls define the descriptor semantics that make shell pipelines work.
Module concepts: pipe, fork, dup2, exec, fd inheritance.
Wrong Approach
Only connect the happy-path descriptors.
Better Approach
Close every unused end in parent and children:
child 1:
stdout -> pipe write
close read end
child 2:
stdin -> pipe read
close write end
parent:
close both pipe ends
wait children
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
Minimal pipe/dup2 wiring | Small initial code | Easy to leak descriptors and hang readers |
| Explicit close plan per process | Correct EOF behavior | More bookkeeping |
| Helper abstraction for pipeline stages | Reusable process setup | More indirection to debug |
Failure Mode
One extra write end remains open in the parent or sibling process, so the reader never sees EOF and appears stuck.
Required Artifact
Draw fd tables for parent and both children before and after dup2.
Project / Capstone Connection
Use this descriptor accounting model when building shells, supervisors, or command runners later on.
Case Study 3: Signal Handler Does Too Much
Scenario: A SIGINT handler calls printf, malloc, and complex cleanup. The program occasionally deadlocks or corrupts state.
Source anchor: Linux signal-safety(7) documents which functions are async-signal-safe.
Module concepts: signal, async-signal-safety, reentrancy, cleanup flag.
Wrong Approach
Run normal application logic inside the signal handler.
Better Approach
Set a flag or write to a pipe:
volatile sig_atomic_t stop = 0;
void handler(int sig) {
stop = 1;
}
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Full cleanup in handler | Seems direct | Reentrancy and deadlock risk |
| Set flag and defer cleanup | Safe control transfer | Main loop must cooperate |
| Self-pipe/eventfd pattern | Integrates with event loop | Extra fd setup |
Failure Mode
The handler interrupts code already inside libc or allocator internals, then reenters unsafe functions and deadlocks or corrupts process state.
Required Artifact
Write a signal-safety review: handler actions, safe functions, deferred cleanup.
Project / Capstone Connection
Carry this pattern into daemons, shells, and service processes that need clean interrupt handling.
Case Study 4: mmap File Update Surprise
Scenario: A file is memory-mapped and modified. The learner assumes every store is immediately durable and visible in all desired ways.
Source anchor: Linux mmap(2) and msync(2) document mapping and synchronization behavior.
Module concepts: mmap, shared/private mapping, page cache, msync.
Wrong Approach
"Memory assignment equals disk write."
Better Approach
Choose mapping and sync semantics:
MAP_SHARED:
changes can propagate to file
MAP_PRIVATE:
copy-on-write private changes
durability:
msync/fsync as required
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Assume stores are durable | Simplifies mental model | Incorrect crash and visibility assumptions |
MAP_SHARED + explicit sync plan | Shared-file semantics are clear | Must handle flushing and ordering |
MAP_PRIVATE for local transforms | Isolates mutations | Changes do not update the file |
Failure Mode
The program appears to update the file in memory, but after a crash or another reader's observation the expected durability or sharing semantics are not there.
Required Artifact
Write an mmap behavior table for MAP_SHARED vs MAP_PRIVATE and crash points.
Project / Capstone Connection
Use this decision model in file-backed indexes, log viewers, and memory-mapped tooling later in the program.
Case Study 5: Thread Lifecycle Leak
Scenario: A server creates pthreads for background work but never joins or detaches them. Resource usage grows.
Source anchor: POSIX pthread lifecycle docs explain joinable and detached threads. See pthread_create(3) and pthread_join(3).
Module concepts: pthread_create, join, detach, lifecycle.
Wrong Approach
"When the thread function returns, everything is cleaned up."
Better Approach
Decide lifecycle:
joinable:
parent must join and collect result
detached:
no join, resources released automatically
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Leave lifecycle implicit | Less code at creation site | Resource leaks and unclear shutdown |
| Joinable threads with explicit join | Collect results and errors | Requires shutdown coordination |
| Detached fire-and-forget threads | Simpler cleanup path | Harder observability and control |
Failure Mode
Finished threads accumulate unreclaimed resources, and long-running servers slowly exhaust limits or become harder to shut down cleanly.
Required Artifact
Write a thread lifecycle plan with join/detach decision and shutdown path.
Project / Capstone Connection
Apply this lifecycle policy to worker pools, background tasks, and concurrency features in later systems projects.
Source Map
| Source | Use it for |
|---|---|
| strace(1) | syscall tracing |
| pipe(2) and dup(2) | shell pipeline mechanics |
| signal-safety(7) | safe signal handlers |
| mmap(2) and msync(2) | memory-mapped files |
| pthread_create(3) | thread lifecycle |
Completion Standard
- At least three artifacts are completed.
- At least one artifact includes fd tables.
- At least one artifact includes syscall trace evidence.