xv6, line by line
tour 19
Tours19 Memory ordering across harts

Tour 19 · Concurrency primitives · about 39 minutes · 20 steps

Memory ordering across harts

When you read a C program you assume that its statements happen in the order you wrote them. On one hart that assumption is safe: whatever the compiler and the CPU do behind the scenes, a hart always sees its own loads and stores as if they ran in program order. The moment a second hart looks at the same memory, the assumption breaks. The compiler may keep a variable in a register, merge or move accesses, and the CPU may let other harts see its stores in a different order than it made them.

This tour follows the places where xv6 has to say “this, then that, and every hart must agree”: the started flag that releases harts 1 and 2 at boot, the barriers hidden inside every acquire and release, a process’s saved registers handed from one hart to another through p->lock, the fences around the virtqueue that talks to the disk, the difference between volatile and atomics, and the fence.i executed on every return to user space.

You will see the real instructions the compiler produced (kernel/kernel.asm of this build), what happens without them (timelines), and exactly what each fence promises and what it does not.

Best after: 3. main: one hart builds the kernel, the others wait, 15. Spinlocks from the hardware up

Who is running where

The tour has two halves. It starts at boot, when the machine has three harts and no processes yet:

Hart What it is doing
0 In main, building the kernel: page allocator, page table, process table, devices
1 In main, spinning on the started flag, paging off, interrupts off
2 The same as hart 1

Later steps jump forward to a running system, where a shell, cat and the disk driver keep three harts busy. Each step says which hart it is on.

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. 1Hart 0 builds, harts 1 and 2 wait kernel/main.c
  2. 2What the compiler may do to a waiting loop kernel/main.c
  3. 3What the compiler may do to the publishing side kernel/main.c
  4. 4What the CPU may do: RVWMO in brief kernel/main.c
  5. 5The release store, as compiled kernel/main.c
  6. 6The acquire load, as compiled kernel/main.c
  7. 7The bug the pair prevents kernel/main.c
  8. 8Visible to the hart is not yet visible to the page-table walker kernel/vm.c
  9. 9volatile and atomic are different tools kernel/main.c
  10. 10A spinlock is a pair of fences kernel/spinlock.c
  11. 11The release half kernel/spinlock.c
  12. 12The free list without the fences kernel/kalloc.c
  13. 13A thread's registers handed across harts kernel/swtch.S
  14. 14Another hart picks the thread up kernel/proc.c
  15. 15Talking to a device through memory kernel/virtio_disk.c
  16. 16io_fence is fence iorw, iorw kernel/riscv.h
  17. 17The completion side reads in order kernel/virtio_disk.c
  18. 18When volatile alone is enough kernel/printk.c
  19. 19fence.i on every return to user space kernel/trampoline.S
  20. 20What it all cost, and what to remember kernel/main.c

Keys: ← → step · Home start