waitpid()
Intermediate
3 Programs
Why waitpid()?
wait() has three big limitations: it cannot target a specific child, it always blocks, and it cannot detect stopped children. waitpid() was designed to fix all three.
Function signature:
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
/* Returns PID of child, 0 (with WNOHANG), or -1 on error */
| pid value | Meaning |
|---|---|
| pid > 0 | Wait for child whose PID equals pid |
| pid == 0 | Wait for any child in the same process group as the caller |
| pid == -1 | Wait for ANY child (same as wait()) |
| pid < -1 | Wait for any child whose process group ID == abs(pid) |
| Flag | Effect |
|---|---|
| WNOHANG | Do not block. Return 0 immediately if no child has changed state. |
| WUNTRACED | Also return when a child is stopped by a signal. |
| WCONTINUED | Also return when a stopped child is resumed by SIGCONT. (Linux 2.6.10+) |
| 0 (no flags) | Block until child terminates (same as wait()) |
| waitpid(pid, &status, options) | ||
| ↓ | ||
| Returns child PID Child state changed |
Returns 0 WNOHANG, no change yet |
Returns -1 Error (e.g. ECHILD) |
Create two children; parent waits only for the second one first.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t child1, child2, result;
int status;
/* Create first child — sleeps longer */
child1 = fork();
if (child1 == 0) {
printf("[Child1] PID=%d sleeping 5s\n", getpid());
sleep(5);
_exit(10); /* exit status 10 */
}
/* Create second child — exits quickly */
child2 = fork();
if (child2 == 0) {
printf("[Child2] PID=%d sleeping 1s\n", getpid());
sleep(1);
_exit(20); /* exit status 20 */
}
printf("[Parent] Waiting specifically for Child2 (PID=%d)\n", child2);
/* Wait only for child2 */
result = waitpid(child2, &status, 0);
if (result == child2 && WIFEXITED(status))
printf("[Parent] Child2 exited with status %d\n", WEXITSTATUS(status));
printf("[Parent] Now waiting for Child1 (PID=%d)\n", child1);
/* Wait for child1 */
result = waitpid(child1, &status, 0);
if (result == child1 && WIFEXITED(status))
printf("[Parent] Child1 exited with status %d\n", WEXITSTATUS(status));
return 0;
}
The parent does other work and periodically checks if the child has finished.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t child, result;
int status;
int poll_count = 0;
child = fork();
if (child == 0) {
printf("[Child] PID=%d, doing work for 3 seconds\n", getpid());
sleep(3);
_exit(42);
}
/* Parent polls every 1 second while doing its own work */
while (1) {
result = waitpid(child, &status, WNOHANG);
if (result == 0) {
/* Child not done yet */
poll_count++;
printf("[Parent] Poll #%d: child still running, doing other work...\n",
poll_count);
sleep(1); /* simulate doing other work */
} else if (result == child) {
/* Child finished */
printf("[Parent] Child finished after %d polls\n", poll_count);
if (WIFEXITED(status))
printf("[Parent] Child exit status: %d\n", WEXITSTATUS(status));
break;
} else {
perror("waitpid");
exit(EXIT_FAILURE);
}
}
return 0;
}
WNOHANG is ideal when the parent cannot afford to block — for example in event loops or servers.
The parent detects when the child is stopped by SIGSTOP, not just when it exits.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t child, result;
int status;
child = fork();
if (child == 0) {
/* Child: loop indefinitely, waiting for signals */
printf("[Child] PID=%d running. Send SIGSTOP to stop me.\n", getpid());
for (;;)
pause(); /* wait for any signal */
}
printf("[Parent] Waiting for child PID=%d (stop or exit)\n", child);
/* Use WUNTRACED to also catch stop events */
for (;;) {
result = waitpid(child, &status, WUNTRACED | WCONTINUED);
if (result == -1) {
perror("waitpid");
break;
}
if (WIFEXITED(status)) {
printf("[Parent] Child exited, status=%d\n", WEXITSTATUS(status));
break;
} else if (WIFSIGNALED(status)) {
printf("[Parent] Child killed by signal %d\n", WTERMSIG(status));
break;
} else if (WIFSTOPPED(status)) {
printf("[Parent] Child stopped by signal %d\n", WSTOPSIG(status));
/* Could resume with kill(child, SIGCONT) */
} else if (WIFCONTINUED(status)) {
printf("[Parent] Child resumed\n");
}
}
return 0;
}
/*
* Test:
* Run program, note child PID
* kill -STOP -> parent prints "Child stopped"
* kill -CONT -> parent prints "Child resumed"
* kill -TERM -> parent prints "Child killed"
*/
These two calls are exactly equivalent:
wait(&status);
waitpid(-1, &status, 0); /* same thing */
waitpid(-1, ...) means “any child”, and 0 options means “block”.
Q1. What is the key difference between wait() and waitpid()?
wait() waits for any child and always blocks. waitpid() can target a specific child, use non-blocking mode (WNOHANG), and detect stopped children (WUNTRACED).
Q2. What does waitpid() return when WNOHANG is used and no child has changed state?
It returns 0 (not -1). A return of -1 with ECHILD means there are no matching children at all.
Q3. What is the difference between WNOHANG returning 0 vs -1?
0 means the child exists but hasn’t changed state yet. -1 (ECHILD) means no matching child process exists.
Q4. How do you wait for any child in the same process group as the caller?
waitpid(0, &status, 0);
Q5. What happens if you pass a PID of a process that is not your child to waitpid()?
It returns -1 with errno = ECHILD, because you can only wait for your own children.
Q6. What flag must you use to detect when a child is resumed from a stop?
WCONTINUED. Available since Linux 2.6.10. Check with WIFCONTINUED(status).
Q7. Can WNOHANG and WUNTRACED be combined?
Yes, using bitwise OR: waitpid(pid, &status, WNOHANG | WUNTRACED).
Q8. In a server that creates many children, why is WNOHANG preferred over blocking wait?
Blocking wait would freeze the server’s main loop. WNOHANG allows the server to poll periodically and continue accepting connections.
