xv6, line by line
tour 21
Tours21 exit, wait and zombies

Tour 21 · Processes · about 31 minutes · 19 steps

exit, wait and zombies

ls has printed its listing. On hart 2 it calls exit(0). On hart 0, the shell has been asleep inside wait(0) since it forked ls. In the next few microseconds the two must meet: the dying process has to tell its parent, the parent has to wake, collect the exit status and free what is left of the child, and nothing may be freed while the child is still using it.

The difficulty is that a process cannot dismantle itself. While exit runs, it is still running in its own slot of the process table, on that slot’s kernel stack, and its parent still needs its exit status. So xv6 splits death in two: exit releases everything it can and leaves a husk, a zombie, and the parent’s wait finishes the job. In between, three locks choreograph the handover: wait_lock, the child’s p->lock, and the parent’s.

You will also see what happens to children whose parent dies first (they are handed to init), how init reaps them, and the lost-wakeup race that wait_lock exists to prevent. The system-call path itself is Tour 5: Life of a system call's; sleeping and waking are explained in Tour 16: sleep and wakeup, and the lost-wakeup problem.

Best after: 5. Life of a system call, 16. sleep and wakeup, and the lost-wakeup problem, 20. fork

Who is running where

The machine has three harts. When the tour starts:

Hart What it is doing
0 Idle in its scheduler. The shell (pid 2) went to sleep here in wait
1 Idle in its scheduler, or running whatever else is runnable
2 Running ls (pid 3, slot 2 of the process table) in user mode

This hart assignment is staged to make the handover visible; any hart may run any process. In our gdb run, ls’s exit and the shell’s reaping happened on the same hart, one after the other: that hart’s scheduler found the shell RUNNABLE right after ls left. Below, the shell resumes on hart 0 for clarity.

init (pid 1) is asleep in its own wait, waiting for the shell. ls has descriptors 0, 1 and 2 on the console, inherited from the shell (Tour 20: fork).

Three harts are running. This tour follows one path through the code, but the machine has three CPUs executing at the same time. Watch the locks held display at the top of each step, and read the Meanwhile, on other harts boxes: they show what the other CPUs could be doing at that very moment.
The route
  1. 1The shell went to sleep in wait user/sh.c
  2. 2kwait's first scan finds a live child kernel/proc.c
  3. 3Registering, then sleeping kernel/proc.c
  4. 4ls finishes user/ls.c
  5. 5sys_exit calls kexit kernel/sysproc.c
  6. 6Closing every open file kernel/proc.c
  7. 7Letting go of the current directory kernel/proc.c
  8. 8Taking wait_lock and giving children away kernel/proc.c
  9. 9Waking the parent, before dying kernel/proc.c
  10. 10Becoming a zombie kernel/proc.c
  11. 11Into the scheduler, never to return kernel/proc.c
  12. 12Hart 2's scheduler lets go of the zombie kernel/proc.c
  13. 13The parent's scan finds the zombie kernel/proc.c
  14. 14Delivering the exit status kernel/proc.c
  15. 15Freeing the husk kernel/proc.c
  16. 16The lost wakeup that wait_lock prevents kernel/proc.c
  17. 17When the parent dies first user/zombie.c
  18. 18init, the reaper user/init.c
  19. 19What dying cost kernel/proc.c

Keys: ← → step · Home start