xv6, line by line
tour 51
Tours51 The lock-order graph, measured

Tour 51 · Locks and interrupt state · about 38 minutes · 22 steps

The lock-order graph, measured

Tour 18: Lock ordering: how xv6 avoids deadlock derived xv6’s lock order by reading the code: find each place that holds two locks, check that it agrees with the others. This tour does the opposite. We let the kernel tell us. A copy of the kernel with a small recorder in its lock functions ran a real workload on three harts (boot, ls | wc, usertests -q with every test passing, stressfs, forktest, cat README | grep the | wc) and wrote down, for every one of nearly 29 million lock acquisitions, which locks the same hart or the same process was already holding.

The result is a graph: an arrow from A to B for every “B was acquired while A was held”. Then we explain every arrow by the line that creates it, with how often it happened. You will see the deepest nesting the kernel reaches, the one cycle among the spinlock classes (and why it cannot deadlock), and the code paths the workload never reached, some of which we then drive on purpose.

All numbers are from our runs of this build. The main run gives the totals. A second run of the same workload, with a recorder that also kept call chains, gives the per-site breakdowns; its totals differ by a few percent because timing differs between runs.

Best after: 15. Spinlocks from the hardware up, 16. sleep and wakeup, and the lost-wakeup problem, 17. Sleep-locks, 18. Lock ordering: how xv6 avoids deadlock

Who is running where

The recorder ran on all three harts at once. The workload is ordinary: a shell, pipelines, usertests forking thousands of children, stressfs writing files from several processes.

Hart What it is doing
0 The only hart that runs clockintr's ticks++; otherwise like the others
1 Running processes, taking interrupts, idling in scheduler
2 The same

When the workload is done, we type Ctrl-P. The recorder prints its tables from inside procdump, and that readout itself adds one edge to the graph.

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 recorder's hook in acquire kernel/spinlock.c
  2. 2Sleep-locks are tracked per process kernel/sleeplock.c
  3. 3Reading the results with Ctrl-P kernel/console.c
  4. 4Edge 1: wait_lock → p->lock, in kexit kernel/proc.c
  5. 5Edge 2: wait_lock → child's p->lock → kmem.lock, the deepest spinlock nesting kernel/proc.c
  6. 6Edges 3–6: kfork holds the child's lock over four leaves kernel/proc.c
  7. 7kfork lets go before taking wait_lock kernel/proc.c
  8. 8The rarest edge in the graph, once per boot kernel/proc.c
  9. 9Edges 7–12: every condition lock sits above p->lock kernel/proc.c
  10. 10The locks interrupt handlers take kernel/virtio_disk.c
  11. 11Edge 13: log.lock → bcache.lock kernel/log.c
  12. 12Edge 14: itable.lock → an inode's sleep-lock, the second noff 3 kernel/fs.c
  13. 13The one cycle, and why it cannot close kernel/sleeplock.c
  14. 14Edge 15 and the third noff 3: Ctrl-P kernel/uart.c
  15. 15Why the spinlock graph has no other cycle kernel/proc.c
  16. 16Sleep-locks: parent directory, then child, in create kernel/sysfile.c
  17. 17unlink, namex and link kernel/sysfile.c
  18. 18Inode → buffer → buffer: three sleep-locks, and four kernel/fs.c
  19. 19Buffer → buffer in commit, with nothing else held kernel/log.c
  20. 20begin_op behaves like the outermost lock kernel/log.c
  21. 21What the run never did kernel/vm.c
  22. 22What keeps the graph true kernel/proc.c

Keys: ← → step · Home start