The full picture
pid_t pid = fork(); // clone: page tables copied, pages COW-shared
if (pid == 0) { // --- child ---
dup2(logfd, STDOUT_FILENO); // redirect stdout BEFORE exec (the gap!)
execvp("sort", argv); // replace image; pid and FDs survive
_exit(127); // only reached if exec failed
} else { // --- parent ---
waitpid(pid, &status, 0); // reap; else the child becomes a zombie
}- COW mechanics: shared pages marked read-only → a write traps → kernel copies that page, remaps, resumes. Fork cost becomes proportional to page-table size, not memory size.
- The big-process fork problem: fork from a 30GB JVM briefly needs to copy huge page tables and risks memory spikes if the child dirties pages — why Redis's fork-based snapshots need headroom, and why
posix_spawn/vforkexist for fork-then-exec-immediately cases (Java'sProcessBuilderuses posix_spawn by default on Linux). - Zombies & orphans: exited-but-unreaped children keep their PCB (zombie) until the parent
wait()s; orphans get re-parented to init. A zombie leak is a missing-wait bug, not a memory leak. FD_CLOEXECmatters: FDs survive exec by default — leaking a listening socket into a child is a classic 'port already in use after restart' mystery.