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
Before wait()
→ All zeros
→ Stats transferred ✓
After wait()
→ Child stats visible ✓
Allocates memory
→ Accumulating stats
→ Becomes zombie
Waiting for parent wait()
The Grandchild Scenario
Consider a three-level process tree: parent → child → grandchild. The accumulation follows these exact rules:
Grandchild’s stats are added to the CHILD’s RUSAGE_CHILDREN accumulator.
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.
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.
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
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.
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.
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.
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).
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.
