xv6, line by line
lab 10
Lab 1010 A shared read-only page: system calls without a trap

Lab 10 · reveal · 18 steps · 8 commits

A shared read-only page: system calls without a trap: the reference solution

getpid() asks the kernel for one integer it already knows. To get it, the process executes ecall, the hart traps, uservec saves 31 registers, the kernel switches page tables, usertrap and syscall run, and the whole trip is undone on the way back. In this tree that is 1,103 instructions after the ecall, two writes to satp and four sfence.vma (counted with gdb). In this lab you map one extra page into every process, at a fixed address just below the trapframe, with the process’s pid in it, readable but not writable from user mode. ugetpid() then becomes one load: 12 instructions, no trap.

A second page, the same physical page in every process, carries a copy of the kernel’s ticks, written by clockintr and read with no lock by uuptime(). That raises the question every shared-memory design must answer: can a reader see a half-written value?

The code is small (about 70 lines of kernel, a third of them comments), but placing a page at the top of the user address space touches more than you might expect. What else in the kernel already decides what lives near the top of a user address space? Who creates a page table, who throws one away, and how often does that happen in the life of one process? Does the kernel itself obey a read-only PTE when a system call touches user memory? Answering those is the lab. On QEMU the result is about 500 times faster per call.

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

The route
  1. 1Two new pages at the top of the address space kernel/memlayout.h
  2. 2The heap stops below the new pages kernel/sysproc.c
  3. 3The one test that knew the old ceiling user/usertests.c
  4. 4allocproc gives every process its own page kernel/proc.c
  5. 5freeproc frees it, and only freeproc kernel/proc.c
  6. 6proc_pagetable maps it read-only kernel/proc.c
  7. 7exec gets the page for free kernel/proc.c
  8. 8proc_freepagetable unmaps it, without freeing kernel/proc.c
  9. 9One page for the whole system, allocated at boot kernel/trap.c
  10. 10clockintr publishes ticks kernel/trap.c
  11. 11The shared page in every page table kernel/proc.c
  12. 12copyin keeps system calls below p->sz kernel/vm.c
  13. 13ugetpid: a system call that is a load user/ulib.c
  14. 14uuptime: volatile, because the kernel writes behind your back user/ulib.c
  15. 15A store to the page, from the test's point of view user/vdsotest.c
  16. 16usertrap: vmfault refuses, the process dies kernel/trap.c
  17. 17Fork, exec, and twenty children at once user/vdsotest.c
  18. 18Measuring the trap user/vdsotest.c

Keys: ← → step · Home start