xv6, line by line
lab 13
Lab 1313 A growable user stack

Lab 13 · reveal · 18 steps · 7 commits

A growable user stack: the reference solution

In this tree every user program gets exactly one page of stack. kexec places a guard page and a single stack page right after the program’s data, and the heap grows from just above them. A recursive function that needs 5 KiB of stack is killed. In this lab the stack moves to the top of the user address space and grows down, one page at a time, as the program touches it, up to a fixed maximum. The heap keeps growing up from the end of the program, and an unmapped gap separates the two.

The fault handler is the easy part. The lazy heap already fills missing pages on demand. What decides which addresses it will fill, and what else in the kernel decides the same thing its own way? Every one of those places has to be asked again: does it still see the whole process? Some of them fail loudly when they get it wrong, some quietly, and one only in a situation usertests never creates. And usertests has opinions about where the stack ends.

The reference solution is seven small commits. With it, 65 recursive calls with 1 KiB of locals each use 70,944 bytes of stack (18 pages), and unbounded recursion is killed one frame above the limit.

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

The route
  1. 1Three constants describe the new layout kernel/memlayout.h
  2. 2The eager heap stops below the guard kernel/proc.c
  3. 3So does the lazy heap kernel/sysproc.c
  4. 4The one test that knew the old ceiling user/usertests.c
  5. 5Every teardown frees the stack region kernel/proc.c
  6. 6uvmcopy learns a range kernel/vm.c
  7. 7fork copies two ranges, and sets sz first kernel/proc.c
  8. 8vmfault learns the stack region kernel/vm.c
  9. 9The kernel grows the stack too, without a trap kernel/vm.c
  10. 10An address in lazy_copy is now a stack page user/usertests.c
  11. 11fetchaddr accepts the stack kernel/syscall.c
  12. 12exec starts the stack empty, at the top kernel/exec.c
  13. 13Committing the image, and freeing the old stack kernel/exec.c
  14. 14USERSTACK becomes a maximum kernel/memlayout.h
  15. 15stacktest passes without a change user/usertests.c
  16. 16The test recurses through 18 pages user/growstack.c
  17. 17Overflow, killed one frame above the limit user/growstack.c
  18. 18The kill, and 64 pages freed kernel/trap.c

Keys: ← → step · Home start