Source: Operating Systems: Three Easy Pieces (OSTEP) — Chapter 5 (Interlude), Arpaci-Dusseau & Arpaci-Dusseau
Interlude: Process API
Interludes focus on practical OS APIs — how real systems let you create and control processes. Skip them only if you dislike useful things (employers usually do not).
Crux: what interfaces should the OS present for process creation and control — powerful, usable, and fast?
UNIX’s answer is an odd but brilliant trio: fork(), exec(), and wait().
fork() — create a near-copy
fork() creates a new process that is an almost exact copy of the caller:
- Own address space, registers, PC, …
- Does not restart at
main— both parent and child return fromfork() - Parent return value: child’s PID
- Child return value: 0
- On failure: negative (check and exit)
int rc = fork();
if (rc < 0) { /* fail */ }
else if (rc == 0) { /* child */ }
else { /* parent: rc is child pid */ }
Output order of parent vs child is nondeterministic — the scheduler decides who runs first.
wait() — parent blocks until child finishes
wait() (or waitpid()) lets a parent delay until a child exits. Then:
- Child always effectively “prints first” (if parent waits before its own print)
wait()returns the child’s PID (when it reaps that child)
Without wait(), a short-lived parent can exit while the child still runs; with it, the shell can show the next prompt only after the command completes.
exec() — replace the program image
exec() (e.g. execvp) does not create a new process. It transforms the current one:
- Load new code + static data from an executable
- Re-init heap / stack / etc.
- Jump into the new program with
argv
A successful exec never returns. Code after execvp(...) is only reached if exec failed.
Rank the sequence
1 / 1Order the steps exec() performs to transform the calling process (top = first).
OrderDrag the handle to rearrange — top should be first.
- 1Jump into the new program, passing argv
- 2Re-initialize the heap, stack, and other regions
- 3Load new code and static data from the executable
Drag items into the correct order (or use ↑↓), then check.
Why separate fork and exec?
A UNIX shell is just a user program: prompt → parse → fork → (optional setup) → exec → wait → prompt again.
The gap between fork and exec is where the shell implements:
| Feature | Trick in the child before exec |
|---|---|
wc file > out.txt | close(STDOUT_FILENO); open("out.txt", …) — next free fd is usually 1 |
Pipes a | b | pipe(); connect one child’s stdout to the other’s stdin |
| Env / cwd tweaks | Change environment without changing the target program |
Many process-creation APIs are possible. Separating fork and exec is simple and extremely powerful — the UNIX designers got it right.
Rank the sequence
1 / 1Order the steps a UNIX shell performs to run one command (top = first).
OrderDrag the handle to rearrange — top should be first.
- 1wait for the child to finish in the parent
- 2exec the requested program in the child
- 3fork a child process
- 4parse the command line typed at the prompt
Drag items into the correct order (or use ↑↓), then check.
Interactive: fork → wait → exec → redirect
Step through the same story as OSTEP’s p1–p4 examples.
Try:
- fork() — two return values; hello printed once; race on print order
- fork + wait — parent blocks → deterministic child-then-parent
- fork + exec — child becomes
wc; post-execcode is dead - I/O redirect — rearrange fds so
wcwrites to a file
Check your understanding
fork, exec, wait
1 / 4After a successful fork(), the child…
Pick an answer to see if you are right.
Coding challenges
Piston cannot really fork/exec — these C drills classify return values the way your local homework will. Real unistd.h calls stay in the lab below.
Homework lab: Process API (C on your machine)
Write real programs with fork / exec / wait / pipe on a UNIX-like system (Linux, macOS, WSL). Use notes and screenshots below — saved in this browser (IndexedDB).
Reference: OSTEP cpu-api homework.
Homework: Process API (code)
0/8 startedProcess control, users, tools
Beyond create/run/wait:
| Tool / API | Role |
|---|---|
Signals (kill, Ctrl-C → SIGINT, Ctrl-Z → SIGTSTP) | Pause, resume, terminate processes |
| Users | Usually you may only signal your processes |
| Superuser (root) | Can control any process — use rarely and carefully |
ps / top / kill | Inspect load; send signals from the shell |
man 2 fork, man 2 execve, man 2 wait, and your shell’s man page beat random blog posts. Colleagues who say “RTFM” mean: read the man pages.
Summary
| Term | Meaning |
|---|---|
| PID | Numeric name of a process |
fork() | Create child ≈ copy of parent; return values differ |
wait() / waitpid() | Parent waits to reap a child |
exec() family | Replace current program image |
| Shell | fork → setup (redirect/pipe) → exec → wait |
| Signals / users / root | Who may control which processes |
Next: Limited Direct Execution — traps, timers, and context switches.
Further Reading
- Remzi & Andrea Arpaci-Dusseau — Operating Systems: Three Easy Pieces Ch. 5
- W. Richard Stevens & Stephen A. Rago — Advanced Programming in the UNIX Environment (process control chapters)
- OSTEP homework code: cpu-api