The Problem: Waiting for clone() Children
The normal waitpid() call waits for children that send SIGCHLD when they terminate. When you use clone() and specify a different termination signal (or no signal at all), regular waitpid() will not see those children. Linux adds three special option flags to handle this.
waitpid(), wait3(), and wait4(), but NOT with waitid().What Is a “Clone Child”?
In the context of these flags, a clone child is a child process created by clone() that terminates by delivering a signal other than SIGCHLD to its parent. A child created with SIGCHLD as the termination signal is treated as a normal (non-clone) child by waitpid().
| Child Type | Termination Signal | Seen by plain waitpid()? | Needs flag |
|---|---|---|---|
| Normal / fork() child | SIGCHLD | ✅ Yes | None |
| clone() child with SIGCHLD | SIGCHLD | ✅ Yes | None |
| clone() child with SIGUSR1 | SIGUSR1 | ❌ No | __WCLONE or __WALL |
| clone() child with signal 0 | None (0) | ❌ No | __WCLONE or __WALL |
The Three Extension Flags
__WCLONE — Wait for Clone Children Only
When __WCLONE is set, waitpid() waits only for clone children — those that deliver a signal other than SIGCHLD. Normal fork() children and SIGCHLD-terminated clone children are ignored.
__WALL — Wait for All Children (Clone and Non-Clone)
When __WALL is set, waitpid() waits for any child regardless of termination signal — both normal children (SIGCHLD) and clone children. Use this when you have a mix of child types and want one waitpid() to catch them all.
__WNOTHREAD — Limit to Calling Process’s Children Only
By default, waitpid() can reap children of any thread in the same thread group, not just the calling thread. This is required by POSIX (“any thread may wait for any child of the process”). Setting __WNOTHREAD limits the wait to only the calling thread’s own children.
waitpid() Flag Decision Logic
| waitpid(-1, &status, options) | ||||
| Is __WALL set? | ||||
|
YES → __WALL
Wait for ALL children (clone + non-clone). __WCLONE is ignored.
|
OR |
NO → Check __WCLONE
|
||
| Additionally: add __WNOTHREAD to restrict to only THIS thread’s children (not thread group’s children) | ||||
Example 1 — Using __WCLONE for Non-SIGCHLD Child
/* wclone_demo.c — Use __WCLONE to wait for a custom-signal clone child */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define STACK_SIZE (64 * 1024)
#define CHILD_SIG SIGUSR1 /* Non-standard termination signal */
static int child_func(void *arg)
{
printf("[Child] running, PID=%d\n", getpid());
sleep(1);
return 77; /* exit status */
}
int main(void)
{
char *stack = malloc(STACK_SIZE);
char *stack_top = stack + STACK_SIZE;
int status;
pid_t child_pid, waited_pid;
/* Must ignore CHILD_SIG or it will terminate the parent */
if (signal(CHILD_SIG, SIG_IGN) == SIG_ERR) {
perror("signal"); exit(1);
}
/* Lower byte = SIGUSR1 as termination signal */
child_pid = clone(child_func, stack_top, CHILD_SIG, NULL);
if (child_pid == -1) { perror("clone"); exit(1); }
printf("[Parent] created clone child PID=%d with SIGUSR1\n", child_pid);
/* ---- WRONG: plain waitpid() will NOT see this child ---- */
waited_pid = waitpid(child_pid, &status, WNOHANG);
printf("[Parent] plain waitpid result: %d (0 = no child ready, "
"-1 = error: %s)\n", waited_pid,
waited_pid == -1 ? strerror(errno) : "n/a");
/* ---- CORRECT: use __WCLONE ---- */
printf("[Parent] waiting with __WCLONE...\n");
waited_pid = waitpid(child_pid, &status, __WCLONE);
if (waited_pid == -1) { perror("waitpid __WCLONE"); exit(1); }
printf("[Parent] reaped PID=%d, exit status=%d\n",
waited_pid, WEXITSTATUS(status));
free(stack);
return 0;
}
Example 2 — __WALL to Handle Mixed Children
/* wall_demo.c — Parent has both fork() and clone() children; use __WALL */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#define STACK_SIZE (64 * 1024)
static int clone_child(void *arg)
{
printf("[Clone child] PID=%d, sleeping 1s...\n", getpid());
sleep(1);
return 11;
}
int main(void)
{
char *stack = malloc(STACK_SIZE);
char *stack_top = stack + STACK_SIZE;
/* Ignore SIGUSR1 so it doesn't kill parent */
signal(SIGUSR1, SIG_IGN);
/* Create a normal fork() child */
pid_t fork_child = fork();
if (fork_child == 0) {
printf("[Fork child] PID=%d, sleeping 2s...\n", getpid());
sleep(2);
exit(22);
}
/* Create a clone() child with SIGUSR1 termination signal */
pid_t clone_pid = clone(clone_child, stack_top, SIGUSR1, NULL);
if (clone_pid == -1) { perror("clone"); exit(1); }
printf("[Parent] fork child=%d, clone child=%d\n",
fork_child, clone_pid);
/* Reap both children using __WALL */
int reaped = 0;
while (reaped < 2) {
int status;
pid_t pid = waitpid(-1, &status, __WALL);
if (pid == -1) { perror("waitpid"); break; }
printf("[Parent] reaped PID=%d, exit=%d\n",
pid, WEXITSTATUS(status));
reaped++;
}
free(stack);
return 0;
}
/*
* __WALL reaps BOTH the fork() child (SIGCHLD) and
* the clone() child (SIGUSR1) — no need for separate calls.
*/
Example 3 — __WNOTHREAD to Restrict to Own Children
/* wnothread_demo.c — Demonstrate __WNOTHREAD limiting wait scope */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define STACK_SIZE (64 * 1024)
/*
* Scenario:
* Thread A creates child X.
* Thread B tries to reap child X with __WNOTHREAD.
* Result: Thread B CANNOT reap X (not its own child).
*
* Without __WNOTHREAD, any thread in the group can reap any child.
*/
static int grandchild_func(void *arg)
{
sleep(1);
return 55;
}
static int thread_b_func(void *arg)
{
pid_t child_pid = *((pid_t *) arg);
int status;
printf("[Thread B] trying to reap child %d with __WNOTHREAD\n",
child_pid);
/* __WNOTHREAD: only wait for children of THIS thread */
pid_t result = waitpid(child_pid, &status, __WNOTHREAD | __WALL);
if (result == -1)
printf("[Thread B] waitpid failed: %s (expected — not our child)\n",
strerror(errno));
else
printf("[Thread B] unexpectedly reaped %d\n", result);
return 0;
}
int main(void)
{
char *stA = malloc(STACK_SIZE), *stB = malloc(STACK_SIZE);
pid_t child_pid, thread_b_pid;
int status;
signal(SIGUSR1, SIG_IGN);
/* Thread A (main) creates child X */
child_pid = clone(grandchild_func, stA + STACK_SIZE, SIGUSR1, NULL);
if (child_pid == -1) { perror("clone child"); exit(1); }
printf("[Main/ThreadA] created child X = %d\n", child_pid);
/* Thread B tries to reap X using __WNOTHREAD */
thread_b_pid = clone(thread_b_func, stB + STACK_SIZE,
CLONE_VM | CLONE_SIGHAND | CLONE_THREAD,
&child_pid);
sleep(2); /* Wait for thread B to attempt its wait */
/* Main reaps child X (Thread A can do it) */
waitpid(child_pid, &status, __WCLONE);
printf("[Main/ThreadA] reaped child X=%d, exit=%d\n",
child_pid, WEXITSTATUS(status));
free(stA); free(stB);
return 0;
}
Key Rules Summary
- A clone child = terminates with signal ≠ SIGCHLD
- Plain waitpid() only sees SIGCHLD children
- __WCLONE: only clone children (non-SIGCHLD)
- __WALL: all children (SIGCHLD + non-SIGCHLD); overrides __WCLONE
- __WNOTHREAD: restrict to THIS thread’s children (not thread group)
- These flags do NOT work with
waitid() - For CLONE_THREAD children: use
pthread_join()/ futex, not waitpid()
