JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Zombies, Orphans, and the Process Tree

You can now fork a child, exec a new program, and wait for it to finish. This guide ties those threads together: every process on the machine hangs from one tree rooted at init, and the rules of that tree are exactly what create a zombie when a parent forgets to wait, and an orphan when a parent dies first.

Every process hangs from one tree

By now you hold all the pieces. From guide 2 you know fork() takes one process and makes two, a parent and a child that differ only in fork()'s return value. From guide 4 you know every process has a PID and also records its parent's PID, its PPID. Put those two facts together and a shape appears on its own: the child's PPID points at the parent, the parent's PPID points at its parent, and following PPID upward from any process always climbs toward a single root. That structure is the process tree, and it is not a metaphor the OS imposes afterwards — it is just what the PPID links already are.

What sits at the root? When the kernel finishes booting it hand-builds exactly one process, traditionally PID 1, the init process (on modern Linux this is usually systemd). PID 1 was not forked by anyone — the kernel created it directly — and everything else on the machine descends from it by fork. Your shell was forked from a login process forked from init; your program was forked from the shell. So the whole running system is one tree with init at the root and your `./a.out` as some leaf far below. This is worth holding concretely: there is no process without a parent except PID 1, and that single exception is the anchor the rest of this guide hangs on.

A zombie: finished, but not yet reaped

Now recall guide 4's rule precisely, because the zombie falls straight out of it. When a child calls exit() or returns from main, it stops running and gives up almost everything: its memory, its open file descriptors, its place on the CPU. But it does not fully vanish, because the kernel must keep one last scrap of information — the child's exit status — until the parent asks for it with wait(). A process that has terminated but whose status has not yet been collected is a zombie. The name is exact: it is dead (no code runs, it holds no memory) yet it still occupies a slot in the process table, refusing to be fully gone.

Why keep even that scrap? Because of a real race. The child might exit a microsecond after fork() returns, long before the parent gets around to calling wait(). If the kernel threw the exit status away the instant the child died, a slow parent would forever lose the answer to "how did my child end?" So the kernel holds the status open, as a zombie, until wait() reaps it — and the moment wait() returns, the status is handed over and the zombie's last slot is freed. Reaping is exactly this collection, and every well-behaved parent owes a wait() to each child it forks. A short-lived zombie between a child's exit and the parent's wait() is completely normal and harmless; you will see them flicker by in `ps`.

An orphan: the parent left first

The zombie is what happens when the parent outlives the child but neglects it. The orphan is the mirror image: the child outlives the parent. Nothing forces a parent to wait for its children before exiting — a parent can fork a child, do its own work, and call exit() while the child is still happily running. The moment the parent is gone, that still-running child has a PPID pointing at a process that no longer exists. It is now an orphan: alive, working, but parentless in a tree where every node except the root is supposed to have a parent.

The tree cannot tolerate a dangling branch, so the kernel repairs it automatically. The instant a parent dies, the kernel walks its children and re-parents every one of them — it rewrites each orphan's PPID to 1, handing the orphans up to init. This is the whole reason PID 1 has a second job beyond being the root: init exists to adopt orphans. From the orphan's own point of view almost nothing changed; it keeps running its code, and the only visible difference is that a quick check of its PPID now returns 1 instead of the PID of the parent that left.

Now the two ideas lock together, and this is the payoff of the whole guide. Sooner or later the orphan itself will finish and become a zombie — and who is now responsible for reaping it? Its new parent, init. And init's entire design is to sit in a loop calling wait() over and over, so it promptly reaps any orphan that dies on its watch. That is why orphans never become stuck zombies: re-parenting to init guarantees they will be reaped. It is also why, when a parent leaks zombies, killing that parent fixes it — the leaked zombies are re-parented to init, which immediately reaps them all.

Walking through it, one fork at a time

Let us trace one tiny program so the states stop being words and become events you can point at. The parent forks once, both sides print, and we deliberately make the parent sleep so you can catch the child in mid-state. This is the bare fork-then-something skeleton you already met, minus the exec — and notice the parent checks fork()'s return value, because a forgotten check here is exactly the kind of error guide 2 warned about.

pid_t pid = fork();
if (pid < 0) { perror("fork"); return 1; }

if (pid == 0) {
    /* child */
    printf("child pid=%d ppid=%d\n", getpid(), getppid());
    return 0;                 /* child terminates -> becomes a ZOMBIE
                                 until the parent reaps it */
} else {
    /* parent */
    sleep(10);                /* during this nap the dead child is a zombie */
    int status;
    wait(&status);            /* reap: the zombie is now fully gone */
}

/* If instead the PARENT returns here while the child still runs,
   the child is re-parented to init (PID 1): an ORPHAN. */
Same skeleton, two endings: the child dies first and waits as a zombie until the parent's wait(); or the parent dies first and the child is re-parented to init as an orphan.
  1. fork() returns. There are now two processes; the child's PPID is the parent's PID.
  2. The child prints and returns 0. It terminates, releases its memory, and turns into a zombie — its exit status is held, waiting to be collected.
  3. The parent is still asleep. List processes now and you will see the child marked Z (zombie) or as <defunct>.
  4. The parent wakes and calls wait(). It reads the exit status, the kernel frees the child's last slot, and the zombie is gone for good.
  5. Alternate ending: if the parent had returned before the child finished, the kernel would set the child's PPID to 1, and init — not the original parent — would later reap it.

Honest edges and a closing picture

A few honest caveats, because the tidy story has real corners. First, "orphan" and "zombie" are not opposites and not exclusive — an orphan is a live child whose parent died, a zombie is a dead child not yet reaped; an orphan that later dies becomes a zombie (then reaped by init). Second, on modern Linux the orphan-adopter is not always literally PID 1: a process can set itself as a "subreaper" so that its own orphaned descendants are re-parented to it rather than init, which is how service managers keep tidy control of their trees. The PPID-rewrite mechanism is the same; only the destination differs. Treat "re-parented to init" as the default rule with a documented exception, not an unbreakable law.

Third, a common beginner reflex is to fear zombies and try to kill them — but a zombie is already dead, so a signal does nothing; the fix is always to make the parent reap, never to attack the corpse. And fourth, do not confuse a child that is blocked or sleeping (alive, just not running right now) with one that is a zombie (terminated, awaiting reaping); the process states from guide 1 keep these straight, and a glance at the state column in `ps` tells them apart at once.

Step back and the rung becomes one coherent picture. A process is not a program; fork() splits one process into two; exec() reshapes a process into a different program; wait() collects a finished child's status. This guide is the frame around all four: those operations build and prune a single living tree rooted at init, and the rules of that tree are exactly what give the zombie and the orphan their precise, non-spooky meanings. You now have the whole process model — and from here the next rung will let these many processes actually talk to one another.