MIT6.S081 LAB page tables (2024)
page tables
文章目录
- page tables
- Inspect a user-process page table (easy)
- Speed up system calls (easy)
- Print a page table (easy)
- Use superpages (moderate/hard)
HOME MIT 6.S081
Inspect a user-process page table (easy)
题目
这个好像直接现成实现好了
Speed up system calls (easy)
题目
大概的含义就是,有些系统调用其实只是想要拿到系统中的信息,所以想到了将这部分数据放在用户可以访问的位置,这样就不需要再进行系统调用,从而加速了系统调用的时间。
思路
因为这是每个进程可能特有的信息,所以应该添加到进程的属性上,修改proc的定义,然后为它在Trapframe下面分配一页(虚拟地址),所以先给这个usyscall分配一个物理页面,然后将这个物理地址与USYSCALL在页表上建立映射。然后在进程销毁的时候,释放掉这个页,并且在页表上释放对这个页的映射。
实现
先分配一个内存页,用于存储usyscall。
在页表上建立虚拟地址到这个内存页的映射
释放掉分配到的物理页
释放掉在页表上映射的内存空间
Print a page table (easy)
题目
题目大概说的就是将页表中指向的所有分配了物理地址的部分全都按照这种形式输出出来,具体的效果应该类似于上图的下半部分,一级一级的输出页号,然后pte的值以及指向的物理地址。
实现
首先知道每级页表大小都是4096B,页表项都是8B,所以有4096 / 8 = 512个页表项,所以只要去递归遍历每级页表的所有页表项,如果出现有效的指向下一级就往下递归,需要注意的是,下一级要知道上一级查过来的虚拟地址,所以需要一个baseP来还原每个页表项对应的虚拟地址。
实验结果
跟所给结果不一样的是打印的时候没有去除前导0,但实际内容相同
Use superpages (moderate/hard)
题目
题目的大概意思就是xv6中原来每个页面只支持映射4096B的数据,但是如果一个进程需要很多页的时候页面分配查找需要非常多次,从而会导致效率不高。所以现在让我们修改其中的代码实现超级页(一页2MB)的功能,也就是当需要的空间很大的时候可以一次分配一个超级页。
思路
先分析测试的代码,如下图,superpg_test会先通过sbrk再给进程分配Nbyte的空间(超过一个超级页的大小),然后从SUPERPGROUNDUP(end)的地方(这个其实已经是对后面一个大坑的提示了)开始进入supercheck进行测试,测试的逻辑是读写检查是不是分配到了超级页,如果不是超级页的话页面发生变化pte会发生改变。
实现
先不管后面fork的测试部分,先完成fork前的测试点。
这里可以注意到超级页的大小刚好是
2
21
B
=
2
9
×
2
12
B
2^{21}B = 2^{9} \times 2^{12}B
221B=29×212B,而xv6的多级页表结构如下图。所以超级页分配的时候只需要让分到超级页的虚拟地址的L1指向的pte直接指向物理地址(而不是指向下一级页表)就ok了,这样连续的
2
21
B
2^{21}B
221B的虚拟地址能用相同的pte找到映射的物理地址。
因此,首先要能分配到一个
2
21
B
2^{21}B
221B大小的连续内存空间。关于内存空闲空间分配的代码在kernel/kalloc.c
文件中,我们添加如下代码(左侧绿色条的就是修改或新增的代码):
这样就能通过superKalloc来分配一个超级页大小的连续内存空间了。接下来就是看sbrk进入到系统调用做了些什么,发现调用链是kernel/sys_proc.c:sys_sbrk
->kernel/proc.c:growproc
->kernel/vm.c:uvmalloc
。这部分代码原先的逻辑就是给进程分配更多的PAGE,我们需要改的是检查要是一次请求分配的虚拟地址空间足够大而且是能够作为超级页的虚拟地址始址的时候那就分配一个超级页,代码实现如下图。
当能分配超级页的时候就调用mapSuperPage
将分配的超级页记录到页表上,也就是在二级页表项上增加PTE_SPUER
的标记(可以对着上面的多级页表结构理解)。
这样我们就在页表上标记了哪个是超级页,还需要修改的是如果读到为PTE_SPUER
的页表项后,就不用再读第三级页表了,所以去修改kernel/vm.c:walk
现在成功将超级页记录到了页表,而且能够读写其中的数据。有始有终,所以还需要修改一下释放的页表的代码(kernel/vm.c:uvmunmap),理解了前面的这个释放应该就不难。
做到这我们就可以通过fork前面的测试,但是fork后的测试用例还不能通过。这是因为,fork创建的子进程的时候会拷贝原来的进程的数据到新的内存空间,然后创建一个新的页表来做虚拟地址到物理地址的映射(kernel/vm.c:uvmcopy)。问题就出在拷贝到新的内存空间的时候,要改的就是将原来的超级页数据也拷贝到超级页上。所以做如下修改:
至此就可以成功运行测试用例,看到如下结果