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
freewalk
may be inspirational.
这个函数展示了如何去recursively访问page table
void |
- Define the prototype for
vmprint
inkernel/defs.h
so that you can call it fromexec.c
.
- Use
%p
in 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 proc
for 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
kvminit
that 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’ssatp
register (seekvminithart
for inspiration). Don’t forget to callsfence_vma()
after callingw_satp()
.
scheduler()
should usekernel_pagetable
when 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
vmprint
may 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.c
andkernel/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 forXXXXXXXX
inkernel/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 usertests
runs 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_new
first, 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_U
set 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