xv6, line by line
tour 26
Tours26 sbrk, eager and lazy, and page faults

Tour 26 · Memory · about 29 minutes · 18 steps

sbrk, eager and lazy, and page faults

A program that needs more memory asks the kernel to move the end of its address space up. xv6 offers two ways to do it. sbrk(n) is eager: the kernel allocates and maps every page right away. sbrklazy(n) is lazy: the kernel only raises the limit, and allocates a page the first time the program touches it, inside a page fault.

This tour follows both. First, the shell’s child (pid 3) parsing ls calls malloc, which grows the heap eagerly by 64 KiB: 16 pages allocated in one system call. Then a usertests child asks lazily for a whole gibibyte, gets it instantly, and touches one page in every 64: 4096 page faults, each turning into one page of real memory, in our gdb run. You will also see the guard page refuse to be “lazily allocated”, memory being given back with sbrk(-n), and why the kernel can free pages without telling any TLB (translation lookaside buffer).

Almost nothing here takes a lock, and that absence is the lesson: a process’s address space is its own. The one shared thing is the pool of free pages, and its lock, kmem.lock, is where the harts meet.

Best after: 5. Life of a system call, 10. Exceptions and faults, 25. A user address space

Who is running where

The machine has three harts. When the tour starts:

Hart What it is doing
0 Idle in its scheduler; the shell (pid 2) sleeps in wait
1 Idle, or running whatever else is runnable
2 Running the shell’s child (pid 3) in user mode, about to parse ls

The child’s memory is a copy of the shell’s five pages (Tour 25: A user address space): p->sz = 0x5000, with an empty heap. Later in the tour, a different scenario runs usertests lazy_alloc.

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 parser needs memory user/sh.c
  2. 2malloc asks for 64 KiB at a time user/umalloc.c
  3. 3Two flavors of one system call user/ulib.c
  4. 4sys_sbrk chooses a path kernel/sysproc.c
  5. 5growproc, with no lock kernel/proc.c
  6. 6uvmalloc maps 16 zeroed pages kernel/vm.c
  7. 7kmem.lock, where the harts meet kernel/kalloc.c
  8. 8The heap is ready kernel/sysproc.c
  9. 9Asking lazily for a gibibyte user/usertests.c
  10. 10Lazy growth only moves a number kernel/sysproc.c
  11. 11A store to a page that is not there user/usertests.c
  12. 12usertrap recognizes a lazy page kernel/trap.c
  13. 13vmfault keeps the promise kernel/vm.c
  14. 14Retry, 4096 times kernel/trap.c
  15. 15The guard page refuses kernel/vm.c
  16. 16Giving memory back kernel/vm.c
  17. 17No TLB flush needed kernel/trampoline.S
  18. 18What growth costs kernel/sysproc.c

Keys: ← → step · Home start