One of the many neat tricks an OS can play with page table hardware
is lazy allocation of heap memory. Xv6 applications ask the kernel
for heap memory using the sbrk
system call. In the kernel we’ve
given you, sbrk
allocates physical memory and maps it into the
process’s virtual address space. There are programs that allocate
memory but never use it, for example to implement large sparse
arrays. Sophisticated kernels delay allocation of each page of
memory until the application tries to use that page—as signaled
by a page fault. You’ll add this lazy allocation feature to xv6 in
this lab.
To start the lab, update your repository and create a new branch for your solution:
It’s often worthwhile to invest time writing code that helps
debugging, so your first task is to implement a function that prints
the contents of a page table. Define the function in kernel/vm.c
;
it has the following prototype: void vmprint(pagetable_t)
.
This function will be handy for debugging and will make you familiar
with RISC-V page tables. Insert a call to vmprint
in exec.c
to print the page table for the first user process.
The output of vmprint
for the first user-level process should be as follows:
The first line prints the address of the argument of vmprint
. Each
PTE line shows the PTE index in its page directory, the pte, the
physical address for the PTE. The output should also indicate the
level of the page directory: the top-level entries are preceeded
by “..”, the next level down with another “..”, and so on. You
should not print entries that are not mapped. In the above example,
the top-level page directory has mappings for entry 0 and 255. The
next level down for entry 0 has only index 0 mapped, and the
bottom-level for that index 0 has entries 0, 1, and 2 mapped. Some
hints:
kernel/riscv.h
.freewalk
may be inspirational.vmprint
in kernel/defs.h
so that you can call it from exec.c
.Delete page allocation from the sbrk(n)
system
call implementation, which is the function sys_sbrk()
in sysproc.c
.
The sbrk(n)
system call grows the process’s memory size by n
bytes,
and then returns the start of the newly allocated region (i.e., the
old size). Your new sbrk(n)
should just increment the process’s
size (myproc()->sz
) by n
and return the old size. It should not
allocate memory—so you should delete the call to growproc()
(but
you still need to increase the process’s size!).
Try to guess what the result of this modification will be: what will break?
Make this modification, boot xv6, and type echo hi to the shell. You should see something like this:
The “usertrap(): …” message is from the user trap handler in
trap.c
; it has caught an exception that it does not know how to
handle. Make sure you understand why this page fault occurs. The
“stval=0x0..04008” indicates that the virtual address that caused
the page fault is 0x4008
.
Modify the code in trap.c
to respond to a page fault from user space
by mapping a newly allocated page of physical memory at the faulting
address, and then returning back to user space to let the process
continue executing. You should add your code just before the printf
call that produced the “usertrap(): …” message. Your solution is
acceptable if it passes both lazytests
and usertests
.
Hints:
r_scause()
is 13 or 15 in usertrap()
.printf()
in usertrap()
that reports the page fault,
in order to see how to find the virtual address that caused the page fault.uvmalloc()
in vm.c
, which is what sbrk()
calls (via growproc()
).
You’ll need to call kalloc()
and mappages()
.PGROUNDDOWN(va)
to round the faulting virtual address down to a page boundary.uvmunmap()
will panic; modify it to not panic if some pages aren’t mapped.sepc
in kernel/kernel.asm
.vmprint
function from above to print the content of a page table.proc.h
(and spinlock.h
).sbrk()
test in usertests
allocates something large; this should succeed now.If all goes well, your lazy allocation code should result in echo hi working. You should get at least one page fault (and thus lazy allocation) in the shell, and perhaps two.
If you have the basics working, now turn your implementation into one that handles the corner cases too:
sbrk()
arguments.sbrk()
.fork()
correctly.sbrk()
to a system call such as read
or write
, but the memory for
that address has not yet been allocated.kalloc()
fails in the page
fault handler, kill the current process.Your solution is acceptable if your kernel passes both lazytests
and usertests
:
You may run make grade to ensure that your code passes all of the tests.
This completes the lab. In the lab directory, commit your changes, type make tarball, and submit the tarball through Canvas.