xv6, line by line
tour 23
Tours23 kill

Tour 23 · Processes · about 27 minutes · 17 steps

kill

kill 5 sounds like an order: stop process 5, now. In xv6 it is closer to a note left on the victim’s desk. kkill sets a flag, p->killed, and if the victim is asleep, nudges it awake. That is all. The victim itself notices the flag later, at a point where it is safe to stop, and calls kexit on its own.

This tour runs kill 5 from the shell on hart 0 twice, against two different victims: (a) pid 5 spinning in user mode on hart 1, which notices at its next timer tick, and (b) pid 5 asleep in piperead, waiting for data that may never come, which kill wakes up so that it can notice. You will see why the kernel never stops a process from the outside, which code checks the flag and which deliberately does not, the narrow window in which a kill is noticed late, and a bug fixed only recently: kill(0) (the fix landed on the same day as the commit this site is built from).

Every access to killed is made under the victim’s p->lock, on whichever hart is looking. That lock, and the places where the flag is checked, are the whole design.

Best after: 5. Life of a system call, 11. From a timer tick to a context switch, 16. sleep and wakeup, and the lost-wakeup problem, 21. exit, wait and zombies

Who is running where

The machine has three harts. Pid 5 was started earlier as a background job. You type kill 5; the shell (pid 2) forks pid 8, which runs /kill. When the tour starts:

Hart What it is doing
0 Running kill (pid 8) in user mode, about to call kill(5)
1 Case (a): running pid 5 in user mode, a loop that never makes a system call
2 Running whatever else is runnable, or idle in its scheduler

For case (b), rewind: pid 5 is instead a process reading from a pipe whose writer has written nothing yet (for example, a child of a program that called pipe and fork), asleep in piperead.

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 kill program user/kill.c
  2. 2sys_kill passes the pid along kernel/sysproc.c
  3. 3pid 0 is not a process kernel/proc.c
  4. 4The regression test, killzero user/usertests.c
  5. 5Find the victim, one lock at a time kernel/proc.c
  6. 6Case (a): the victim is running kernel/proc.c
  7. 7Hart 1's next tick kernel/trap.c
  8. 8Reading the flag under the lock kernel/proc.c
  9. 9kexit, on the victim's own terms kernel/proc.c
  10. 10The other boundary, at system-call entry kernel/trap.c
  11. 11Case (b): the victim is asleep in piperead kernel/pipe.c
  12. 12kkill wakes the sleeper kernel/proc.c
  13. 13Pid 5 wakes up somewhere kernel/proc.c
  14. 14piperead notices and gives up kernel/pipe.c
  15. 15The window where a kill arrives late kernel/pipe.c
  16. 16Which sleeps check, and which do not kernel/proc.c
  17. 17A note, not a bullet user/kill.c

Keys: ← → step · Home start