xv6, line by line
lab 20
Lab 2020 Ticket spinlocks

Lab 20 · reveal · 16 steps · 4 commits

Ticket spinlocks: the reference solution

Every spinlock in this tree is one word and one atomic swap. When the holder lets go, the next hart whose amoswap happens to reach the word first takes the lock. Nothing remembers who has been waiting longest. In this lab you replace that loop with a ticket lock: a hart takes a number and waits until its number is called, like at a deli counter, so harts get the lock in the order they asked.

The change is about thirty lines in two files (spinlock.c and spinlock.h), and every line raises a question about hardware and timing. Which of the two counters must be updated atomically, and which need not be? What memory ordering does each step need, and which instructions does the compiler turn them into? What happens when a 32-bit counter wraps around? Interrupts are already off while a hart waits for a lock: was that only a convenience before, and is it still? What exactly does holding() mean when there is no “locked” flag any more?

You also build the instrument to see the difference: a system call that makes three processes hammer one kernel lock and keeps score. On QEMU the result is not what the textbook predicts, and the lab shows what we measured and why.

Each step shows one change on the branch ext/20-ticketlock, the code around it, and the state of the machine when that code runs.

The route
  1. 1The instrument: a system call that hammers one lock kernel/syscall.c
  2. 2One lock, and a score kept under it kernel/lockbench.c
  3. 3A start line, outside any lock kernel/lockbench.c
  4. 4The loop: time the wait, keep score, let go kernel/lockbench.c
  5. 5Inside the critical section kernel/lockbench.c
  6. 6locktest: three processes at the start line user/locktest.c
  7. 7Two counters in the same eight bytes kernel/spinlock.h
  8. 8Taking a ticket kernel/spinlock.c
  9. 9Waiting for your number kernel/spinlock.c
  10. 10Release: clear the owner, then serve the next number kernel/spinlock.c
  11. 11holding() without a flag kernel/spinlock.c
  12. 12The p->lock handoff does not notice kernel/proc.c
  13. 13Interrupts off before the ticket, not after kernel/spinlock.c
  14. 14Starting just below the wrap kernel/spinlock.c
  15. 15The queue across the wrap kernel/spinlock.c
  16. 16The handoff from 0xffffffff to 0 kernel/spinlock.c

Keys: ← → step · Home start