MIT6.S081 XV6 lab3 page tables
Speed up system calls
When each process is created, map one read-only page at USYSCALL (a VA defined in
memlayout.h). At the start of this page, store astruct usyscall(also defined inmemlayout.h), and initialize it to store the PID of the current process. For this lab,ugetpid()has been provided on the userspace side and will automatically use the USYSCALL mapping. You will receive full credit for this part of the lab if theugetpidtest case passes when runningpgtbltest.
Some hints:
- You can perform the mapping in
proc_pagetable()inkernel/proc.c.- Choose permission bits that allow userspace to only read the page.
- You may find that
mappages()is a useful utility.- Don’t forget to allocate and initialize the page in
allocproc().- Make sure to free the page in
freeproc().
加速系统调用,在内核和用户之间建立一个共享的只读页,这样内核往这个页写入数据的时候,用户程序就可以不经过复杂的系统调用,直接读取。
实验要求我们映射一个只读页USYSCALL,用户程序可以直接调用它。用户态调用ugetpid()如下:
1 |
|
读取时直接从USYSCALL这个地址里读取数据。TRAMPOLINE意为蹦床,用来进行trap to the kernel操作的。下面的TRAPFRAME保存了一些进程的参数。USYSCALL是紧挨着trapframe下端的一页。
1 | // map the trampoline page to the highest address, |
内存分布如下所示:
proc_pagetable里面设置映射:
1 | // map the trampoline code (for system call return) |
xv6先给TRAPFRAME分配一块内存,再白TRAPFRAME映射到这块内存上。allocproc()中,首先会搜索进程表,搜索到UNUSED进程就为其分配内存,然后给进程表p赋值:
1 | p->pid = allocpid(); |
具体思路如下:
- 进程表中添加usyscall的成员变量
1 | // Per-process state |
- 为usyscall分配一页内存,并把进程的pid保存到页表中。
1 | // allocate a usyscall page |
- 添加映射。如果映射失败,需要释放
TRAMPOLINE和TRAPFRAME的映射
1 | // map the usyscall just below TRAMPOLINE, for trampoline.S. |
freeproc的时候需释放usyscall页
1 | if(p->usyscall) |
proc_freepagetable取消映射页表的时候,添加USYSCALL的映射关系
1 | // Free a process's page table, and free the |
Print a page table
Define a function called
vmprint(). It should take apagetable_targument, and print that pagetable in the format described below. Insertif(p->pid==1) vmprint(p->pagetable)in exec.c just before thereturn argc, to print the first process’s page table. You receive full credit for this part of the lab if you pass thepte printouttest ofmake grade.
Now when you start xv6 it should print output like this, describing the page table of the first process at the point when it has just finished
exec()inginit:
1
2
3
4
5
6
7
8
9
10
11
12 page table 0x0000000087f6e000
..0: pte 0x0000000021fda801 pa 0x0000000087f6a000
.. ..0: pte 0x0000000021fda401 pa 0x0000000087f69000
.. .. ..0: pte 0x0000000021fdac1f pa 0x0000000087f6b000
.. .. ..1: pte 0x0000000021fda00f pa 0x0000000087f68000
.. .. ..2: pte 0x0000000021fd9c1f pa 0x0000000087f67000
..255: pte 0x0000000021fdb401 pa 0x0000000087f6d000
.. ..511: pte 0x0000000021fdb001 pa 0x0000000087f6c000
.. .. ..509: pte 0x0000000021fdd813 pa 0x0000000087f76000
.. .. ..510: pte 0x0000000021fddc07 pa 0x0000000087f77000
.. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000
The first line displays the argument to
vmprint. After that there is a line for each PTE, including PTEs that refer to page-table pages deeper in the tree. Each PTE line is indented by a number of" .."that indicates its depth in the tree. Each PTE line shows the PTE index in its page-table page, the pte bits, and the physical address extracted from the PTE. Don’t print PTEs that are not valid. In the above example, the top-level page-table page has mappings for entries 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.Your code might emit different physical addresses than those shown above. The number of entries and the virtual addresses should be the same.
- You can put
vmprint()inkernel/vm.c.- Use the macros at the end of the file kernel/riscv.h.
- The function
freewalkmay be inspirational.- Define the prototype for
vmprintin kernel/defs.h so that you can call it from exec.c.- Use
%pin your printf calls to print out full 64-bit hex PTEs and addresses as shown in the example.
xv6使用的是三级页表,如果页表项的PTE_V这一位是1,则该页表项是有效的,可以根据次页表项内的地址访问下一集页表。采用DFS遍历即可。
实现如下:
1 | void vmprint(pagetable_t pagetable, int depth) |
Detecting which pages have been accessed
Your job is to implement
pgaccess(), a system call that reports which pages have been accessed. The system call takes three arguments. First, it takes the starting virtual address of the first user page to check. Second, it takes the number of pages to check. Finally, it takes a user address to a buffer to store the results into a bitmask (a datastructure that uses one bit per page and where the first page corresponds to the least significant bit). You will receive full credit for this part of the lab if thepgaccesstest case passes when runningpgtbltest.
Some hints:
- Start by implementing
sys_pgaccess()inkernel/sysproc.c.- You’ll need to parse arguments using
argaddr()andargint().- For the output bitmask, it’s easier to store a temporary buffer in the kernel and copy it to the user (via
copyout()) after filling it with the right bits.- It’s okay to set an upper limit on the number of pages that can be scanned.
walk()inkernel/vm.cis very useful for finding the right PTEs.- You’ll need to define
PTE_A, the access bit, inkernel/riscv.h. Consult the RISC-V manual to determine its value.- Be sure to clear
PTE_Aafter checking if it is set. Otherwise, it won’t be possible to determine if the page was accessed since the last timepgaccess()was called (i.e., the bit will be set forever).vmprint()may come in handy to debug page tables.
实验要求:从一个用户页表地址开始,搜索所有被访问过的页并返回一个bitmap来显示这些页是否被访问过。第一个参数传入页表的地址,第二个参数传入需要查询的页表数,第三个是返回的结果变量的用户空间地址。
参考:walk(pagetable_t pagetable, uint64 va, int alloc)函数传入页表和虚拟地址,返回PTE在页表中的地址,如果alloc不等于 0,则创建任何需要的页。
在sysproc.c中完善sys_pgaccess():
1 |
|
在vm.c中添加函数:
1 | uint |





