page tables [os lab3]
Print a page table
To help you learn about RISC-V page tables, and perhaps to aid future debugging, your first task is to write a function that prints the contents of a page table.
Define a function called vmprint(). It should take a pagetable_t argument, and print that pagetable in the format described below. Insert if(p->pid==1) vmprint(p->pagetable) in exec.c just before the return argc, to print the first process’s page table. You receive full credit for this assignment if you pass the pte printout test of make 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()ing init:
page table 0x0000000087f6e000 |
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.
Some hints
- You can put
vmprint()inkernel/vm.c.
- Use the macros at the end of the file
kernel/riscv.h.
- The function
freewalkmay be inspirational.
这个函数展示了如何去recursively访问page table
void |
- Define the prototype for
vmprintinkernel/defs.hso that you can call it fromexec.c.
- Use
%pin your printf calls to print out full 64-bit hex PTEs and addresses as shown in the example.
Final Code
想法比较自然,写一个递归函数vmprintRecursive去深度遍历page table, 因为打印.. 的原因,还提供了一个函数参数level
这个深度遍历函数的逻辑是: 从0-511去查看page table entry
如果查到了pte并且他是valid的, 就通过PTE2PA获取指向的physical address。这个时候打印题目所要求的内容
更进一步,如果这个pte不是PTE_R, PTE_W, PTE_X没有被置位,说明这个pte指向了下一层page table的physical address, 递归去访问。
void vmprintRecursive(pagetable_t pagetable, int level) { |
A kernel page table per process
Xv6 has a single kernel page table that’s used whenever it executes in the kernel. The kernel page table is a direct mapping to physical addresses, so that kernel virtual address x maps to physical address x. Xv6 also has a separate page table for each process’s user address space, containing only mappings for that process’s user memory, starting at virtual address zero. Because the kernel page table doesn’t contain these mappings, user addresses are not valid in the kernel. Thus, when the kernel needs to use a user pointer passed in a system call (e.g., the buffer pointer passed to write()), the kernel must first translate the pointer to a physical address. The goal of this section and the next is to allow the kernel to directly dereference user pointers.
Xv6 has a single kernel page table that’s used whenever it executes in the kernel. The kernel page table is a direct mapping to physical addresses, so that kernel virtual address x maps to physical address x. Xv6 also has a separate page table for each process’s user address space, containing only mappings for that process’s user memory, starting at virtual address zero. Because the kernel page table doesn’t contain these mappings, user addresses are not valid in the kernel. Thus, when the kernel needs to use a user pointer passed in a system call (e.g., the buffer pointer passed to write()), the kernel must first translate the pointer to a physical address. The goal of this section and the next is to allow the kernel to directly dereference user pointers.
这个实验中修改的部分有vm.c, proc.h, proc.c, defs.h
Some hints
- Add a field to
struct procfor the process’s kernel page table.
- A reasonable way to produce a kernel page table for a new process is to implement a modified version of
kvminitthat makes a new page table instead of modifyingkernel_pagetable. You’ll want to call this function fromallocproc.
allocproc函数分配并且初始化struct proc, 因为我们在struct proc中增加了kernel page table, 因此需要在这里进行初始化。
在kernel/vm.c中kvminit函数创建了direct-map kernel page table。在kernel/proc.c中仿照kvminit函数新写一个proc_kpagetable函数来创建process中的kernel page table
pagetable_t |
在kernel/proc.c的allocproc函数中调用proc_kpagetable函数
// An empty user page table. |
- Make sure that each process’s kernel page table has a mapping for that process’s kernel stack. In unmodified xv6, all the kernel stacks are set up in
procinit. You will need to move some or all of this functionality toallocproc.
修改kernel/proc.c中的procinit函数, 注释掉分配kernel stack的部分
void |
将这部分代码移动到allocproc中
// Allocate a page for the process's kernel stack. |
注意: 这里将kvmmap函数改成了ukvmmap函数, kvmmap函数默认操作kernel_pagetable, 而这里我们想要在process的kpagetable中进行映射, 因此增加了一个ukvmmap函数,多提供了一个pagetable的参数
// add a mapping to the kernel page table. |
- Modify
scheduler()to load the process’s kernel page table into the core’ssatpregister (seekvminithartfor inspiration). Don’t forget to callsfence_vma()after callingw_satp().
scheduler()should usekernel_pagetablewhen no process is running.
注意, 在kernel/vm.c中,kvmpa函数会在进程执行期间调用,这个函数不应调用全局kernel page table, 而应调用进程对应的kernel page table
// translate a kernel virtual address to |
为了使用myproc()函数 ,还需要在头文件中
- Free a process’s kernel page table in
freeproc. - You’ll need a way to free a page table without also freeing the leaf physical memory pages.
修改kernel/proc.c中的freeproc函数。free kernel stack, free kernel page table
- free kstack是通过kpage table去查找kstack对应的物理地址,然后free
- free kernel page table是通过
kernel/proc.c中一个新写的函数proc_freekpagetable来实现的。这个函数借鉴kernel/vm.c中的freewalk函数 。注意这里只free kernel page table, 不能把page table指向的地址free掉,也就是不能free leaves
void |
// free a proc structure and the data hanging from it, |
注意这里要先free kstack。如果先free pagetable, 再free kstack, 则会出现error
vmprintmay come in handy to debug page tables.
- It’s OK to modify xv6 functions or add new functions; you’ll probably need to do this in at least
kernel/vm.candkernel/proc.c. (But, don’t modifykernel/vmcopyin.c,kernel/stats.c,user/usertests.c, anduser/stats.c.)
- A missing page table mapping will likely cause the kernel to encounter a page fault. It will print an error that includes
sepc=0x00000000XXXXXXXX. You can find out where the fault occurred by searching forXXXXXXXXinkernel/kernel.asm.
Final Code
-
在
kernel/defs.h中补上一些函数的声明
-
kernel/proc.h
struct proc { |
kernel/vm.c
void |
uint64 |
kernel/proc.c
extern char etext[]; // kernel.ld sets this to end of kernel code. |
void |
static struct proc* |
static void |
pagetable_t |
void |
void |
Simplify copyin/copyinstr
The kernel’s copyin function reads memory pointed to by user pointers. It does this by translating them to physical addresses, which the kernel can directly dereference. It performs this translation by walking the process page-table in software. Your job in this part of the lab is to add user mappings to each process’s kernel page table (created in the previous section) that allowcopyin (and the related string function copyinstr) to directly dereference user pointers.
Replace the body of copyin in kernel/vm.c with a call to copyin_new (defined in kernel/vmcopyin.c); do the same for copyinstr and copyinstr_new. Add mappings for user addresses to each process’s kernel page table so that copyin_new and copyinstr_new work. You pass this assignment if usertestsruns correctly and all the make grade tests pass.
This scheme relies on the user virtual address range not overlapping the range of virtual addresses that the kernel uses for its own instructions and data. Xv6 uses virtual addresses that start at zero for user address spaces, and luckily the kernel’s memory starts at higher addresses. However, this scheme does limit the maximum size of a user process to be less than the kernel’s lowest virtual address. After the kernel has booted, that address is 0xC000000 in xv6, the address of the PLIC registers; see kvminit() in kernel/vm.c, kernel/memlayout.h, and Figure 3-4 in the text. You’ll need to modify xv6 to prevent user processes from growing larger than the PLIC address.
为了让kernel page table直接能够translate user space address, 需要把user pagetable的信息复制到kernel pagetable中。
这里要回答两个问题
- 怎么将user pagetable的信息复制到kernel page table (通过
ukvmcopy函数来解决这个问题) - 哪些函数需要生成了/改变了address mapping, 需要将user pagetable的信息复制到kernel page table (
userinit,fork,exec,sbrk)
Some hints
- Replace
copyin()with a call tocopyin_newfirst, and make it work, before moving on tocopyinstr.
修改kernel/vm.c中的copyin和copyin_new函数
- At each point where the kernel changes a process’s user mappings, change the process’s kernel page table in the same way. Such points include
fork(),exec(), andsbrk(). - What permissions do the PTEs for user addresses need in a process’s kernel page table? (A page with
PTE_Uset cannot be accessed in kernel mode.) - Don’t forget about the above-mentioned PLIC limit
这几个函数都需要将user space中的地址映射拷贝到kernel page table。因此在kernel/vm.c中增加一个函数ukvmcopy来完成这项工作。
从user space的virtual address的起始地址开始,通过walk函数在pagetable中找到地址对应的page table entry, 然后在kernel page table中为这个page分配一个kernel page table entry。要注意对flag的处理,如果PTE_U置位,在kernel mode下是无法访问的。
void ukvmcopy(pagetable_t pagetable, pagetable_t kpagetable, uint64 beginsz, uint64 endsz) { |
接下来在fork, exec, sbrk的代码使用这个函数,将user page table的信息记录到kernel page table.
对于fork(), 修改kernel/proc.c中的fork函数
对于exec, 修改kernel/exec.c中的exec函数

对于sbrk
kernel/sysproc.c中的sys_sbrk是这样定义的
uint64 |
主要工作是在kernel/proc.c的growproc函数中完成的, 因此把代码加在growproc中
growproc函数在扩张的时候要注意不能超过PLIC。
使用ukvmcopy的时候要注意这里的sz是扩张或者收缩n bytes之后的sz
- Don’t forget that to include the first process’s user page table in its kernel page table in
userinit.
在创建第一个user process的时候就要将user page table复制到kernel page table中
对了!不要忘记在kernel/defs.h中声明ukvmcopy, copyin_new, copyinstr_new函数
Reference
[1]https://github.com/gaofanfei/xv6-riscv-fall20/tree/pgtbl
[2]https://blog.csdn.net/u013577996/article/details/109582932




