xv6, line by line
lab 18
Lab 1818 Per-CPU free lists in kalloc

Lab 18 · reveal · 14 steps · 5 commits

Per-CPU free lists in kalloc: the reference solution

In this tree every page of physical memory is handed out from one list, kmem.freelist, under one spinlock, kmem.lock. Every kalloc and every kfree on every hart takes that lock, even when the three harts are working on entirely different pages for entirely different processes. With three processes growing and shrinking their memory at the same time, one acquisition in sixteen finds the lock already held and spins.

In this lab you give each CPU its own free list and its own lock. A CPU frees onto its own list and allocates from it, so in the common case no two harts ever touch the same lock. The idea is simple; the details are a short course in what “per-CPU” really means. How does code know which CPU it is on, and for how long is that answer true? What does a CPU do when its own list is empty, and what new locking between harts does that bring back? You will answer these questions yourself, then break the answers on purpose and record what happens, one of them with gdb.

The reference solution is five small commits. On three harts it cuts the contended acquisitions of the allocator’s locks during a stress test from about 47,000 to under 10, and you will see where memory ends up when nobody puts it back where it came from.

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

The route
  1. 1The test: three harts allocating at once user/kalloctest.c
  2. 2What a single process must still be able to do user/kalloctest.c
  3. 3One list and one named lock per CPU kernel/kalloc.c
  4. 4kfree puts the page on this CPU's list kernel/kalloc.c
  5. 5Why cpuid() needs interrupts off kernel/proc.c
  6. 6kfree inside kwait, at noff 4 kernel/kalloc.c
  7. 7kalloc tries its own list, then the others, one lock at a time kernel/kalloc.c
  8. 8A page fault versus a system call kernel/kalloc.c
  9. 9steal: cut a batch off the victim's list kernel/kalloc.c
  10. 10steal: keep one page, give the rest to your own list kernel/kalloc.c
  11. 11kalloc, final shape kernel/kalloc.c
  12. 12A count per list, kept under the list's lock kernel/kalloc.c
  13. 13procdump prints the counts kernel/proc.c
  14. 14What changed, and what it cost kernel/kalloc.c

Keys: ← → step · Home start