实验概述¶
温(守)馨(住)提(红)示(线)
本课程实验已引入代码自动查重系统,请同学们保持学术诚信!
课程复习和预习要求
本节实验与理论课的 “虚拟内存” 和 “内存管理” 这两章课程内容相关,请同学们复习这两章课程内容。
在做实验之前,请同学们阅读xv6手册的以下章节及相关源代码:
- [1] xv6 book, Chapter 3 Page tables (页表)
- [2] kernel/memlayout.h (定义内存的布局)
- [3] kernel/vm.c (虚拟内存代码)
- [4] kernel/kalloc.c (分配和释放物理内存代码)
此外,特别推荐同学们观看由我校OS课题组、各级学长助教们合力为大家录制的XV6讲解视频,希望能够让大家对整个XV6系统有一个更为直观的认识,推荐大家按需观看:
-
【HITSZ操作系统课程组讲解XV6(二)进程管理】
-
【HITSZ操作系统课程组讲解XV6(三)内存管理】
提示
本节实验将探索内核的内存管理模块,并且需要修改相应的部分。建议阅读完所有部分,再开始代码的编写。
0. 实验分数¶
本实验有三个任务,作为最后一个MIT xv6实验,任务二和任务三的难度相比之前来说有 明显提高 ,但是我们希望大家能够尽量完成,如果实在无法完成,也可以给出你的思路,以尽可能拿到分数,即使任务二和任务三的分数占比不高。
1. 实验报告
- 回答实验中的问题 40%;
- 给出实验设计思路 60%:任务一:30%、任务二:20%、任务三:10%
若对应任务未给出实验设计,那么对应任务代码分记0分。
2. 实验代码
- 任务一:50%
- 任务二:30%
- 任务三:20%
若对应任务代码无法通过测试,那么实验报告中对应任务的设计分最高只能得到满分的50%。 因此,即使没有实现,你也应该尽可能地给出你的思路。
1. 实验目的¶
- 了解页表的实现原理。
- 修改页表,使内核更方便的进行用户虚拟地址翻译。
2. 实验学时¶
本实验为4学时。
3. 实验内容及要求¶
请先同步上游远程仓库,并注意切换到pgtbl分支进行试验
本次实验基于 pgtbl 分支,请同学们注意切换。
Step 1. 首先,保存实验三的代码,请参考实验实用工具的3.3.1 使用命令行完成操作或者3.3.2 使用VSCode内建的图形化界面完成操作这两小节,完成commit操作。
Step 2. 然后,参考“Lab2:进程与系统调用”的3.1 切换分支进行切换。
同时,本次实验的 输出格式不同于MIT原版实验,请严格按照指导书要求!
3.1 任务一:打印页表¶
本任务中,你需要加入页表打印功能,来帮助你在之后的实验中进行debug。
首先,我们需要了解页表的数据结构。然后,按层次每页打印即可,可以采用 递归 的算法。
3.1.1 打印函数定义¶
void vmprint(pagetable_t pgtbl)
该函数将获取一个 根页表指针 作为参数,然后打印对应的页表数据。
函数的使用位置 :注意!该函数一定要插入在exec()
逻辑结束的末尾,来打印第一个进程或刚载入程序的页表数据。在开发过程中,你可以插入到其他的地方,但是最后一定只保留这一个地方的函数使用。
- 在exec.c中的返回argc之前插入
vmprint()
函数,以输出第一个进程或刚载入程序的页表。其中,vmprint()
函数的入参可以根据你自己的设计来填入。

3.1.2 结果示例¶
当xv6启动的时候,它自身会调用 exec()
启动第一个进程 init
,这个时候我们的函数会得到以下的输出:
xv6 kernel is booting
hart 1 starting
hart 2 starting
page table 0x0000000087f25000
||idx: 0: pa: 0x0000000087f21000, flags: ----
|| ||idx: 0: pa: 0x0000000087f20000, flags: ----
|| || ||idx: 0: va: 0x0000000000000000 -> pa: 0x0000000087f22000, flags: rwxu
|| || ||idx: 1: va: 0x0000000000001000 -> pa: 0x0000000087f1f000, flags: rwx-
|| || ||idx: 2: va: 0x0000000000002000 -> pa: 0x0000000087f1e000, flags: rwxu
||idx: 255: pa: 0x0000000087f24000, flags: ----
|| ||idx: 511: pa: 0x0000000087f23000, flags: ----
|| || ||idx: 510: va: 0x0000003fffffe000 -> pa: 0x0000000087f76000, flags: rw--
|| || ||idx: 511: va: 0x0000003ffffff000 -> pa: 0x0000000080007000, flags: r-x-
init: starting sh
$
我们先不着急动手,先看看结果长什么样(不清楚页表的含义先不急,实验原理会告诉你)。
一些注意事项
由于xv6不支持%c, 因此打印字符的时候请通过将字符转化为字符串的方式,使用%s格式化字符串 。
- 第一行打印的是
vmprint
的参数,即获得的页表参数具体的值。 - 在之后打印的则是页表项。RISC-V的页表被设计成了三层(具体见实验原理),每一个”||“都代表一层。
- 打印的格式为:(注意 冒号后面都接一个空格 )
- 如果是非叶节点,则为:
idx: [索引编号]: pa: [物理地址], flags: [四个权限位(r/w/x/u)]
- 如果是叶子节点,则为:
idx: [索引编号]: va: [虚拟地址] -> pa: [物理地址], flags: [四个权限位(r/w/x/u)]
- 如果是非叶节点,则为:
- 上述的详细含义为:
索引编号
: 指示了该页表项在当前等级页表内的序号(取值范围:0-511);物理地址
:指示了这个页表项对应的十六进制物理地址;flags的四个权限位
:指示了这个页表项的flags,包括读(R)、写(W)、执行(X)、用户态(U)
- 只打印有效的pte。
在上面的示例中,根页表项只有第0项和第255项的映射是有效的,其中第0项的次页表只映射了索引0,该索引0映射了叶子页表的0、1和2。
你的代码输出的物理地址与上述示例可能不相同,但显示项数和虚拟地址应相同。
3.1.3 测试¶
运行make grade
,其中的pte printout
测试就是该任务的测试(可参考测评 3.4)。
3.2 任务二:独立内核页表(中等)¶
目前,xv6的每个进程都有自己独立的 用户页表 (只包含该进程用户内存的映射,从虚拟地址0开始),但是每个进程进入内核的时候,会使用唯一的一个 全局共享内核页表 。我们需要 将全局共享内核页表改成独立内核页表 ,使得每个进程拥有自己独立的内核页表,也就是全局共享内核页表的副本。
这部分有一定的难度,因此指导书会尽可能给出多的描述,大家应反复阅读,并试图将知识点串联起来。
3.2.1 独立页表的背景¶
共享内核页表中,所有物理地址都和与之完全相等的虚拟地址建立映射,也就是直接映射。这是让内核能够直接以物理地址访问内存的数据,不需要使用任何的虚拟地址。
但是 ,由于用户地址的映射并未存储于内核页表,如果我们需要处理用户程序传来的虚拟地址(比如系统调用传入的指针),我们需要先找到用户页表,逐个页表项地找到能够翻译对应虚拟地址的页表项后,才可以获取实际的物理地址并进行访问,这叫做软件模拟翻译。软件模拟翻译的实现很复杂,同时,因为需要复杂的查找,还降低了性能。
所以我们将 用户页表中的内存映射 和 原来共享内核页表中的内存映射 进行合并,这样内核也能够直接对用户的虚拟地址进行访问,而不需要软件模拟翻译。需要注意的是,这是 任务三和任务二一同 需要实现的结果,任务二分离出独立页表,任务三利用独立页表去除软件模拟翻译。
3.2.2 独立页表的要求¶
共享内核页表的映射:虚实地址相同,也就是直接映射。
独立内核页表的映射:虚实地址相同的映射应该要保留, 先不需要 加上用户页表的内容,在 任务三 中再加上加上用户页表的内容。
同时还需要修改有关的操作。
3.2.3 测试¶
首先,在xv6运行 kvmtest
,如果你确实使用了独立内核页表,会看到以下结果:

然后,请在xv6运行 usertests
,确保所有测试通过(显示"ALL TESTS PASSED")。
3.3 任务三:简化软件模拟地址翻译(难)¶
xv6目前使用kernel/vm.c
中的copyin()/copyinstr()
将用户地址空间的数据拷贝至内核地址空间,它们通过软件模拟翻译的方式获取用户空间地址对应的物理地址,然后进行复制(详情见代码注释)。
任务目标:你需要 在独立内核页表加上用户地址空间的映射,同时将函数 copyin()/copyinstr()
中的软件模拟地址翻译改成直接访问 ,使得内核能够不必花费大量时间,用软件模拟的方法一步一步遍历页表,而是直接利用硬件。
3.3.1 任务要求¶
- 要求提供了新的函数
copyin_new()/copyinstr_new()
(在kernel/vmcopyin.c
中定义)。你需要找到调用copyin()
(在kernel/vm.c
中定义)的地方,用copyin_new()
替换之 。同样地,用copyinstr_new()
代替copyinstr()
。 - 在独立内核页表加上用户页表的映射,以保证刚刚替换地新函数能够使用。但是要注意地址重合问题,见“实验原理 3.5”.
3.3.2 结果示例¶
首先,在xv6运行 stats stats
,如果你正确使用了copyin_new
和copyinstr_new
,会看到输出的数值不为零:

然后,请在xv6运行 usertests
,确保所有测试通过(显示"ALL TESTS PASSED")。
3.4 测评¶
当完成上述的三个实验后,在命令行输入 make grade
进行测试。如果通过测试,会显示如下内容:
