本实验探索如何使用陷阱来实现系统调用。你将首先完成关于栈的热身练习,然后实现一个用户级陷阱处理的示例。
在开始编码之前,请阅读 xv6 book 的第4章,以及相关源文件:
开始实验前,切换到 trap 分支:
$ git fetch $ git checkout traps $ make clean
了解一些 RISC-V 汇编很重要,你在 6.004 中已经接触过。你的 xv6 仓库中有一个 user/call.c 文件。make fs.img 会编译它,并在 user/call.asm 中生成该程序的可读汇编版本。
阅读 call.asm 中 g、f 和 main 函数的代码。RISC-V 的指令手册在参考页面上。以下是你应该回答的一些问题(将答案存储在 answers-traps.txt 文件中):
运行以下代码。
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
输出是什么?
这是一个 ASCII 码表,映射字节到字符。
输出取决于 RISC-V 是小端序这一事实。如果 RISC-V 是大端序,你应该将 i 设置为什么值才能得到相同的输出?你需要将 57616 改为不同的值吗?
在以下代码中,'y=' 之后会打印什么?(注意:答案不是一个具体的值。)为什么会这样?
printf("x=%d y=%d", 3);
在调试时,拥有回溯信息通常很有用:它是栈上从错误发生点往上的函数调用列表。
在 kernel/printf.c 中实现一个 backtrace() 函数。在 sys_sleep 中插入对该函数的调用,然后运行 bttest,它会调用 sys_sleep。你的输出应该如下所示:
backtrace: 0x0000000080002cda 0x0000000080002bb6 0x0000000080002898在 bttest 之后退出 qemu。在你的终端中:地址可能略有不同,但如果你运行 addr2line -e kernel/kernel(或 riscv64-unknown-elf-addr2line -e kernel/kernel)并粘贴上述地址,如下所示:
$ addr2line -e kernel/kernel
0x0000000080002de2
0x0000000080002f4a
0x0000000080002bfc
Ctrl-D
你应该看到类似这样的内容:
kernel/sysproc.c:74
kernel/syscall.c:224
kernel/trap.c:85
编译器在每个栈帧中放置一个帧指针,保存调用者的帧指针地址。你的 backtrace 应该使用这些帧指针沿栈向上遍历,并打印每个栈帧中保存的返回地址。
一些提示:
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x) );
return x;
}
并在 backtrace 中调用此函数来读取当前帧指针。该函数使用内联汇编来读取 s0。
当你的回溯功能正常工作后,从 kernel/printf.c 中的 panic 调用它,这样当内核 panic 时你就能看到内核的回溯信息。
在本练习中,你将向 xv6 添加一个功能,在进程使用 CPU 时间时定期发出警报。这对于想要限制 CPU 时间消耗量的计算密集型进程可能很有用,或者对于想要计算但也想定期执行某些操作的进程也很有用。更一般地说,你将实现一种原始形式的用户级中断/异常处理程序;例如,你可以使用类似的方法来处理应用程序中的缺页异常。如果你的解决方案通过了 alarmtest 和 usertests,就是正确的。
你应该添加一个新的 sigalarm(interval, handler) 系统调用。如果应用程序调用 sigalarm(n, fn),那么在程序每消耗 n 个 CPU 时间"滴答"后,内核应该调用应用程序函数 fn。当 fn 返回时,应用程序应该从中断处继续执行。滴答是 xv6 中一个相当随意的时间单位,由硬件定时器产生中断的频率决定。如果应用程序调用 sigalarm(0, 0),内核应该停止产生周期性闹钟调用。
你会在你的 xv6 仓库中找到一个 user/alarmtest.c 文件。将其添加到 Makefile 中。在你添加 sigalarm 和 sigreturn 系统调用之前(见下文),它无法正确编译。
alarmtest 在 test0 中调用 sigalarm(2, periodic) 来请求内核每 2 个滴答强制调用一次 periodic(),然后自旋一段时间。你可以在 user/alarmtest.asm 中查看 alarmtest 的汇编代码,这对调试可能很有用。当 alarmtest 产生如下输出且 usertests 也能正确运行时,你的解决方案就是正确的:
$ alarmtest test0 start ........alarm! test0 passed test1 start ...alarm! ..alarm! ...alarm! ..alarm! ...alarm! ..alarm! ...alarm! ..alarm! ...alarm! ..alarm! test1 passed test2 start ................alarm! test2 passed $ usertests ... ALL TESTS PASSED $
完成后,你的解决方案只需要几行代码,但可能很难写对。我们将使用原始仓库中的 alarmtest.c 版本来测试你的代码。你可以修改 alarmtest.c 来帮助调试,但确保原始 alarmtest 显示所有测试都通过。
首先修改内核以跳转到用户空间中的闹钟处理程序,这将使 test0 打印 "alarm!"。先不要担心 "alarm!" 输出之后会发生什么;如果你的程序在打印 "alarm!" 后崩溃,目前是可以的。以下是一些提示:
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);
if(which_dev == 2) ...
make CPUS=1 qemu-gdb
alarmtest 很可能在打印 "alarm!" 后在 test0 或 test1 中崩溃,或者 alarmtest(最终)打印 "test1 failed",或者 alarmtest 在没有打印 "test1 passed" 的情况下退出。要修复这个问题,你必须确保当闹钟处理程序完成时,控制返回到用户程序最初被定时器中断的指令处。你必须确保寄存器内容恢复到中断时的值,以便用户程序在闹钟之后能够不受干扰地继续执行。最后,你应该在每次闹钟触发后重新"设置"闹钟计数器,使处理程序被周期性地调用。
作为起点,我们已经为你做了一个设计决定:用户闹钟处理程序在完成后必须调用 sigreturn 系统调用。查看 alarmtest.c 中的 periodic 作为示例。这意味着你可以在 usertrap 和 sys_sigreturn 中添加代码,使它们协作让用户进程在处理完闹钟后正确恢复。
一些提示:
通过了 test0、test1 和 test2 后,运行 usertests 确保你没有破坏内核的其他部分。
>
实验到此完成。确保你通过了所有 make grade 测试。如果本实验有问题,不要忘记在 answers-lab-name.txt 中写下你的答案。提交你的更改(包括添加 answers-lab-name.txt),然后在实验目录中运行 make handin 来提交你的实验。
创建一个新文件 time.txt,在其中写入一个整数,即你在本实验上花费的小时数。不要忘记 git add 和 git commit 该文件。
提交实验
花费时间
提交
你将使用提交网站来提交你的作业。在提交任何作业或实验之前,你需要从提交网站申请一次 API 密钥。
在提交你对实验的最终更改后,运行 make handin 来提交你的实验。
$ git commit -am "ready to submit my lab"
[util c2e3c8b] ready to submit my lab
2 files changed, 18 insertions(+), 2 deletions(-)
$ make handin
tar: Removing leading `/' from member names
Get an API key for yourself by visiting https://6828.scripts.mit.edu/2020/handin.py/
Please enter your API key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 79258 100 239 100 79019 853 275k --:--:-- --:--:-- --:--:-- 276k
$
make handin 会将你的 API 密钥存储在 myapi.key 中。如果你需要更改 API 密钥,只需删除此文件并让 make handin 重新生成(myapi.key 不能包含换行符)。
如果你运行 make handin 时有未提交的更改或未跟踪的文件,你将看到类似以下的输出:
M hello.c ?? bar.c ?? foo.pyc Untracked files will not be handed in. Continue? [y/N]检查上述行,确保你的实验解决方案所需的所有文件都已跟踪,即不在以 ?? 开头的行中列出。你可以使用 git add filename 让 git 跟踪你创建的新文件。
如果 make handin 无法正常工作,请尝试修复 curl 或 Git 命令的问题。或者你可以运行 make tarball。这将为你生成一个 tar 文件,然后你可以通过我们的网页界面上传。