lab3 page table
vm.c 源码解析:
1.
kernel/vm.c包含了xv6中绝大部分用于操控地址空间和页表的代码。
offset必须是12bit,因为对应了一个page的4096个字节。单个虚拟地址转换为物理地址太浪费,所以转换都是一个页的大小,27位的index对应物理地址的ppn,offet对应到底是4096中哪一个是被转换的。所以va的index每一个对应一个page。虽然risc中寄存器大小是64比特,但是按照规定,最高位的25位没被使用,所以va有效值才是39位。RISC-V物理内存地址是56bit(56也是规定)。ppn指向下一级页表结构的地址。一级页表当只是用一个page时,还是需要2^27个PTE,因为必须初始化。而三级页表需要一级页表512个,二级页表512,三级页表512个,大大简化。
2.
uvm开头的函数用来操纵用户态地址空间,kvm开头的函数用来操纵内核地址空间。二者都在内核态中运行,使用的都是内核页表。
3.
三个全局变量
pagetable_t kernel_pagetable;
extern char etext[];
extern char trampoline[];
全局变量kernel_pagetable,它是一个pagetable_t类型的变量。定义如下:
typedef uint64 pte_t; //页表项的大小是64位,所以定义为uint64类型
typedef uint64 *pagetable_t; //512 PTEs 一个级别页表有512个PTE,正对应4K页大小
xv6的定义中,一个目录项pet (page table entry, pte)的大小是64比特,即8个字节。一个4k大小的页正好对应512个PTE。 下面的va也是uint64,64比特,8字节大小。
pte的具体结构如下:64比特大小,0-9是flag位, 10-63是PPN。
Valid。如果Valid bit位为1,那么表明这是一条合法的PTE,你可以用它来做地址翻译。
readable。可读
writable。可写
pagetable_t是指向uint64的指针,它本质上指向内核页表的根目录页表(root page-table page)的地址,当使用MMU进行虚拟地址转换时,这个物理地址会被存放在SATP寄存器中。
全局变量etext:定义了一个字节指针,指向内核代码部分的结束位置
全局变量trampoline:一个字节指针,指向了trampoline代码的开始
4.walk函数:
walk可以正常工作的前提是: 进行内核地址空间的映射时,物理内存和虚拟内存采用的是直接映射的方法。xv6内核地址空间中执行的是直接映射策略。
地址空间范围如下:
walk函数如下:
用软件来模拟硬件MMU查找页表的过程,返回以输入的pagetable为根页表(三级页表),经过多级索引之后va(输入参数)这个虚拟地址所对应的页表项(pte_t)。如果alloc != 0(allloc输入参数:),则在需要时创建新的页表页,反之则不用。alloc :发生缺页异常时,当页面项指向的页表页还没有加载时,是否需要创建新的页表页
pte_t * walk(pagetable_t pagetable, uint64 va, int alloc)
{
if(va >= MAXVA) // 如果虚拟地址超过了最大值,陷入错误
panic("walk");
// 模拟三级页表的查询过程,三级列表索引两次页表即可,最后一次直接组成物理地址
for(int level = 2; level > 0; level--) {
// 索引到对应的PTE项
pte_t *pte = &pagetable[PX(level, va)];
// 确认一下索引到的PTE项是否有效(valid位是否为1)
if(*pte & PTE_V) {
// 这行代码从PTE中提取出物理地址,直接赋值给pagetable指针(而它是一个虚拟地址)
// 这样赋值合理吗?只有在虚拟地址==物理地址时合理,即直接映射。
// 如果有效接着进行下一层索引
pagetable = (pagetable_t)PTE2PA(*pte);
} else {
// 如果无效(说明对应页表没有分配)
// 则根据alloc标志位决定是否需要申请新的页表
// 注意