Target Duration: 2–4 minutes (~300–450 spoken words)
Focus: Pointwise verbal delivery covering live process hierarchy mutation undertasklist_lock, pointer surgery (parent/real_parent), asynchronousSIGCHLDreaping by adopting parents, and recursive subtree termination.
What You Mentioned: "Live process tree surgery & reparenting"
Why It Was Done (The Motivation): Allow dynamic runtime adoption of running child processes without calling fork().
Problems Faced & How Solved (The Reality): Manipulated kernel children and sibling lists and updated real_parent/parent pointers under tasklist_lock.
Opening & Scope:
"In Part 2.2, I implemented a kernel character driver (2.2/chardev.c) that performs live process tree surgery: dynamically reparenting running processes to an unrelated supervisor process without fork(), and executing recursive signal delivery across entire process subtrees."
Step 1: The Process Reparenting Problem:
"First, under standard POSIX semantics, a process's parent is immutably set at fork() time. If a process exits, its exit code and resource accounting can only be reaped by its immediate parent; otherwise, it reparents to init (PID 1) upon orphaning. The challenge in Part 2.2 was allowing an independent supervisor (2.2/control_station.c) to adopt worker processes (2.2/soldier.c) on the fly, becoming their legal parent so it can receive their SIGCHLD and reap them."
Step 2: Tree Surgery Under tasklist_lock:
"Next, to modify the process hierarchy safely, holding the right lock is critical. The kernel process tree is protected by tasklist_lock. In change_parent() (2.2/chardev.c:26), the driver resolves the new parent via pid_task(find_vpid(tpid)) (2.2/chardev.c:32) and safely synchronizes pointer mutations across scheduler ticks."
Step 3: Unlinking and Splicing Task Hierarchy Pointers:
"Then, inside the critical section, I performed the pointer surgery:
list_del_init(¤t->sibling) (2.2/chardev.c:40).current->parent and current->real_parent to point to the new supervisor task_struct using RCU_INIT_POINTER() (2.2/chardev.c:43-44).list_add_tail_rcu(¤t->sibling, &parent_task->children) (2.2/chardev.c:41).
Finally, exiting the critical section made the new hierarchy active system-wide."Step 4: Asynchronous Zombie Reaping via SIGCHLD:
"In user space, when a reparented soldier worker terminates, the kernel delivers SIGCHLD directly to the new adopting control_station. I configured control_station with a dedicated handler (sigchld_handler in 2.2/control_station.c:24) using sigaction with SA_RESTART | SA_NOCLDSTOP. Inside the handler, it executes a non-blocking waitpid(-1, &status, WNOHANG) loop to reap all exited children cleanly, preventing zombie process leaks."
Step 5: Recursive Subtree Termination (kill_all):
"Finally, I implemented a recursive subtree teardown command (kill_all() in 2.2/chardev.c:51). Triggered via ioctl, the driver iterates through the supervisor's children list using list_for_each_entry_rcu() (2.2/chardev.c:69), and dispatches signals to every process in the subtree using send_sig() (2.2/chardev.c:72, 85). This guarantees atomic and complete cleanup of multi-tier worker pools."
| Step | What Was Done | How It Works | Why This Mechanism / Order | Code Reference |
|---|---|---|---|---|
| 1. Lock Acquisition | Acquired tasklist_lock / RCU |
Disables preemption and claims tasklist read/write context. | Prevents interrupt handlers and concurrent fork/exit calls from corrupting tree. | 2.2/chardev.c:30 |
| 2. Unlink Sibling | Removed from old parent list | Called list_del_init(¤t->sibling). |
Safely detaches child list node before modifying parent pointers. | 2.2/chardev.c:40 |
| 3. Pointer Rewriting | Updated parent & real_parent |
Set both pointers to new supervisor struct task_struct* via RCU_INIT_POINTER. |
parent receives signals; real_parent handles ptrace/debugger relationships. |
2.2/chardev.c:43-44 |
| 4. Splice New List | Spliced into new parent's children | Called list_add_tail_rcu() to insert into target's list. |
Establishes the new legal parent-child link in the kernel. | 2.2/chardev.c:41 |
| 5. Asynchronous Reaping | Handled SIGCHLD in user space |
Used waitpid(-1, &status, WNOHANG) in signal handler. |
Prevents zombies and reaps arbitrary worker numbers without blocking main loop. | 2.2/control_station.c:242.2/chardev.c:51-87 |
fork()? What practical problem in process supervision does this solve?Answer: In standard Unix/Linux, parent-child relationships are permanently locked at
fork(). If a long-running worker process is spawned by an ephemeral shell or deployment script, and that launcher terminates, the worker is orphaned toinit(PID 1) or a subreaper. Standard Linux provides no user-space syscall to reassign parentage. The persistent monitoring daemon cannot receive the worker's exit signals (SIGCHLD) or inspect exit codes viawaitpid(). Live reparenting in kernel space transfers custody dynamically, allowing a supervisor to adopt existing running processes without killing or restarting them.
write_lock_irq?Answer: Rewriting process relationships requires unlinking nodes from
old_parent->childrenand splicing intonew_parent->childrenwhile updatingparentandreal_parentpointers. If another CPU forks or exits a process simultaneously, or if a timer/hardware interrupt fires on the current CPU and attempts to read or modify process states (e.g., during scheduling or signal delivery), circular lists get corrupted, causing instant kernel panics or deadlock. We usedwrite_lock_irq(&tasklist_lock), which disables local interrupts and locks the entire global tasklist across all CPUs during the pointer rewrites.
Answer: When an adopted worker exits, it enters
TASK_ZOMBIEand deliversSIGCHLDto its new parent. Because our supervisor adopted multiple worker processes asynchronously, multiple workers could terminate simultaneously. POSIX signals are not queued—if multipleSIGCHLDsignals arrive in rapid succession, the kernel coalesces them into a single delivery. A singlewaitpid()call would reap only one worker, leaking all others as permanent zombies. In our user-space control station, we installed a non-blocking reaping loopwhile (waitpid(-1, &status, WNOHANG) > 0)inside theSIGCHLDsignal handler, guaranteeing that every adopted child is reaped immediately.