waitpid(2)

System call

Waits for a child process to end and collects its status: exit code or the signal that killed it. Without this call, the dead child stays a zombie.

Synopsis
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);

Description

waitpid suspends the calling process until one of its children changes state, which in practice means until it terminates. It then releases what was left of the child in the kernel: a terminated child that nobody has waited for yet is a zombie.

The pid parameter picks whom to wait for: a positive PID targets that specific child, -1 accepts any child. wait(&status) is exactly waitpid(-1, &status, 0). The last parameter takes options, the most useful being WNOHANG, which returns right away if no child has finished.

If wstatus is not NULL, waitpid stores an encoded integer there, which you read with these macros:

MacroMeaning
WIFEXITED(s)true if the child ended normally (exit, return from main)
WEXITSTATUS(s)its exit code, the low 8 bits of the value passed to exit
WIFSIGNALED(s)true if a signal killed the child
WTERMSIG(s)the number of that signal

Return value

The PID of the child whose state changed. With WNOHANG, 0 if the targeted children exist but have not changed state yet. On error, -1 and errno: ECHILD when there is no child to wait for, EINTR when a signal interrupted the wait.

Pitfalls

  • WEXITSTATUS only makes sense if WIFEXITED is true, and WTERMSIG only if WIFSIGNALED is true. Always test the first macro.

  • The exit code is cut down to 8 bits: exit(256) reads as 0, exit(-1) as 255.

  • A child can be waited for only once. A second waitpid on the same PID fails with ECHILD.

Example

status.c
#include <stdio.h>#include <stdlib.h>#include <sys/wait.h>#include <unistd.h>
static void report(pid_t pid){    int status;
    if (waitpid(pid, &status, 0) == -1) {        perror("waitpid");        return;    }    if (WIFEXITED(status))        printf("%d: exited, code %d\n", pid, WEXITSTATUS(status));    else if (WIFSIGNALED(status))        printf("%d: killed by signal %d\n", pid, WTERMSIG(status));}
int main(void){    pid_t a = fork();    if (a == 0)        exit(256);
    pid_t b = fork();    if (b == 0)        abort();
    report(a);    report(b);    report(b);    return 0;}
Terminal
$ gcc -Wall -Wextra status.c -o status$ ./status15: exited, code 016: killed by signal 6waitpid: No child processes

The first child asked for 256 and the parent reads 0. The second one is killed by SIGABRT, signal 6, which abort raises. The third call targets a child that was already waited for, hence ECHILD.

C

signal_watchdog

Write signal_watchdog, which runs a function in a child, reads how it ended from its status, and leaves no zombie behind, even after a timeout.

Open the exercise

Now write it yourself.