Part 5 · 1 chapters · ~8 min
Processes and Signals
Process creation with fork and exec, copy-on-write, waitpid, exit statuses and zombies, environment and arguments, signals and handlers, async-signal safety, the self-pipe trick, SIGTERM and graceful shutdown, process groups and job control, and posix_spawn.
6
fork, exec, wait
code
pid_t pid = fork();
if (pid < 0) { perror("fork"); exit(1); }
if (pid == 0) { execvp(argv[0], argv); perror("execvp"); _exit(127); } // only returns on failure
int status; waitpid(pid, &status, 0);
if (WIFEXITED(status)) printf("exit %d\n", WEXITSTATUS(status));
// signals: a handler may only call async-signal-safe functions (no printf, no malloc)
static volatile sig_atomic_t stop = 0;
static void on_term(int sig) { (void)sig; stop = 1; }
struct sigaction sa = { .sa_handler = on_term }; sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, NULL);
while (!stop) serve_one(); // main loop checks the flag; drain and exit cleanlyposix_spawn creates and execs in one call and avoids copying page tables for large parents; runtimes such as Node's libuv and Go use spawn-style paths when starting children. On the same machine, the OS course measured Node's spawnSync('/usr/bin/true') at 1.53 ms per process, against 486 µs for a bare fork, exit and wait in C: the difference is exec, loading a program, and Node's own bookkeeping.
HOW A SHELL RUNS A COMMAND
fork, exec, wait, and a signal
swipe the figure sideways, or tap expand for full screen
1/4
fork
fork creates a near-identical child process; memory is shared copy-on-write, so the copy is cheap until either side writes. Measured here: fork, exit and wait took 486 µs each.
clone the process486 µs measured, COW