Chapter 59: Waiting for Child Processes: wait()
and waitpid()
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the complete lifecycle of a Linux process, including termination, and the significance of parent-child relationships.
- Explain the origin and system impact of “zombie” processes and why they must be managed.
- Implement the
wait()
system call to synchronize a parent process with the termination of a child process. - Utilize the
waitpid()
system call for more flexible synchronization, including waiting for specific children and non-blocking status checks. - Analyze the exit status of a terminated child process to determine if it exited normally or was terminated by a signal.
- Develop robust multi-process applications on an embedded Linux system like the Raspberry Pi 5, ensuring proper resource cleanup.
Introduction
In the world of embedded Linux, the ability to create and manage multiple processes is a cornerstone of building responsive and efficient systems. Whether it’s a web server handling concurrent requests on a network-connected device, a robotics controller managing separate motors and sensors, or a data logger spawning tasks to process incoming information, multiprocessing is fundamental. However, simply creating child processes with fork()
is only half the story. A parent process that creates children incurs a fundamental responsibility: it must wait for them to complete their work and clean up the resources they leave behind. This act of waiting, or “reaping,” is not merely good practice; it is essential for long-term system stability.
This chapter delves into the critical mechanism of process synchronization between a parent and its children. We will explore what happens when a child process terminates and why the operating system depends on the parent to acknowledge this event. Failure to do so leads to an accumulation of “zombie” processes—the lingering ghosts of terminated children that consume kernel resources and can, over time, destabilize the entire system. You will learn how the wait()
and waitpid()
system calls provide the necessary tools for a parent to pause its own execution, wait for a child to finish, and retrieve information about its termination. By mastering these calls, you will gain the ability to write robust, multi-process embedded applications that manage system resources correctly and avoid critical stability issues. The concepts discussed here are foundational for building anything from simple task managers to complex, fault-tolerant embedded systems on your Raspberry Pi 5.
Technical Background
The Process Lifecycle and Termination: A Family Affair
In Linux, every process is part of a family tree. With the exception of the initial init
process (PID 1), every process is created by another process, its parent. The new process is, naturally, the child. This parent-child relationship, established by the fork()
system call, is the primary mechanism for creating new execution contexts. The parent-child bond is maintained by the kernel, which tracks the Parent Process ID (PPID) for every process. This relationship persists for the entire life of the child and even slightly beyond its death, a crucial detail that underpins the need for synchronization.
When a process concludes its execution, it does so in one of two ways: normal termination or abnormal termination. Normal termination occurs when the process has completed its task successfully. It can trigger this by calling the exit()
library function, which wraps the _exit()
system call, or by returning from its main()
function. In either case, the process provides an integer exit code, a value between 0 and 255, to signal its final status. By convention, an exit code of 0 signifies success, while a non-zero value indicates some form of error or specific outcome.
Abnormal termination, on the other hand, happens when the process is forced to stop due to an unhandled signal. This could be a SIGSEGV
(segmentation fault) from an invalid memory access, a SIGKILL
sent by another process, or any other signal whose default action is to terminate.
Regardless of how it terminates, the process does not simply vanish. The kernel must retain a small amount of information about the now-defunct process for the parent to inspect. This information includes the process ID (PID), its exit status (whether it exited normally or was killed by a signal), and some resource usage statistics. The process is now in a peculiar state of limbo—it is dead and no longer executing, but its entry in the kernel’s process table cannot be fully removed. This is the state of a zombie process.
The Problem of Zombie Processes
A zombie process, also known as a defunct process, is a terminated process that has not yet been “reaped” by its parent. It holds no memory, no open files, and consumes no CPU time. It is little more than a single entry in the process table containing the child’s exit status. The operating system keeps this entry specifically so the parent process has an opportunity to find out what happened to its child. Did it succeed? Did it crash? The parent can only get these answers by explicitly waiting for the child.
graph TD subgraph Parent Process P1(Parent Runs) P2("Calls <b>wait()</b> / <b>waitpid()</b>") P3(Parent Resumes) end subgraph Child Process C1(Child is Forked) C2(Child Runs) C3("Child calls <b>exit()</b>") end subgraph Kernel Space Z["<font size=5>🧟</font><br><b>ZOMBIE STATE</b><br><i>(Process Entry Exists,<br>No Execution)</i>"] R(Process Entry Removed) end C1 -- "fork()" --> C2; P1 -- "fork()" --> C2; C2 -- "Completes task" --> C3; C3 -- "Terminates" --> Z; P1 -- "Continues execution" --> P2; Z -- "Parent is waiting" --> P2; P2 -- "Reaps child & gets status" --> P3; P2 -- "Frees entry in process table" --> R; classDef primary fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff; classDef process fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; classDef endo fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff; classDef system fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff; classDef warning fill:#eab308,stroke:#eab308,stroke-width:1px,color:#1f2937; class P1,P2,P3,C1,C2,C3 process; class Z warning; class R endo;
The existence of a zombie is not, in itself, an error. It is a normal, transient part of the process lifecycle. The problem arises when the parent process fails to perform its duty. If a parent creates children but never calls wait()
or waitpid()
, its zombie children will accumulate. Each zombie holds a PID and a slot in the process table. Since the number of PIDs is finite, a system that continuously creates zombies without reaping them will eventually run out of PIDs, preventing any new processes from being created. This is a slow but certain way to cripple a system, making proper reaping a non-negotiable aspect of robust system programming.
What if the parent terminates before its child? In this scenario, the child becomes an orphan process. Linux cannot leave a process without a parent, so any orphan processes are immediately “adopted” by the init
process (or a sub-reaper process in modern systems). The init
process has a built-in, perpetual loop that calls wait()
and reaps any adopted children, ensuring that no orphan is left to become a permanent zombie. This is a fail-safe, but relying on it is poor design. A well-written program must manage its own children.
The wait()
System Call: The Simplest Form of Reaping
The most fundamental tool for reaping a child is the wait()
system call. Its purpose is straightforward: to suspend the execution of the calling (parent) process until one of its child processes terminates.
The function prototype, found in <sys/wait.h>
, is:
pid_t wait(int *wstatus);
sequenceDiagram actor Parent participant Kernel actor Child Parent->>Kernel: fork() activate Kernel Kernel-->>Child: Create Process deactivate Kernel Child->>Child: Run task... Parent->>Kernel: wait(&wstatus) note right of Parent: Parent execution is BLOCKED Child->>Kernel: exit(status) activate Kernel note left of Kernel: Child becomes Zombie Kernel-->>Parent: Wake up, return child_pid deactivate Kernel note right of Parent: Parent resumes, <br>analyzes wstatus
When a parent process calls wait()
, one of two things happens immediately:
- If any of the parent’s children have already terminated and are zombies,
wait()
returns instantly. It “reaps” one of the zombie children, cleans up its process table entry, and returns the PID of the reaped child. - If there are no zombie children, but the parent has one or more children still running,
wait()
blocks. The parent process is put to sleep, consuming no CPU time, until one of its children terminates. As soon as a child terminates, the kernel wakes up the parent,wait()
reaps the new zombie, and returns its PID.
The single argument, wstatus
, is a pointer to an integer where the kernel will place the child’s exit status information. This is not simply the exit code. It’s a bitmask containing details about how the child terminated. If you don’t care about the exit status, you can pass NULL
for this argument. However, inspecting this status is usually the primary reason for waiting.
To make sense of the wstatus
integer, a set of macros is provided:
WIFEXITED(wstatus)
: Returns true if the child terminated normally (by callingexit()
or returning frommain()
).WEXITSTATUS(wstatus)
: IfWIFEXITED
is true, this macro returns the child’s actual 8-bit exit code.WIFSIGNALED(wstatus)
: Returns true if the child was terminated by a signal.WTERMSIG(wstatus)
: IfWIFSIGNALED
is true, this macro returns the number of the signal that terminated the child.
Using wait()
is like a parent telling the kernel, “I’m going to pause now. Please wake me up as soon as any of my children finish, and tell me which one it was and how it went.” It is simple and effective but lacks flexibility. You cannot choose which child to wait for, nor can you check on children without blocking. For that, we need a more powerful tool.
The waitpid()
System Call for Precise Control
The waitpid()
system call is the more versatile and commonly used function for process synchronization. It overcomes the limitations of wait()
by allowing the caller to specify which child to wait for and whether the call should block.
Its prototype is:
pid_t waitpid(pid_t pid, int *wstatus, int options);
Let’s break down its arguments, as they are the source of its power:
pid
: This argument specifies the set of child processes to wait for. Its interpretation is nuanced:pid > 0
: Wait for the specific child process whose PID is equal topid
. This is the most common use case for waiting on a particular child.pid == -1
: Wait for any child process. In this mode,waitpid(-1, &status, 0)
is functionally identical towait(&status)
.pid == 0
: Wait for any child process whose process group ID is the same as the calling process.pid < -1
: Wait for any child process in the process group whose ID is the absolute value ofpid
.
wstatus
: This is identical to thewstatus
argument inwait()
. It’s a pointer to an integer where the kernel stores the child’s termination status, which can be analyzed with the sameWIFEXITED
,WEXITSTATUS
, etc., macros.options
: This argument is a bitmask that modifies the behavior of the call. The most important option isWNOHANG
.- If
options
is 0,waitpid()
behaves likewait()
: it blocks until a relevant child terminates. - If
options
includes theWNOHANG
flag, the call becomes non-blocking. If a specified child has not yet terminated,waitpid()
will not block; instead, it will return 0 immediately. This allows a parent process to periodically “poll” the status of its children without having to suspend its own execution. This is incredibly useful in applications like shells or servers that must remain responsive to user input or network requests while managing background tasks.
- If
The return value of waitpid()
provides rich information:
- On success (when it reaps a child), it returns the PID of the terminated child.
- If
WNOHANG
was used and no child has terminated, it returns 0. - On error (e.g., the specified
pid
does not exist), it returns -1 and setserrno
.
flowchart TD Start("Start: waitpid(pid, wstatus, options)") subgraph "Parameter Evaluation" CheckPid{What is <b>pid</b>?} CheckOpts{Is <b>WNOHANG</b> set in <b>options</b>?} end subgraph "Execution Path" WaitAny[Wait for ANY child] WaitSpecific[Wait for specific child PID] WaitPgrp[Wait for any child in Process Group] Block(<b>BLOCKS</b><br>Parent execution is suspended) Poll{Any reaped child?} NonBlock(<b>NON-BLOCKING</b><br>Parent continues execution) ReturnPID(Return Child's PID) ReturnZero(Return 0) ReturnError(Return -1 and set errno) end Start --> CheckPid CheckPid -- "pid == -1" --> WaitAny CheckPid -- "pid > 0" --> WaitSpecific CheckPid -- "pid == 0 or pid < -1" --> WaitPgrp WaitAny --> CheckOpts WaitSpecific --> CheckOpts WaitPgrp --> CheckOpts CheckOpts -- "No (options == 0)" --> Block CheckOpts -- "Yes" --> NonBlock Block --> ReturnPID NonBlock --> Poll Poll -- "Yes" --> ReturnPID Poll -- "No" --> ReturnZero style Start fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff style ReturnPID fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff style ReturnZero fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff style ReturnError fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff style CheckPid fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff style CheckOpts fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff style Poll fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff style WaitAny fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; style WaitSpecific fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; style WaitPgrp fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; style Block fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; style NonBlock fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
In summary, waitpid()
provides the fine-grained control necessary for complex applications. It allows a parent to be selective about which child it waits for and to choose between blocking suspension and non-blocking polling. This flexibility is essential for building embedded systems that must juggle multiple concurrent tasks while maintaining responsiveness.
Practical Examples
These examples are designed to be compiled and run on a Raspberry Pi 5 running Raspberry Pi OS or a similar Linux distribution. You will need the gcc
compiler, which is typically pre-installed.
Example 1: Basic wait()
to Prevent a Zombie
This first example demonstrates the fundamental use of wait()
to reap a single child process. We will fork a child, have it do some trivial work and exit, and the parent will wait for it. We will also include code to inspect the child’s exit status.
File Structure and Code
Create a file named basic_wait.c
.
// basic_wait.c: Demonstrates the basic use of wait() to reap a child process.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
pid_t pid;
int wstatus;
printf("Parent (PID: %d): About to fork a child...\n", getpid());
// Fork a child process
pid = fork();
if (pid < 0) {
// Error handling for fork()
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// --- This is the child process ---
printf("Child (PID: %d): I am running. My parent is %d.\n", getpid(), getppid());
sleep(2); // Simulate doing some work
printf("Child (PID: %d): I am done. Exiting with status 42.\n", getpid());
exit(42); // Exit with a custom status code
} else {
// --- This is the parent process ---
printf("Parent (PID: %d): I have forked child with PID %d.\n", getpid(), pid);
printf("Parent (PID: %d): Now waiting for my child to terminate...\n", getpid());
// Wait for any child to terminate. The call will block here.
pid_t terminated_pid = wait(&wstatus);
if (terminated_pid < 0) {
perror("wait failed");
exit(EXIT_FAILURE);
}
printf("Parent (PID: %d): Child with PID %d has terminated.\n", getpid(), terminated_pid);
// Analyze the child's exit status
if (WIFEXITED(wstatus)) {
// Child exited normally
int exit_code = WEXITSTATUS(wstatus);
printf("Parent: Child exited normally with status code: %d\n", exit_code);
} else if (WIFSIGNALED(wstatus)) {
// Child was terminated by a signal
int term_signal = WTERMSIG(wstatus);
printf("Parent: Child was terminated by signal: %d\n", term_signal);
}
}
printf("Parent (PID: %d): All done. Exiting.\n", getpid());
return EXIT_SUCCESS;
}
Build and Run
- Compile the code:
gcc -o basic_wait basic_wait.c -Wall
The-Wall
flag enables all compiler warnings, which is a good practice. - Run the executable:
./basic_wait
Expected Output
The output will look something like this (PIDs will vary):
Parent (PID: 2345): About to fork a child...
Parent (PID: 2345): I have forked child with PID 2346.
Parent (PID: 2345): Now waiting for my child to terminate...
Child (PID: 2346): I am running. My parent is 2345.
Child (PID: 2346): I am done. Exiting with status 42.
Parent (PID: 2345): Child with PID 2346 has terminated.
Parent: Child exited normally with status code: 42
Parent (PID: 2345): All done. Exiting.
Explanation: The parent process forks, creating the child. The parent then immediately calls wait()
, which causes it to block. The child process runs for two seconds, prints its messages, and then exits with the status code 42. The moment the child terminates, the kernel wakes up the parent. The wait()
call completes, returning the child’s PID. The parent then proceeds to analyze the wstatus
variable, correctly determines that the child exited normally, and extracts the exit code 42.

Example 2: Non-Blocking Check with waitpid()
and WNOHANG
This example shows how a parent can continue doing its own work while periodically checking if its child has finished, without blocking.
File Structure and Code
Create a file named nonblocking_wait.c
.
// nonblocking_wait.c: Demonstrates a non-blocking wait using waitpid() and WNOHANG.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
pid_t child_pid;
int wstatus;
child_pid = fork();
if (child_pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
// --- Child Process ---
printf("Child (PID: %d): Starting work for 5 seconds.\n", getpid());
sleep(5);
printf("Child (PID: %d): Work done. Exiting.\n", getpid());
exit(0);
} else {
// --- Parent Process ---
printf("Parent (PID: %d): Created child %d. I will check on it without blocking.\n", getpid(), child_pid);
pid_t terminated_pid;
int loop_count = 0;
// Loop until the child is reaped
while ((terminated_pid = waitpid(child_pid, &wstatus, WNOHANG)) == 0) {
// waitpid returns 0 because WNOHANG is set and the child hasn't exited yet.
loop_count++;
printf("Parent: Child is still running. Doing other work (loop #%d).\n", loop_count);
sleep(1); // Simulate the parent doing other work
}
if (terminated_pid < 0) {
perror("waitpid failed");
exit(EXIT_FAILURE);
}
printf("\nParent: Reaped child with PID %d.\n", terminated_pid);
if (WIFEXITED(wstatus)) {
printf("Parent: Child exited normally with status: %d\n", WEXITSTATUS(wstatus));
}
}
return EXIT_SUCCESS;
}
Build and Run
- Compile the code:
gcc -o nonblocking_wait nonblocking_wait.c -Wall
- Run the executable:
./nonblocking_wait
Expected Output
Parent (PID: 2401): Created child 2402. I will check on it without blocking.
Child (PID: 2402): Starting work for 5 seconds.
Parent: Child is still running. Doing other work (loop #1).
Parent: Child is still running. Doing other work (loop #2).
Parent: Child is still running. Doing other work (loop #3).
Parent: Child is still running. Doing other work (loop #4).
Parent: Child is still running. Doing other work (loop #5).
Child (PID: 2402): Work done. Exiting.
Parent: Reaped child with PID 2402.
Parent: Child exited normally with status: 0
Explanation: The parent forks the child and immediately enters a while
loop. In each iteration, it calls waitpid()
with WNOHANG
. For the first five seconds, the child is still running, so waitpid()
returns 0. The parent prints a message and sleeps for one second, simulating useful work. Once the child exits after five seconds, the next call to waitpid()
succeeds, returns the child’s PID, and breaks the loop. The parent then reaps the child and reports its status. This pattern is essential for applications that cannot afford to be suspended.

Example 3: Managing Multiple Children
A more realistic scenario involves a parent that spawns multiple worker processes and must wait for all of them to complete.
File Structure and Code
Create a file named multi_wait.c
.
// multi_wait.c: A parent process forks multiple children and waits for all of them.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#define NUM_CHILDREN 3
int main() {
pid_t pids[NUM_CHILDREN];
int i;
int children_reaped = 0;
printf("Parent (PID: %d): Forking %d children...\n", getpid(), NUM_CHILDREN);
for (i = 0; i < NUM_CHILDREN; i++) {
pids[i] = fork();
if (pids[i] < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pids[i] == 0) {
// --- Child Process ---
// Each child sleeps for a different amount of time
srand(getpid()); // Seed random number generator
int sleep_time = (rand() % 5) + 1;
printf(" Child (PID: %d): I will work for %d seconds.\n", getpid(), sleep_time);
sleep(sleep_time);
printf(" Child (PID: %d): Done. Exiting.\n", getpid());
exit(i); // Exit with its creation index as status
}
}
// --- Parent Process ---
printf("Parent: All children are running. Now waiting for them to finish.\n");
while (children_reaped < NUM_CHILDREN) {
int wstatus;
// Wait for ANY child to terminate. This is a blocking call.
pid_t terminated_pid = wait(&wstatus);
if (terminated_pid > 0) {
children_reaped++;
printf("Parent: Reaped child %d (%d/%d). ", terminated_pid, children_reaped, NUM_CHILDREN);
if (WIFEXITED(wstatus)) {
printf("It exited with status %d.\n", WEXITSTATUS(wstatus));
} else {
printf("It terminated abnormally.\n");
}
} else {
// This should not happen in this simple case, but is good practice.
perror("wait failed");
}
}
printf("Parent: All children have been reaped. Exiting.\n");
return EXIT_SUCCESS;
}
Build and Run
- Compile the code:
gcc -o multi_wait multi_wait.c -Wall
- Run the executable:
./multi_wait
Expected Output
The order of child termination will vary due to the random sleep times.
Parent (PID: 2510): Forking 3 children...
Parent: All children are running. Now waiting for them to finish.
Child (PID: 2511): I will work for 3 seconds.
Child (PID: 2512): I will work for 1 seconds.
Child (PID: 2513): I will work for 5 seconds.
Child (PID: 2512): Done. Exiting.
Parent: Reaped child 2512 (1/3). It exited with status 1.
Child (PID: 2511): Done. Exiting.
Parent: Reaped child 2511 (2/3). It exited with status 0.
Child (PID: 2513): Done. Exiting.
Parent: Reaped child 2513 (3/3). It exited with status 2.
Parent: All children have been reaped. Exiting.
Explanation: The parent creates three children in a loop. It then enters a while
loop that continues until it has reaped all three. Inside the loop, it calls wait()
, which blocks until any child terminates. When wait()
returns, the parent increments its counter and prints which child was reaped and its exit status. This loop naturally handles children finishing in any order.
Common Mistakes & Troubleshooting
Exercises
- Analyze the Exit Status:
- Objective: Modify the
basic_wait.c
example to demonstrate the difference between normal and abnormal termination. - Steps:
- Create a copy of
basic_wait.c
. - In the child process, instead of calling
exit(42)
, cause an abnormal termination. A simple way is to perform integer division by zero:int x = 1 / 0;
. - Compile and run the program.
- Create a copy of
- Verification: Observe the parent’s output. It should now report that the child was terminated by a signal. Use
WTERMSIG
to print the signal number. What signal is it? (It should beSIGFPE
, floating-point exception).
- Objective: Modify the
- Zombie Apocalypse:
- Objective: Write a program that deliberately creates a zombie process and proves its existence.
- Steps:
- Write a C program where the parent forks a child.
- The child should print its PID and then
exit(0)
immediately. - The parent should print the child’s PID and then
sleep(30)
. The parent should not callwait()
. - Compile and run the program. While it’s running (during the 30-second sleep), open another terminal.
- Use the command
ps -elf | grep Z
to look for zombie processes.
- Verification: You should see your child process listed with a
<defunct>
status, indicating it is a zombie. After 30 seconds, the parent will exit, the zombie will be adopted byinit
and reaped, and it will disappear from the process list.
- Ordered Execution:
- Objective: Create three child processes that must execute a task in a specific, sequential order.
- Steps:
- The parent process should fork Child A.
- The parent should then
waitpid()
specifically for Child A to finish. - Only after Child A has been reaped, the parent should fork Child B.
- The parent should then
waitpid()
for Child B. - Finally, after reaping Child B, the parent should fork and wait for Child C.
- Each child should print a message indicating it is running.
- Verification: The output should strictly show “Child A running”, then “Child B running”, then “Child C running”, with no overlap. This demonstrates how
waitpid()
can enforce sequential execution.
- Simple Shell Command:
- Objective: Write a very basic shell that can execute a single command in the foreground.
- Steps:
- Write a program that prints a prompt (e.g.,
>
). - Use
fgets()
to read a line of input from the user. - The parent should
fork()
. - The child process should use
execlp()
to execute the command entered by the user. Remember to handleexeclp
failing. - The parent process should
wait()
for the child to complete before printing the prompt again.
- Write a program that prints a prompt (e.g.,
- Verification: Your program should be able to run simple commands like
ls -l
,pwd
, orecho hello
, and after each command finishes, it should return to your prompt.
- Background Task Manager:
- Objective: Extend the simple shell from Exercise 4 to handle background tasks.
- Steps:
- Modify your shell to check if the command line ends with an ampersand (
&
). - If it does not end with
&
, the parent shouldwait()
for the child (foreground process). - If it does end with
&
, the parent should notwait()
. It should immediately print the background process’s PID and return to the prompt. - Add a feature: before printing the prompt each time, call
waitpid(-1, &status, WNOHANG)
in a loop to reap any background jobs that might have finished.
- Modify your shell to check if the command line ends with an ampersand (
- Verification: You should be able to run
sleep 5 &
and immediately get your prompt back. If you then hit Enter a few times, after 5 seconds you should see a message indicating the background job has been reaped.
Summary
- Process Termination: Processes end either normally by calling
exit()
or abnormally via an unhandled signal. The kernel retains the exit status for the parent. - Zombie Processes: A terminated process whose parent has not yet called
wait()
orwaitpid()
is a zombie. It consumes a PID and a process table slot but no other resources. - Orphan Processes: A process whose parent terminates first is adopted by the
init
process, which automatically reaps it to prevent it from becoming a permanent zombie. wait()
System Call: Blocks the parent until any child process terminates. It returns the PID of the reaped child.waitpid()
System Call: A more flexible call that can wait for a specific PID, a process group, or any child.- Non-Blocking Waits: Using the
WNOHANG
option withwaitpid()
prevents the call from blocking, allowing a parent to poll the status of its children while performing other tasks. - Exit Status: The
wstatus
integer returned by the wait calls is a bitmask that must be interpreted using macros likeWIFEXITED()
,WEXITSTATUS()
,WIFSIGNALED()
, andWTERMSIG()
to understand how the child terminated. - Responsibility: Proper process management requires that a parent process reliably reaps every child it creates to ensure system stability and prevent resource leaks.
Further Reading
wait(2)
Linux Manual Page: The definitive reference for thewait
family of system calls. Access it on your system withman 2 wait
.fork(2)
Linux Manual Page: Understanding the creation of processes is essential context for waiting on them.man 2 fork
.- Advanced Programming in the UNIX Environment by W. Richard Stevens and Stephen A. Rago. Chapter 8, “Process Control,” provides an exhaustive, authoritative treatment of
fork
,exit
,wait
, andexec
. - The Linux Programming Interface by Michael Kerrisk. Chapters 24, 25, and 26 offer a deeply detailed exploration of process creation, termination, and monitoring.
- POSIX.1-2017 standard,
waitpid()
specification: For those interested in the formal standard that defines this behavior across UNIX-like systems. Available on the The Open Group’s website. - Raspberry Pi Documentation: While not specific to system calls, the official documentation provides context for the hardware platform and its Linux environment.