RUSAGE_CHILDREN

 

36.1b — RUSAGE_CHILDREN
Accumulation Rules, wait() Dependency & the Grandchild Scenario | EmbeddedPathashala

RUSAGE_CHILDREN — The Key Rule

When you pass RUSAGE_CHILDREN to getrusage(), you get the accumulated resource usage of your terminated and waited-for children. This sounds simple but has subtle implications that every systems programmer must understand. The most important rule is: child statistics are only transferred to the parent after the parent calls wait(). Until wait() is called, the child’s stats are invisible even if the child has already exited.

The wait() Dependency — Visualised

PARENT
Calls getrusage(RUSAGE_CHILDREN)
Before wait()
→ All zeros
Calls wait() or waitpid()
→ Stats transferred ✓
Calls getrusage(RUSAGE_CHILDREN)
After wait()
→ Child stats visible ✓
CHILD
Runs, burns CPU
Allocates memory
→ Accumulating stats
Calls exit()
→ Becomes zombie
Waiting for parent wait()
Critical point: Even after the child calls exit() and becomes a zombie, its resource statistics are NOT yet visible in the parent’s RUSAGE_CHILDREN. They are only transferred when the parent calls wait()/waitpid()/wait3()/wait4().

The Grandchild Scenario

Consider a three-level process tree: parent → child → grandchild. The accumulation follows these exact rules:

PARENT
CHILD
GRANDCHILD
Step 1 — Child waits for grandchild:
Grandchild’s stats are added to the CHILD’s RUSAGE_CHILDREN accumulator.
Step 2 — Parent waits for child:
Both the child’s own stats AND the grandchild’s stats (which the child had already accumulated) are added to the PARENT’s RUSAGE_CHILDREN accumulator.
Failure case — Child does NOT wait for grandchild:
Even when parent calls wait() for child, the grandchild’s stats are never included in parent’s RUSAGE_CHILDREN. The stats are permanently lost.

Special Behaviour of ru_maxrss for RUSAGE_CHILDREN

For most fields, RUSAGE_CHILDREN returns the sum of all descendants’ values. But ru_maxrss is an exception. It returns the maximum RSS value among all descendants, not the sum.

Example: If child A peaked at 50 MB and child B peaked at 80 MB, then ru_maxrss in RUSAGE_CHILDREN = 80 MB (not 130 MB).

The SIGCHLD Deviation (Kernel Bug before 2.6.9)

SUSv3 has a rule: if the process is ignoring SIGCHLD (which prevents children from becoming zombies), then child statistics should not be added to RUSAGE_CHILDREN.

The reasoning: if SIGCHLD is ignored, children are auto-reaped without wait() being called. So there is no wait() event to transfer the stats, and the stats should not appear.

Linux deviation: In kernels before 2.6.9, Linux violated this rule. Even with SIGCHLD ignored, dead children’s stats were included in RUSAGE_CHILDREN. This was fixed in kernel 2.6.9 to match the SUSv3 requirement.

/* Demonstrating SIGCHLD ignore behaviour */
#include <signal.h>
#include <sys/resource.h>

/* Ignore SIGCHLD — children auto-reaped, no zombies */
signal(SIGCHLD, SIG_IGN);

/* On Linux >= 2.6.9: RUSAGE_CHILDREN will NOT include
   stats for children terminated while SIGCHLD is ignored.
   On Linux < 2.6.9: stats were incorrectly included. */

Code Example — Proving wait() Is Required

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

static void show_children_cpu(const char *label)
{
    struct rusage ru;
    getrusage(RUSAGE_CHILDREN, &ru);
    printf("%s: child user CPU = %ld.%06ld s\n", label,
           (long)ru.ru_utime.tv_sec,
           (long)ru.ru_utime.tv_usec);
}

int main(void)
{
    pid_t pid;
    long i;

    show_children_cpu("BEFORE fork");

    pid = fork();
    if (pid == -1) { perror("fork"); exit(1); }

    if (pid == 0) {
        /* Child: burn ~200 million iterations of CPU work */
        for (i = 0; i < 200000000L; i++) ;
        printf("  [child] Done. Exiting.\n");
        exit(0);
    }

    /* Parent: sleep so child has time to finish */
    sleep(2);

    /* Read BEFORE wait() — child has exited but not been waited */
    show_children_cpu("AFTER child exits, BEFORE wait()");

    /* Now reap the child */
    waitpid(pid, NULL, 0);

    /* Read AFTER wait() — stats now accumulated */
    show_children_cpu("AFTER wait()");

    return 0;
}

/*
 * Compile: gcc -O0 -o rusage_children rusage_children.c
 * Run:     ./rusage_children
 *
 * Expected output:
 *   BEFORE fork: child user CPU = 0.000000 s
 *     [child] Done. Exiting.
 *   AFTER child exits, BEFORE wait(): child user CPU = 0.000000 s
 *   AFTER wait(): child user CPU = 0.450000 s  (or similar)
 *
 * Key lesson: stats are ZERO before wait(), non-zero after.
 */

Interview Questions

Q1. When using RUSAGE_CHILDREN, are the stats for a running child included?

No. RUSAGE_CHILDREN only includes stats for children that have (1) already terminated and (2) been waited for by the calling process using wait() or waitpid(). Running children are excluded. Children that exited but have not yet been waited for are also excluded.

Q2. In a parent → child → grandchild tree, when does the parent see the grandchild’s resource usage in RUSAGE_CHILDREN?

Only after both: (1) the child calls wait() for the grandchild, and (2) the parent calls wait() for the child. If the child does not wait for the grandchild, the grandchild’s stats are never visible in the parent’s RUSAGE_CHILDREN, even after the parent waits for the child.

Q3. How does ru_maxrss behave differently for RUSAGE_CHILDREN compared to other fields?

Most fields in RUSAGE_CHILDREN accumulate as a sum (e.g., total minor faults = sum of all children’s minor faults). But ru_maxrss gives the maximum RSS across all descendants, not the sum. If three children peaked at 20 MB, 50 MB, and 30 MB respectively, ru_maxrss shows 50 MB, not 100 MB.

Q4. What Linux kernel version fixed the SIGCHLD/RUSAGE_CHILDREN deviation from SUSv3?

The fix came in kernel 2.6.9. Before that, if SIGCHLD was being ignored, dead children’s resource usage was incorrectly included in RUSAGE_CHILDREN. SUSv3 requires that stats should not be included in this case (because SIGCHLD ignore prevents children from becoming zombies, making them impossible to wait on).

Q5. Can RUSAGE_CHILDREN be used to detect resource leaks in child processes?

Yes. By comparing RUSAGE_CHILDREN before and after a batch of child processes, you can measure total CPU time consumed, total page faults, and total I/O operations for the whole batch. This is how tools like make and shell job control utilities track the resource consumption of the commands they execute.

Leave a Reply

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