Reading — step 1 of 5
Read
fork() and exec()
Unix creates processes with a design so strange that people refuse to believe it at first: there is no "run a program" call. There's fork() — clone me — and exec() — replace me — and running a program means composing the two. Every shell ever written is a loop around this pair, and understanding why the split is understanding Unix.
The two halves
fork() clones the calling process — address space, open files, working directory, everything (copy-on-write making it cheap — the OS course's paging lesson, if you've taken it, explains how a "full copy" costs almost nothing). It returns twice: the parent receives the child's PID; the child receives 0. One call in, two processes out, distinguishable by return value:
pid_t pid = fork();
if (pid == 0) {
/* CHILD — from here, two histories diverge */
} else {
/* PARENT — pid tells us who our child is */
}
exec() (the execvp variant, in practice) replaces the current process with a new program: same PID, same open file descriptors — but the code, data, and stack are now the new program's. On success, exec never returns (there's nothing to return to — that code is gone). Any line after exec only runs if exec failed: command not found, not executable. That's where "command not found: xyz" gets printed — by the doomed child, just before it exits.
wait() completes the trio: the parent blocks until the child finishes and collects its exit status — the number that becomes $?, feeds &&/||, and (Refs to the git course) the entire conditional-execution model you've been using all along.
The shell's heartbeat
loop:
read line → tokenize → parse → expand (chapters 0–1: done!)
if built-in: run in-process (the cd lesson's rule)
else:
pid = fork()
child: apply redirections; execvp(words[0], words)
parent: wait(pid) → record status
That's a functioning shell. Genuinely — everything else in this course is refinement of this ten-line loop.
Why the weird split is genius
A combined "spawn(program, args, redirects, env, …)" call — what Windows' CreateProcess is — needs a parameter for every property the child might want different. The fork/exec split needs none of it, because of the gap between the calls: for one magic moment, the child is a running copy of the shell, able to run arbitrary setup code on itself — open files onto its own stdin/stdout (redirections!), rewire pipes, drop privileges, change directory — and then exec. The child configures itself using ordinary code, and the new program wakes up in a world already arranged for it. Next lesson's redirections and pipelines are just "code the child runs in the gap" — the design's whole payoff.
Your exercise: Process Tree Simulation
Given a trace of fork/exec/exit/wait events, track the process tree and statuses — the same bookkeeping the kernel does, at exercise scale (and the same tree the OS course's process lesson builds, if you've met it: parents, children, orphans, zombies-until-waited). The classic traps are the fork return values (0 = you're the child; nonzero = you're the parent and that's your kid's PID — mixing these up inverts the whole tree) and remembering exec preserves the PID: a process that execs is the same node wearing a new program, not a new child. Get the simulation solid and the real syscalls, when you meet them, will feel like an implementation detail.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…