Linux System Programming · intermediate · ~10 min
Collect a child's exit status.
wait(&status) and waitpid(pid, &status, opts) pause the parent process until one of its child processes finishes. They return the child's PID and its exit status.
If the parent never calls wait, finished children stick around as zombies. A zombie is an entry in the kernel's process table that holds no memory of its own. It keeps only the child's exit code, waiting for the parent to collect it.
In a program that uses fork, the parent must collect each child's exit code. If it does not, zombies build up.
This matters most for long-running servers. Imagine a server that spawns hundreds of subprocesses an hour. Each missed wait leaves one more zombie behind.
Over time these entries fill the kernel's process table. Once it is full, the system can no longer create new processes, and fork starts to fail.
The status value packs several pieces of information into one integer. You read it with macros:
WIFEXITED(status) is true if the child exited normally.WEXITSTATUS(status) extracts the exit code (only valid after WIFEXITED).WIFSIGNALED(status) is true if a signal killed the child.WTERMSIG(status) gives the signal number that killed it.WNOHANG makes the call non-blocking. If no child has exited yet, waitpid returns 0 immediately instead of waiting.When a child exits, the kernel sends the parent a SIGCHLD signal. You can install a handler for it that calls waitpid in a loop, reaping every child that has finished.
int status;
pid_t kid = waitpid(child, &status, 0);
if (WIFEXITED(status)) printf("exited with %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status)) printf("killed by signal %d\n", WTERMSIG(status));
When a child exits, the kernel keeps a small record of it (the zombie) until the parent calls wait or waitpid.
The signature is:
int waitpid(pid_t pid, int *status, int options);
It blocks until the chosen child terminates, or polls without blocking when you pass WNOHANG.
Decode the returned status like this:
WIFEXITED then WEXITSTATUS.WIFSIGNALED then WTERMSIG.int st;
waitpid(pid, &st, 0);
if (WIFEXITED(st)) printf("exit %d\n", WEXITSTATUS(st));
if (WIFSIGNALED(st)) printf("killed by signal %d\n", WTERMSIG(st));
WEXITSTATUS without checking WIFEXITED first. The exit code lives in a specific bit field of status, so the value is only meaningful once WIFEXITED confirms a normal exit.Run ps -ef | grep defunct to list zombies. Each one is a child you forgot to wait on.
Modern Linux also offers prctl(PR_SET_CHILD_SUBREAPER, 1). This lets a parent claim its grandchildren, which is useful in service managers.
wait is about resource hygiene, not memory.
The kernel keeps a small struct for each child that has not yet been reaped. Long-running servers must reap these children to avoid piling them up.
Two common approaches:
wait / waitpid directly.SIGCHLD handler that calls waitpid in a loop.cron.init / systemd, which act as the system's process reaper._exit(7). Have the parent print the exit code using WEXITSTATUS.waitpid for each one.SIGCHLD handler that calls waitpid(-1, &st, WNOHANG) in a loop.wait and waitpid collect a finished child's exit status and clear it from the process table.fork with a wait (or a SIGCHLD handler) to avoid zombies.WIFEXITED before reading WEXITSTATUS.