How to Use wait and waitpid in C to Reap Child Processes
When a child process terminates in a Unix-like operating system, it
enters a "zombie" state until its parent process reads its exit status
and releases its entry from the operating system's process table. This
article explains how to reap terminated child processes in the C
programming language using the wait and
waitpid system calls, details the key differences between
both functions, demonstrates how to inspect exit statuses using standard
macros, and outlines best practices for handling asynchronous process
termination.
Why Child Processes Must Be Reaped
When a child process created by fork() finishes
executing via exit() or by returning from
main(), the kernel deallocates its memory and closed file
descriptors. However, the process table entry—which retains the Process
ID (PID), termination status, and resource usage statistics—remains
active. If the parent does not explicitly collect this information, the
child becomes a zombie process (<defunct>).
Accumulating too many zombies exhausts available PIDs, preventing the
operating system from launching new processes.
Using the wait System
Call
The wait() function suspends the calling parent process
until any one of its child processes terminates.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);- Parameter:
wstatusis a pointer to an integer where the kernel writes the child's status information. If this information is not needed,NULLcan be passed. - Return Value: On success, it returns the PID of the
terminated child. On failure (such as when the process has no children),
it returns
-1and setserrno.
Basic wait Example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
}
if (pid == 0) {
// Child process
printf("Child process (PID: %d) running...\n", getpid());
sleep(2);
exit(42);
} else {
// Parent process
int status;
pid_t child_pid = wait(&status);
printf("Parent reaped child (PID: %d)\n", child_pid);
if (WIFEXITED(status)) {
printf("Child exited with code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}Using the waitpid
System Call
The waitpid() function provides fine-grained control
over process reaping. Unlike wait(), it can wait for a
specific process, operate non-blockingly, and monitor stopped or resumed
children.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);Parameters
pid:< -1: Wait for any child process whose process group ID equals the absolute value ofpid.-1: Wait for any child process (identical towait()).0: Wait for any child process whose process group ID matches the parent.> 0: Wait for the specific child process whose PID matches this value.
wstatus: Pointer to an integer where status information is stored.options: Bitwise OR flags modifying behavior:WNOHANG: Return immediately if no child has terminated (non-blocking).WUNTRACED: Also return for children that have stopped (e.g., viaSIGSTOP).WCONTINUED: Also return for children resumed bySIGCONT.
Non-Blocking Reaping Example
Using WNOHANG allows the parent to execute other tasks
while periodically polling for terminated children:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
sleep(1);
exit(0);
}
int status;
pid_t result;
while ((result = waitpid(pid, &status, WNOHANG)) == 0) {
printf("Child still running. Parent performing work...\n");
usleep(300000); // Sleep for 300ms
}
if (result > 0) {
printf("Successfully reaped child PID: %d\n", result);
} else {
perror("waitpid error");
}
return 0;
}Inspecting Child Termination Status
The integer populated in wstatus is an encoded bitfield.
Standard POSIX macros are used to evaluate how the process
terminated:
WIFEXITED(status): Returns true if the child terminated normally (viaexit()or returning frommain).WEXITSTATUS(status): Evaluates to the exit code passed toexit()or returned frommain(only valid ifWIFEXITEDis true).WIFSIGNALED(status): Returns true if the child was terminated by an unhandled signal (such asSIGKILLorSIGSEGV).WTERMSIG(status): Evaluates to the signal number that caused the termination (only valid ifWIFSIGNALEDis true).WIFSTOPPED(status): Returns true if the child process was stopped by a signal.
Asynchronous Reaping with
SIGCHLD
When a child terminates, the kernel sends a SIGCHLD
signal to the parent. Handling this signal allows a parent to reap
children asynchronously without blocking main execution threads.
Because standard Unix signals do not queue, multiple children
terminating simultaneously may only trigger a single
SIGCHLD. Therefore, the signal handler must invoke
waitpid in a loop with WNOHANG:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void sigchld_handler(int sig) {
(void)sig;
int status;
pid_t pid;
// Reap all terminated children currently available
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("Reaped child PID: %d\n", pid);
}
}
int main() {
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction failed");
exit(1);
}
// Fork child processes
for (int i = 0; i < 3; i++) {
if (fork() == 0) {
sleep(1 + i);
exit(0);
}
}
// Simulate main process work
while (1) {
sleep(5);
}
return 0;
}This pattern ensures that every terminating child is promptly reaped without stalling parent execution or leaking zombie entries in the system process table.