2026-07-31 至 2026-08-11 期间完成 2025 年的 MIT 6.1810 的 xv6 课程。
MIT 6.1810 之前叫做 MIT 6.S081,课程围绕 xv6 讲操作系统。
- 课程网站:https://pdos.csail.mit.edu/6.828/2025/schedule.html
- CSDIY: <csdiy.wiki/操作系统/MIT6.S081>
- 我的实现:https://github.com/z0z0r4/xv6-labs-2025
xv6 是一个教学目的简化的 RISC-V 操作系统,提供完整的仓库和手册。
CSDIY 上面有许多参考实现和前人的笔记,但是由于记录都比较老,在 2025 年课程已经有部分更新,遂在此提供一份。
我没有看过课程的视频和官方笔记,在做 Lab 之前花了一周时间看 OSTEP (Operating Systems: Three Easy Pieces),这也是一本著名教材。
Lab 主要有以下:
按照 Tools 安装好必要的工具之后,记得检查 Guidance 中后续要用到的 RISCV GDB 调试时否可用。
Lab Utilities
首先克隆 xv6-labs-2025 仓库。
1 | git clone git://g.csail.mit.edu/xv6-labs-2025 |
如果 Tools 安装没问题,使用 make qemu 试着编译运行,应该得到以下输出。
1 | make qemu |
试着 ls 可以列出所有能用的程序。
1 | $ ls |
接下来可以正式进入正题,试着实现以下的用户程序。
sleepsixfivememdumpfindexec
基本上,我们在接下来的过程中,时不时就会去翻阅 xv6 的各处源代码。主要分为 kernel 和 user 两个部分。user 部分调用各种系统调用,然后再 kernel 里面实现这些系统调用。在当前 Util Lab 还不需要去实现,只需要调用就行了。
你可以在 user/user.h 中找到需要的调用:
1 |
|
除此之外,我们没有 libc。但好在 user/ulib.c 已经提供了不少有用的函数。如果你觉得自己缺少某个方法,很可能只是没注意到它在哪里藏着,去看看 user/user.h 里面 ulib.c 的部分!
每个 Task 提供的 Some hints 实际上一定包含了~~几乎~~所有你需要的提示,仔细看看这些提示(不要遗漏)能事半功倍的完成。虽然如此,但是 Hints 也不少,依旧需要你翻看源码!
小心注意什么情况下失败的返回值是 -1,什么情况下是 0。你最好仔细阅读注释(很可能不存在)和源码,并适当补充注释,以免写错。
Add your sleep program to UPROGS in Makefile; once you’ve done that, make qemu will compile your program and you’ll be able to run it from the xv6 shell.
如果没有在
ls看到你的程序,很可能是你忘了添加到 Makefile 的UPROGS里面!
Task 1: sleep
Use the system call
pause().使用系统调用
pause()。
所以只需要看看 pause 的声明是:
1 | int pause(int); |
实际上这是一个系统调用,你可以试着找 sys_pause 找到具体实现。关于 pause 如何变成 sys_pause,你将在下一个 Lab syscall 中了解到(或者自己去找)。
所以只需要按照如下获取参数、完成调用即可:
1 |
|
Task 2: sixfive
sixfive 需要读取文件中的文本,输出 5 或 6 的倍数。这意味着我们需要找到用来读取文件的系统调用。
你需要 open、read、close,这些系统调用的声明都在 user/user.h 中。
但是实际上你很可能需要去看手册或者去翻源码,以确认如何传递参数。
1 |
|
Task 3: memdump
memdump 需要输出内存中的内容。你需要使用 printf。
参考 xv6 中的 printf 实现,找到所需的格式化。(其实可能是我不知道如何使用 printf haha)
1 |
|
可见我们需要 %d、%lx、%c、%s,要注意转换类型。
1 | void |
我一开始没看明白,
p当成打印指针了。得用uint64和%lx才能打印出正确的值。
Task 4: find
和 Linux 中的 find 类似,find 需要递归地遍历目录,找到指定的文件。
需要参考 ls 的实现学习如何遍历目录。
你应该使用 open、fstat、read、close 调用。
open 后需要通过 fstat 判断是目录、文件、还是设备(如果是设备则忽略了):
-
如果是
T_DIR,应该递归调用find。记得跳过.和..目录。 -
如果是
T_FILE,则判断文件名是否匹配,如果匹配则输出。
Task 5: exec
这部分指的是对于每找到一个匹配的文件,如果有 -exec 参数,则将文件名放在下一个参数,然后执行这个命令。你需要使用 fork、exec、wait 系统调用。
比如 find . wc -exec echo hi 的行为是直接执行 echo hi ./wc(注意不需要再输出文件名!)。
为了实现,当然你需要理解 fork 和 wait 的行为:fork 会创建一个子进程,子进程会从 fork 返回 0,而父进程会从 fork 返回子进程的 PID。wait 会阻塞父进程,直到子进程结束。
子进程会直接从
fork的下一行开始执行,并不是从头开始执行程序。(因为它是父进程的一个拷贝)
你还需要在执行完
exec后,让子进程exit!
你还需要小心不要让参数太多了!
1 |
|
至此已经完成了这个 Lab,当然除此之外你还可以完成哈如 Optional challenge exercises,例如实现 uptime 和让 find 支持基本的正则表达式,或者更进一步,完善 sh。
Lab System calls
从现在开始往后,会实现很多不同的系统调用。Linux 中的系统调用有将近快四百个系统调用。
To Be Continued…
说些什么吧!