Chapter 26 — Part 4: waitid(), wait3() and wait4()

Chapter 26 — Part 4: waitid(), wait3() and wait4()
Advanced Child-Waiting System Calls with siginfo_t and Resource Usage
Topic
waitid / wait3 / wait4
Level
Advanced
Examples
3 Programs

Beyond waitpid()

Linux provides three more wait-family system calls, each offering capabilities beyond waitpid():

  • waitid() — fine-grained event control, returns rich siginfo_t info, can leave child in waitable state
  • wait3() — like waitpid(-1,...) but also returns child resource usage
  • wait4() — like waitpid(pid,...) but also returns child resource usage

waitid() — Function Signature
#include <sys/wait.h>

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
/*
 * Returns:
 *   0 on success (or WNOHANG with no children ready)
 *  -1 on error
 */

waitid() — idtype and id Arguments
idtype id value Meaning
P_ALL ignored Wait for any child
P_PID process ID Wait for child with that specific PID
P_PGID process group ID Wait for any child in that process group
Note: Unlike waitpid(0,...), you cannot pass 0 to mean “same process group as caller”. You must use getpgrp() explicitly: waitid(P_PGID, getpgrp(), &info, options)

waitid() — options Flags
Flag Description
WEXITED Report children that have terminated (normally or abnormally)
WSTOPPED Report children stopped by a signal
WCONTINUED Report stopped children resumed by SIGCONT
WNOHANG Non-blocking poll; return 0 if no children ready
WNOWAIT Unique to waitid: leave child in waitable state so it can be waited for again later

siginfo_t Fields Filled by waitid()
Field Content
si_code CLD_EXITED, CLD_KILLED, CLD_STOPPED, or CLD_CONTINUED
si_pid PID of the child that changed state
si_signo Always SIGCHLD
si_status Exit code, or signal number (check si_code to know which)
si_uid Real user ID of the child (Linux-specific, most others don’t set this)

Example 1: Basic waitid() Usage
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main(void)
{
    pid_t child;
    siginfo_t info;

    child = fork();
    if (child == 0) {
        printf("[Child] PID=%d exiting with code 7\n", getpid());
        exit(7);
    }

    /* Wait for any child that exits */
    memset(&info, 0, sizeof(siginfo_t));
    if (waitid(P_ALL, 0, &info, WEXITED) == -1) {
        perror("waitid");
        exit(EXIT_FAILURE);
    }

    printf("[Parent] Child PID    : %d\n", info.si_pid);
    printf("[Parent] Signal       : %d (SIGCHLD)\n", info.si_signo);

    switch (info.si_code) {
    case CLD_EXITED:
        printf("[Parent] Event        : Normal exit\n");
        printf("[Parent] Exit status  : %d\n", info.si_status);
        break;
    case CLD_KILLED:
        printf("[Parent] Event        : Killed by signal %d\n", info.si_status);
        break;
    case CLD_STOPPED:
        printf("[Parent] Event        : Stopped by signal %d\n", info.si_status);
        break;
    case CLD_CONTINUED:
        printf("[Parent] Event        : Continued\n");
        break;
    }

    return 0;
}

Example 2: WNOWAIT — Peek Without Consuming

WNOWAIT is unique to waitid(). It lets you “peek” at a child’s status without removing it from the waitable state.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main(void)
{
    pid_t child;
    siginfo_t info;
    int status;

    child = fork();
    if (child == 0) {
        exit(42);
    }

    /* Give child time to exit */
    sleep(1);

    /* PEEK — does not consume the zombie */
    memset(&info, 0, sizeof(siginfo_t));
    if (waitid(P_PID, child, &info, WEXITED | WNOWAIT) == -1) {
        perror("waitid peek");
        exit(EXIT_FAILURE);
    }
    printf("[Peek]  Child PID=%d exited with status=%d\n",
           info.si_pid, info.si_status);

    /* Child is still a zombie — peek again */
    memset(&info, 0, sizeof(siginfo_t));
    waitid(P_PID, child, &info, WEXITED | WNOWAIT);
    printf("[Peek2] Child PID=%d still waitable, status=%d\n",
           info.si_pid, info.si_status);

    /* NOW actually consume the zombie with waitpid */
    waitpid(child, &status, 0);
    printf("[Reap]  Zombie reaped.\n");

    /* Try to wait again — no more zombie */
    if (waitpid(child, &status, 0) == -1)
        printf("[Reap2] No child to wait for (as expected)\n");

    return 0;
}

wait3() and wait4() — Resource Usage
#define _BSD_SOURCE
#include <sys/resource.h>
#include <sys/wait.h>

pid_t wait3(int *status, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);

The key difference is the rusage argument which returns:

  • ru_utime — user CPU time consumed by the child
  • ru_stime — system CPU time consumed by the child
  • ru_maxrss — peak resident set size (memory)
  • and many other fields (see getrusage(2))

wait3() is equivalent to waitpid(-1, &status, options) plus rusage.
wait4() is equivalent to waitpid(pid, &status, options) plus rusage.

Example 3: wait4() with Resource Usage
#define _BSD_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/resource.h>

/* Child that burns CPU */
static void burn_cpu(int iterations)
{
    volatile long x = 0;
    for (int i = 0; i < iterations; i++)
        for (int j = 0; j < 1000; j++)
            x += j * i;
    (void)x;
}

int main(void)
{
    pid_t child;
    int status;
    struct rusage usage;

    child = fork();
    if (child == 0) {
        printf("[Child] Burning CPU...\n");
        burn_cpu(100000);
        printf("[Child] Done. Exiting.\n");
        exit(0);
    }

    /* wait4 — wait for specific child AND get resource usage */
    if (wait4(child, &status, 0, &usage) == -1) {
        perror("wait4");
        exit(EXIT_FAILURE);
    }

    if (WIFEXITED(status))
        printf("[Parent] Child exited with status %d\n", WEXITSTATUS(status));

    printf("[Parent] Child CPU time (user):   %ld.%06ld sec\n",
           (long)usage.ru_utime.tv_sec,
           (long)usage.ru_utime.tv_usec);
    printf("[Parent] Child CPU time (system): %ld.%06ld sec\n",
           (long)usage.ru_stime.tv_sec,
           (long)usage.ru_stime.tv_usec);
    printf("[Parent] Child max RSS:           %ld KB\n",
           usage.ru_maxrss);

    return 0;
}

Note: wait3() and wait4() are BSD-derived and not in SUSv3. Avoid them for portable code — use waitpid() + getrusage(RUSAGE_CHILDREN,...) instead.

Comparison: All Four Wait Calls
Feature wait() waitpid() waitid() wait3/wait4
Wait for specific child wait4 only
Non-blocking (poll) ✓ WNOHANG ✓ WNOHANG ✓ WNOHANG
Detect stopped child ✓ WUNTRACED ✓ WSTOPPED ✓ WUNTRACED
Resource usage
Peek without consuming ✓ WNOWAIT
POSIX / SUSv3 ✗ (BSD)

Key Terms:

waitid() siginfo_t si_code CLD_EXITED WNOWAIT P_PID P_ALL wait3() wait4() rusage

Interview Questions

Q1. What is the main advantage of waitid() over waitpid()?

waitid() provides finer-grained control over which events to wait for (using WEXITED, WSTOPPED, WCONTINUED separately), returns richer information via siginfo_t, and supports WNOWAIT to “peek” without consuming the status.

Q2. What does WNOWAIT do in waitid()?

It returns the child’s current status but leaves the child in a waitable state. A subsequent waitid() or waitpid() call can retrieve the same information again.

Q3. Why must you memset siginfo_t to 0 before calling waitid() with WNOHANG?

When WNOHANG is used and no child has changed state, some implementations leave siginfo_t unchanged. Zeroing first lets you check if si_pid == 0, which reliably indicates “no child ready”.

Q4. How do you use waitid() to wait for a specific process group?

waitid(P_PGID, (id_t)getpgrp(), &info, WEXITED);

Q5. What is the key difference between wait3() and wait4()?

wait3() waits for ANY child. wait4() waits for a SPECIFIC child (selected by pid). Both return resource usage via rusage.

Q6. Why should wait3()/wait4() be avoided in portable code?

They originated in BSD and are not in SUSv3/POSIX. For portable code, use waitpid() for waiting and getrusage(RUSAGE_CHILDREN) for resource stats.

Q7. What si_code value does waitid() set when a child is killed by a signal?

CLD_KILLED. The signal number that killed the child is in si_status.

Q8. What happens if you waitid() with WEXITED only and the child is stopped (not exited)?

The call blocks until the child actually terminates (or returns 0 if WNOHANG is also set), because the stop event doesn’t match the requested events.

Next: Orphan and Zombie Processes — What Happens After Parents or Children Die

Part 5: Orphans & Zombies → EmbeddedPathashala Home

Leave a Reply

Your email address will not be published. Required fields are marked *