[MIT 6.s081] Xv6 Lab 5 (2020): Lazy Page Allocation Record
The content below was generated entirely by machine translation. Please verify its accuracy. If anything is unclear, consult the Chinese source version.
Update on 2022/9/14: I recently put the lab code on GitHub. If you need a reference, you can find it here:
https://github.com/ttzytt/xv6-riscv
The different branches contain the different labs.
Lab 5 (2020): lazy page allocation
Eliminate allocation from sbrk()
Remove the part of thesbrk()system call that actually allocates memory.
There is not much to explain here. Follow the hint and remove the call to growproc():
1 | uint64 |
Naturally, entering echo hi afterward produces a panic.
Lazy allocation
Implement lazy allocation for page tables. When a page fault occurs during trap handling, allocate a page for the faulting address.
The RISC-V manual and lab hints show that values 13 and 15 in the scause register represent page faults caused by attempted reads or writes:
In trap.c, inspect scause and perform additional handling when it is 13 or 15:
1 | …… |
The required handling allocates the missing user page. We can encapsulate it in a function named lazy_alloc().
Although a page fault reports an address, the entire page frame containing that address must be mapped to physical memory. Use PGROUNDDOWN first to find the beginning of that frame.
1 | int lazy_alloc(uint64 va){ |
When calling mappages(), pay attention to the permissions. The page is accessible from user mode, so PTE_U must be set.
After these changes, running echo hi produces a panic in uvmunmap().
With lazy allocation, some pages may never be used before uvmunmap() attempts to remove them. Because such pages were never physically allocated, unmapping them causes a panic. We therefore need to modify uvmunmap():
1 | void |
This completes the basic part of the lab.
Lazytests and Usertests (moderate)
Make the lazy allocation implementation passusertestsandlazytests.
The lazy allocator just written still contains several bugs. This exercise asks us to fix them and pass both test suites.
The hints can be handled one at a time. First, support a negative argument to sbrk().
For a positive amount, we change only the process-size field and do not allocate actual space. For a negative amount, which reduces the process size, memory must truly be released so that other processes can use it. The implementation can be written as follows:
1 | uint64 |
The next hint is:
Kill a process if it page-faults on a virtual memory address higher than any allocated with
sbrk().
That is, when the faulting address was never allocated through sbrk(), the kernel must not allocate a page there; it should kill the process instead.
We can write a helper that determines whether a virtual address belongs to a valid lazily allocated page:
1 | int is_lazy_addr(uint64 va){ |
First, a page with PTE_V set is clearly not lazily allocated because it already has a valid mapping.
Next, if va >= p->sz, the address was never requested through sbrk(), so it is not a valid lazy-allocation address.
Adding this helper to the condition in trap.c gives:
1 | …… |
The next requirement is:
Handle the parent-to-child memory copy in fork() correctly.
This means that memory copying from the parent into the child during fork() must work with lazily allocated pages.
Reading fork() shows that uvmcopy() in vm.c performs this copy. It fails under lazy allocation because some parent page frames have never actually been allocated, and trying to copy them causes a panic. As with uvmunmap(), skip such lazy pages by replacing the panics with continue:
1 | int |
The next hint says:
Handle the case in which a process passes a valid address from sbrk() to a system call such as read or write, but the memory for that address has not yet been allocated.
This hint was honestly hard to understand, and I searched online for a long time. Some system calls write data to a user virtual address, such as write(). If that address is lazy, the access causes a page fault. A user-mode page fault is fine because our handler deals with it. A kernel-mode exception, however, causes an immediate panic, as explained in the xv6 notes.
System calls use copyin() and copyout() to read or write user virtual addresses. Examine one of these functions:
1 | // Copy from user to kernel. |
Both functions call walkaddr() to find the physical address corresponding to a user virtual address. walkaddr() is implemented as:
1 | // Look up a virtual address, return the physical address, |
walkaddr() calls walk() and returns zero immediately when no mapping is found.
The behavior also makes sense from the function’s purpose. A lazily allocated page frame does not yet have any physical address, so looking up that address naturally returns zero.
If va belongs to a lazy page, walk() necessarily returns zero. The following implementation shows why:
1 | pte_t * |
We can modify walkaddr() to check whether the current va belongs to a lazy page. If it does, allocate a physical page before returning zero and then continue the normal lookup. Once physical memory has been allocated, its address can be found.
1 | // Look up a virtual address, return the physical address, |
The fifth hint is:
Handle out-of-memory correctly: if kalloc() fails in the page fault handler, kill the current process.
In other words, if no physical page is available, kill the current process.
This is already implemented in trap.c:
1 | uint64 fault_addr = r_stval(); |
If lazy_alloc() fails because memory is exhausted, the process is killed.
The final hint is:
Handle faults on the invalid page below the user stack.
This requires reviewing the page-table chapter. The following diagram shows the user-mode memory layout:

Immediately below the stack is a guard page whose PTE_V bit is not set. Accessing it from user mode triggers a page fault. That mechanism worked before, but lazy allocation now responds to a fault by allocating physical memory rather than killing the process.
The guard page exists to prevent memory overflow and must not receive a physical page. Add a test to is_lazy_addr(): if an address belongs to the guard page, it is not a valid lazy-allocation address.
1 | if(va < PGROUNDDOWN(p->trapframe->sp) // Use the user stack pointer sp to locate the stack's virtual address. |
After this change, the tests pass. I wish everyone working on this lab an early AC:

Summary
I need to improve my debugging ability. This lab genuinely took me a very long time to debug…







![[Stanford CS144] Lab 4 Record](/img/CS144/tcp%E7%8A%B6%E6%80%81%E6%B5%81%E8%BD%AC%E5%9B%BE.jpg)