Pipes, Signals, and the Environment Boundary
POSIX IPC in miniature: one-way pipes between parent and child, signal handlers that set flags, and env vars as inherited process state.
A pipe is a pair of fds
pipe(int fds[2]) (POSIX) gives a one-way channel: write to fds[1], read from fds[0]. The classic pattern:
pipe()beforefork()โ both processes inherit both ends.- Child:
dup2(fds[1], STDOUT_FILENO), exec โ its stdout flows into the pipe. - Parent: close the write end (else
readnever returns 0), read until EOF, thenwaitpid.
Forgetting the parent-side close is the classic bug: the reader blocks forever because a write end is still open somewhere.
Signals: the async flag discipline
A handler runs between arbitrary two instructions of your program. The contract: the handler touches only volatile sig_atomic_t flags and the async-signal-safe functions. Everything else โ allocation, stdio, locks โ is off-limits. The production shape is always the same:
static volatile sig_atomic_t shutdown_requested = 0;
static void on_term(int sig) { (void)sig; shutdown_requested = 1; }
/* main loop: while (!shutdown_requested) { ... } */
The environment is inherited state
getenv (ISO C) reads the process environment each child inherits from its parent. That is the whole mechanism behind PATH, HOME, and every 12-factor app config: environment is per-process state, copied at fork and passed through exec.