xv6, line by line
tour 52
Tours52 Breaking the lock rules

Tour 52 · Locks and interrupt state · about 33 minutes · 17 steps

Breaking the lock rules

xv6’s locking rests on a short list of rules: never acquire a lock you already hold; never sleep holding a spinlock; keep interrupts off while you hold one; let intena travel with the thread; register for a wakeup before you let go of the condition lock; take locks in one global order; release with a fence. Tours Tour 15: Spinlocks from the hardware up to Tour 18: Lock ordering: how xv6 avoids deadlock explained why each rule exists. This tour breaks them, one at a time, and reports what happened.

Each experiment is one small change to a scratch copy of the kernel (never to the tree the site annotates), built with the same compiler, booted in QEMU on three harts, with gdb attached (in most runs from the first instruction, with a breakpoint on panic). Where a rule needs pressure to fail, the copy also contains a 30-line test program, lockstress, that we added to its user/ directory: three worker processes that each call uptime(), or fork() + exit() + wait(), in a tight loop. When a run panicked, gdb stopped every hart at the panic call; when it hung, gdb interrupted the machine and printed where each hart was.

Some breaks panic with a message that names the rule. Some freeze the whole machine without a word. One freezes one or two processes while everything else keeps running. Four changes, to two rules, produced nothing we could observe, and we say why, because “it passed” is not the same as “it is safe”.

Best after: 15. Spinlocks from the hardware up, 16. sleep and wakeup, and the lost-wakeup problem, 18. Lock ordering: how xv6 avoids deadlock, 48. Breaking the invariants, 50. noff and intena through a sleep, a yield and an interrupt

Who is running where

Every experiment starts from the same place: a fresh copy of the source tree, a fresh disk image, one change, three harts.

Hart What it is doing
0 Booting, then running whatever its scheduler picks; also the only hart that counts ticks
1 Booting, then the same
2 Booting, then the same

The control: the unmodified kernel (with lockstress added) passed usertests -q on three harts (ALL TESTS PASSED) and finished lockstress fork 3000 and lockstress up 100000.

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. 1Rule 1: never acquire a lock you already hold kernel/spinlock.c
  2. 2Break 1b, with the check deleted kernel/sysproc.c
  3. 3Rule 2: never sleep holding a spinlock kernel/console.c
  4. 4What sched counted, and what it prevented kernel/proc.c
  5. 5Rule 3: interrupts off while any spinlock is held kernel/spinlock.c
  6. 6pop_off is the alarm kernel/spinlock.c
  7. 7Break 3b: the alarm removed kernel/trap.c
  8. 8Why every interrupt handler forces the rule on every lock kernel/proc.c
  9. 9Rule 4: intena belongs to the thread, not the hart kernel/proc.c
  10. 10Break 4: three ways to lose track of intena kernel/proc.c
  11. 11Rule 5: register before releasing the condition lock kernel/proc.c
  12. 12Break 5: a parent asleep for a wakeup that already happened kernel/proc.c
  13. 13Rule 6: one global order, wait_lock before p->lock kernel/proc.c
  14. 14Break 6: three harts, one cycle kernel/proc.c
  15. 15Rule 7: release publishes the critical section kernel/spinlock.c
  16. 16Break 7: no failure, and why that proves nothing kernel/spinlock.c
  17. 17What broke, and how loudly kernel/proc.c

Keys: ← → step · Home start