Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 系统与高性能 / Systems & Performance

xv6 源码学习 / xv6 Source Learning

1. xv6 源码学习专题 / xv6 Source Learning

2. xv6 学习路线:从 C 指针到操作系统源码 / xv6 Learning Roadmap

3. 读 xv6 必备的 C 语言和指针基础 / C And Pointers For xv6

4. 读 xv6 必备的 RISC-V 基础 / RISC-V Basics For xv6

5. 如何阅读 xv6 源码 / How To Read xv6 Source

6. xv6 启动与内核入口 / xv6 Boot And Kernel Entry

7. xv6 内存布局与页表 / xv6 Memory Layout And Page Tables

8. xv6 trap、中断与系统调用 / xv6 Traps Interrupts And System Calls

9. xv6 进程与 struct proc / xv6 Process And struct proc

10. xv6 上下文切换与调度器 / xv6 Context Switch And Scheduler

11. xv6 sleep/wakeup、wait 与 exit / xv6 Sleep Wakeup Wait And Exit

12. xv6 锁与并发 / xv6 Locks And Concurrency

13. xv6 文件描述符与 inode / xv6 File Descriptor And Inode

14. xv6 文件系统与日志 / xv6 File System And Log

15. xv6 设备驱动、console、UART 与磁盘 / xv6 Device Driver Console UART And Disk

16. xv6 用户程序与 shell / xv6 User Programs And Shell

17. 用 QEMU/GDB 调试 xv6 / Debugging xv6 With QEMU And GDB

18. 从 xv6 过渡到 Linux 0.11 / Bridge From xv6 To Linux 0.11

19. xv6 专题总结与实践项目 / xv6 Summary And Practice Projects

本页目录

xv6 专题总结与实践项目 / xv6 Summary And Practice Projects ​

1. 这篇文章解决什么问题 ​

前面的文章已经走过 xv6 的主要路径。

我们从 C 和指针开始。

再进入 RISC-V 基础。

再看启动流程。

再看内存布局和页表。

再看 trap、中断和系统调用。

再看进程结构。

再看调度。

再看 sleep/wakeup、wait/exit。

再看锁。

再看文件描述符、inode、日志、设备、用户程序和 shell。

最后用 GDB 验证模型。

这一篇做两件事。

第一,总结整个专题的核心模型。

第二,给出实践项目。

实践项目不是刷题。

它们是把源码从“读过”变成“能改”的桥。

本篇基于本地可读的 E:\Github\xv6-riscv。

如果你使用标准 MIT xv6-riscv,也可以直接参考。

1.1 本文基础词小注释 ​

实践项目 project:这里的项目不是“大工程”,而是一个能迫使你读懂、修改、验证某条内核路径的小任务。目标是把被动阅读变成主动掌握。

实验 experiment:实验要有明确问题、修改点、预期现象和观察方法。比如“给 fork 加日志”比“随便改 proc.c”更像实验。

假设 hypothesis:动手前先写下你认为会发生什么。比如“如果 timer interrupt 调用 yield(),那么运行中的进程会周期性让出 CPU”。之后用 GDB 或日志验证。

观察 observation:运行后看到的事实。它可能支持你的假设,也可能推翻你的假设。学习内核时,被推翻很正常,关键是回到源码解释原因。

text
question
   |
   v
hypothesis
   |
   v
small code change
   |
   v
run / debug
   |
   v
observation
   |
   v
revise model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

回归 regression:原来能工作的行为被你改坏了,就叫回归。做 xv6 实验时,即使只是学习,也要养成改完跑测试或跑基本命令的习惯。

心智模型 mental model:你脑中对系统如何工作的简化图。它不要求一开始完全精确,但必须能被源码、日志、GDB 和实验不断修正。

2. 总体心智模型 ​

xv6 是一个小型 Unix-like 教学内核。

Unix-like 表示它采用了 Unix 风格的进程、文件、系统调用和 shell 模型。

它不是玩具到毫无价值。

它是把真实操作系统骨架压缩到可读范围。

可以把 xv6 看成四层:

text
+------------------------------------------------+
| user programs: init, sh, cat, echo, tests       |
+------------------------------------------------+
| syscall boundary: usys, ecall, trap, syscall    |
+------------------------------------------------+
| kernel services: proc, vm, fs, file, pipe       |
+------------------------------------------------+
| hardware layer: RISC-V, timer, UART, virtio     |
+------------------------------------------------+
1
2
3
4
5
6
7
8
9

读 xv6 最重要的是跨层理解。

只读用户程序会看不到内核边界。

只读内核函数会看不到谁触发它们。

只读汇编会看不到设计目的。

只读概念会缺少证据。

3. 本篇涉及的 xv6 源码文件总览 ​

kernel/entry.S 处理早期入口。

kernel/start.c 进入机器启动准备。

kernel/main.c 初始化内核子系统。

kernel/proc.h 定义进程、CPU、trapframe、context。

kernel/proc.c 实现进程生命周期和调度。

kernel/swtch.S 实现上下文切换。

kernel/trap.c 处理用户 trap、内核 trap、设备中断。

kernel/trampoline.S 保存恢复用户寄存器并切换页表。

kernel/syscall.c 分派系统调用。

kernel/sysproc.c 实现进程类系统调用。

kernel/sysfile.c 实现文件类系统调用。

kernel/vm.c 实现页表和地址空间操作。

kernel/kalloc.c 实现物理页分配。

kernel/bio.c 实现 buffer cache。

kernel/fs.c 实现 inode、目录、块映射。

kernel/file.c 实现 file 对象和 fd 相关逻辑。

kernel/pipe.c 实现管道。

kernel/log.c 实现文件系统日志。

kernel/virtio_disk.c 实现虚拟磁盘驱动。

kernel/console.c、kernel/uart.c 实现控制台输入输出。

user/init.c 启动 shell 并回收孤儿。

user/sh.c 实现命令解释器。

user/usys.pl 生成用户态系统调用汇编桩。

Makefile 串联编译、链接、镜像和 QEMU 启动。

4. 进程模型总结 ​

进程是运行中程序的内核表示。

程序是磁盘上的文件。

进程是加载后正在被管理的执行实体。

xv6 用 struct proc 表示进程。

它保存状态。

它保存 pid。

它保存父进程。

它保存用户页表。

它保存 trapframe。

它保存内核上下文。

它保存打开文件。

它保存当前目录。

进程状态形成状态机:

text
UNUSED -> USED -> RUNNABLE -> RUNNING
                     ^          |
                     |          v
                  SLEEPING <- yield/sleep
                     |
                     v
                  ZOMBIE -> UNUSED
1
2
3
4
5
6
7

理解进程要抓住三组关系。

第一,用户地址空间和内核数据结构的关系。

第二,进程状态和调度器的关系。

第三,父子关系和 wait/exit 的关系。

5. 系统调用模型总结 ​

系统调用是用户态请求内核服务的接口。

接口表示双方都遵守的约定。

用户程序不能直接调用内核函数。

它通过用户态 stub 执行 ecall。

内核通过 trap 入口接管。

RISC-V 寄存器传递系统调用号和参数。

xv6 再用 syscalls[] 分派到具体实现。

总路径:

text
user code
  |
  v
usys stub
  |
  v
ecall
  |
  v
trampoline uservec
  |
  v
usertrap
  |
  v
syscall
  |
  v
sys_* wrapper
  |
  v
kernel service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

这个模型能解释新增 syscall 需要改哪些文件。

它也能解释为什么用户指针需要 copyin 和 copyout。

6. 内存模型总结 ​

xv6 使用虚拟内存。

虚拟内存让每个进程看到自己的地址空间。

页表负责虚拟地址到物理地址的映射。

内核有内核页表。

每个用户进程有用户页表。

TRAMPOLINE 被映射在高地址。

TRAPFRAME 位于 trampoline 下方。

用户页表中既有用户代码数据,也有 trap 相关映射。

内存路径:

text
virtual address
  |
  v
page table walk
  |
  v
PTE permission check
  |
  v
physical address
1
2
3
4
5
6
7
8
9
10

PTE 是页表项。

它包含物理页号。

它包含有效位。

它包含读写执行权限。

它包含用户位。

页表实验最容易验证虚拟内存模型。

7. 调度模型总结 ​

调度器决定哪个可运行进程占用 CPU。

xv6 的调度器很朴素。

它扫描进程表。

它寻找 RUNNABLE。

它切换到进程上下文。

进程通过 yield、sleep、exit 等路径离开 CPU。

上下文切换保存的是内核执行上下文。

它不是保存整个用户地址空间。

进程用户寄存器在 trapframe。

内核调度上下文在 context。

区分这两个结构很重要。

text
trapframe
  |
  +-- user registers saved on trap

context
  |
  +-- kernel registers saved by swtch
1
2
3
4
5
6
7

调度实验可以帮助你看懂状态变化。

8. sleep/wakeup 模型总结 ​

sleep/wakeup 是等待事件的机制。

sleep 把当前进程变成 SLEEPING。

wakeup 把等待某个通道的进程变成 RUNNABLE。

chan 是等待通道。

它通常是某个内核对象地址。

chan 只是比较用的标签。

不是数据队列。

sleep 必须和锁配合。

否则会出现 lost wakeup。

lost wakeup 表示唤醒发生在睡眠之前,导致等待者永远睡下去。

正确模型:

text
hold condition lock
  |
  v
check condition
  |
  v
atomically prepare sleep and release lock
  |
  v
wakeup changes SLEEPING to RUNNABLE
1
2
3
4
5
6
7
8
9
10

这个模型在 pipe、wait、console、ticks 中都会出现。

9. 文件系统模型总结 ​

xv6 把一切尽量统一到文件接口。

文件描述符是进程看到的小整数。

file 是内核中的打开文件对象。

inode 是文件系统中的文件元数据。

buffer 是磁盘块在内存中的缓存。

磁盘驱动负责实际 I/O。

层次如下:

text
fd
 |
 v
struct file
 |
 v
struct inode / pipe / device
 |
 v
block mapping
 |
 v
buffer cache
 |
 v
virtio disk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

常见误区是把 fd、file、inode 混为一谈。

实践时要刻意区分。

dup 复制 fd 到同一 file。

open 创建或引用 file。

link 操作 inode 的目录引用。

unlink 可能减少链接数但不立刻释放打开文件。

10. shell 模型总结 ​

shell 是普通用户程序。

它不是内核。

它通过系统调用组合出命令语义。

普通命令使用 fork、exec、wait。

重定向使用 close、open、dup。

管道使用 pipe、fork、close、dup、wait。

后台命令使用 fork 但不等待。

cd 必须在父 shell 中执行。

因为工作目录是进程属性。

shell 的核心图:

text
parse command line
  |
  v
build command tree
  |
  v
fork child environment
  |
  v
adjust fd if needed
  |
  v
exec target program
1
2
3
4
5
6
7
8
9
10
11
12
13

掌握 shell 能反过来理解系统调用为什么长这样。

11. 调试模型总结 ​

调试不是碰运气。

调试是验证模型。

先写出假设。

再设置断点。

再观察结构体和寄存器。

再判断是否符合预期。

GDB 最值得看的对象:

myproc()。

*myproc()。

myproc()->trapframe。

myproc()->pagetable。

r_sepc()。

r_scause()。

r_stval()。

syscalls[]。

proc[]。

把 GDB 当作证明工具,而不是猜谜工具。

12. 项目一:新增 syscall ​

项目目标:新增一个简单系统调用。

建议名称:tracepid 或 hello。

功能可以很小。

例如返回当前 pid 的两倍。

或者打印当前进程名。

重点不是功能。

重点是走完整条系统调用链。

涉及文件:

kernel/syscall.h 添加系统调用号。

kernel/syscall.c 添加 extern 声明和分派表项。

kernel/sysproc.c 或新文件中实现 sys_*。

user/user.h 添加用户态声明。

user/usys.pl 添加 entry。

user/ 下添加测试程序。

Makefile 添加用户程序到 UPROGS。

验收标准:

用户程序能调用新 syscall。

返回值正确。

未知 syscall 不受影响。

构建生成新的 usys.S。

能用 GDB 在 syscall() 看到新系统调用号。

13. 项目一的实验步骤 ​

第一步,选一个未使用的系统调用号。

第二步,在 kernel/syscall.h 定义 SYS_hello。

第三步,在 user/usys.pl 添加 entry("hello")。

第四步,在 user/user.h 声明 int hello(void);。

第五步,在 kernel/sysproc.c 实现 sys_hello。

第六步,在 kernel/syscall.c 注册 sys_hello。

第七步,写 user/hellotest.c。

第八步,在 Makefile 添加 _hellotest。

第九步,运行 xv6。

第十步,在 shell 中执行 hellotest。

建议观察:

系统调用号是否进入 a7。

返回值是否写到 a0。

用户程序是否能看到返回值。

失败时优先检查 usys.pl 和分派表。

14. 项目二:打印 proc 生命周期 ​

项目目标:观察进程从创建到回收的状态变化。

涉及文件:

kernel/proc.c。

kernel/proc.h。

可选涉及 kernel/sysproc.c。

观察点:

allocproc。

userinit。

kfork。

scheduler。

sleep。

wakeup。

kexit。

kwait。

freeproc。

建议输出格式:

text
proc pid=3 name=sh event=fork state=RUNNABLE parent=2
1

输出要克制。

不要在极高频路径打印太多。

否则 QEMU 输出会淹没真正信息。

验收标准:

运行 echo hi 能看见 fork、exec、exit、wait。

运行管道能看见两个子进程。

运行后台命令能看见父 shell 不 wait。

15. 项目二的分析问题 ​

问题一:第一个用户进程是谁创建的?

问题二:shell 是谁 fork 出来的?

问题三:普通命令的父进程是谁?

问题四:exec 后 pid 是否改变?

问题五:exit 后为何不是立刻 UNUSED?

问题六:wait 在哪个进程里执行?

问题七:孤儿进程最后被谁回收?

问题八:状态变更是否都持有必要锁?

这些问题比打印本身更重要。

打印只是证据来源。

16. 项目三:页表实验 ​

项目目标:打印并解释用户进程页表。

涉及文件:

kernel/vm.c。

kernel/defs.h。

kernel/exec.c。

kernel/proc.c。

kernel/riscv.h。

建议实现一个 vmprint。

输出页表层级。

输出 PTE 地址。

输出权限位。

输出物理地址。

关注区域:

用户代码和数据。

用户栈。

guard page。

trapframe。

trampoline。

验收标准:

执行某个用户程序时能打印其页表。

能解释每个高地址映射的作用。

能说明哪些 PTE 有 PTE_U。

能说明为什么 trampoline 需要固定映射。

17. 项目三的扩展:页错误 ​

扩展目标:观察非法访问如何杀死进程。

写一个用户程序访问非法地址。

断在 usertrap。

观察 scause。

观察 stval。

观察进程被设置 killed。

再观察退出路径。

专业名词:

page fault 是页错误。

它表示地址访问无法被当前页表和权限满足。

缺页不一定是错误。

如果系统实现懒分配,缺页可能触发分配。

非法访问则会终止进程。

验收标准:

能区分合法懒分配缺页和非法访问。

能说清 stval 中地址的意义。

18. 项目四:调度实验 ​

项目目标:修改调度策略并观察结果。

起步实验:统计每个进程被调度次数。

涉及文件:

kernel/proc.h 添加计数字段。

kernel/proc.c 在调度点更新计数。

kernel/sysproc.c 可添加查询 syscall。

user/ 添加测试程序。

可选策略:

保持原始轮询,只打印统计。

增加简单优先级字段。

实现简化时间片。

实现 lottery scheduling。

lottery scheduling 是彩票调度。

它用随机抽签按票数选择进程。

验收标准:

多个 CPU 密集进程同时运行时,统计结果可解释。

修改策略后结果发生符合预期的变化。

sleeping 进程不会被错误调度。

ZOMBIE 进程不会被调度。

19. 项目四的注意点 ​

不要在调度器里随便 sleep。

不要破坏 p->lock 的持锁约定。

不要让一个进程在两个 CPU 上运行。

不要只看一次输出就下结论。

调度结果有时受时钟中断和运行时序影响。

建议先用 CPUS=1 实验。

再用多 CPU 对比。

如果系统卡死,优先检查锁。

如果输出极多,优先减少打印频率。

如果进程永远不退出,检查 wait/exit 是否被破坏。

20. 项目五:文件系统小实验 ​

项目目标:理解 fd、file、inode、buffer 的关系。

实验 A:观察 open 和 close。

实验 B:观察 dup 后共享 offset。

实验 C:观察 unlink 已打开文件。

实验 D:观察小文件写入涉及的磁盘块。

实验 E:观察目录项创建。

涉及文件:

kernel/sysfile.c。

kernel/file.c。

kernel/fs.c。

kernel/bio.c。

kernel/log.c。

user/ 测试程序。

验收标准:

能解释 fd 指向 file。

能解释 file 指向 inode。

能解释 inode 如何映射到数据块。

能解释 bread/bwrite 的作用。

能解释日志为什么包住文件系统修改。

21. 项目五:dup 共享 offset ​

写用户程序:

打开一个文件。

写入一段内容。

调用 dup(fd)。

通过原 fd 读一部分。

通过 dup fd 再读一部分。

观察 offset 是否共享。

原因:

dup 复制文件描述符表项。

两个 fd 指向同一个 struct file。

offset 在 struct file 中。

所以 offset 被共享。

图:

text
fd 3 ----+
        |
        v
      struct file ----> inode
        ^
        |
fd 4 ----+
1
2
3
4
5
6
7

这个实验能直接区分 fd 和 file。

22. 项目五:unlink 已打开文件 ​

写用户程序:

创建文件。

打开文件。

unlink 文件名。

继续通过 fd 读写。

关闭 fd。

观察文件何时真正不可达。

专业名词:

link count 是链接计数。

它表示有多少目录项指向 inode。

reference count 是引用计数。

它表示内核中还有多少对象正在引用它。

unlink 减少链接计数。

close 减少打开文件引用。

文件释放通常需要两类计数都归零或满足释放条件。

23. 项目六:shell 小改造 ​

项目目标:在 user/sh.c 上做小功能。

可选功能一:新增 pwd 内建命令。

可选功能二:改进错误提示。

可选功能三:记录上一条命令退出状态。

可选功能四:支持简单环境变量样式替换。

可选功能五:支持命令别名。

建议从 pwd 或退出状态开始。

不要一上来实现完整 Bash。

涉及文件:

user/sh.c。

可能需要 user/user.h。

可能需要新增 syscall。

可能需要新增用户测试程序。

验收标准:

普通命令仍可执行。

重定向仍可用。

管道仍可用。

cd 仍在父 shell 中执行。

错误路径不会让 shell 退出。

24. shell 改造的关键提醒 ​

shell 改造容易误伤 fork/exec/wait 模型。

添加内建命令前先判断:

它是否必须改变父 shell 状态?

如果是,就在父 shell 执行。

如果不是,可以作为普通用户程序。

例如 cd 必须内建。

例如 exit 通常需要内建。

例如 echo 不需要内建。

例如 pwd 可以内建,也可以做用户程序。

如果命令涉及重定向,要考虑是否应在子进程中调整 fd。

不要让父 shell 的标准输入输出被永久改掉。

25. 项目七:系统调用追踪器 ​

项目目标:实现简化 syscall trace。

功能:让进程开启追踪后,内核打印它调用的 syscall。

涉及文件:

kernel/proc.h 添加 trace mask。

kernel/sysproc.c 添加 trace syscall。

kernel/syscall.c 在分派后打印。

user/usys.pl 添加入口。

user/user.h 添加声明。

user/trace.c 添加用户命令。

验收标准:

trace 32 echo hi 能只打印匹配系统调用。

子进程是否继承 trace mask 要有明确设计。

输出包含 pid、syscall 名、返回值。

不会破坏正常返回值。

这个项目很适合综合系统调用、proc、fork、exec。

26. 项目八:进程树命令 ​

项目目标:实现一个类似简化 pstree 的功能。

方案一:内核新增 syscall 导出进程信息。

方案二:在内核中提供调试打印。

方案三:只实现 procdump 扩展。

推荐方案一。

它能练习 copyout。

copyout 是从内核复制数据到用户地址空间。

要设计一个用户可见结构体。

结构体应包含 pid、ppid、state、name。

不要把内核指针暴露给用户。

验收标准:

用户程序能打印进程列表。

不会泄漏内核地址。

不会因为进程表变化导致崩溃。

能解释需要哪些锁。

27. 项目九:buffer cache 统计 ​

项目目标:观察缓存命中率。

在 bio.c 中统计:

查找次数。

命中次数。

未命中次数。

回收次数。

写回次数。

提供 syscall 或控制台打印。

运行:

ls。

cat README。

重复运行同一命令。

观察第二次是否更多命中。

专业名词:

cache hit 是缓存命中。

cache miss 是缓存未命中。

命中表示请求的数据已在缓存中。

未命中表示需要从下层设备读取。

验收标准:

统计数随文件操作变化。

重复读文件命中率提升。

不会破坏 buffer 引用计数。

28. 项目十:日志边界观察 ​

项目目标:理解文件系统日志。

在 begin_op 和 end_op 打印。

在 log_write 打印块号。

运行创建文件、写文件、unlink。

观察一次系统调用涉及哪些块。

日志是保证文件系统一致性的机制。

一致性表示崩溃后磁盘结构不会停在半更新状态。

xv6 日志是简化事务日志。

事务表示一组更新要么整体可见,要么整体不生效。

验收标准:

能说清哪些系统调用需要事务。

能解释为什么普通读不一定需要日志事务。

能说明 begin_op 可能等待的原因。

29. 实践项目的工作方法 ​

每个项目都按同一节奏做。

先写目标。

再列涉及文件。

再画路径图。

再决定最小改动。

再写测试程序。

再用 GDB 验证。

再记录观察结果。

模板:

text
Hypothesis:
  what I think happens

Change:
  smallest source modification

Observation:
  print/GDB/test output

Conclusion:
  model confirmed or corrected
1
2
3
4
5
6
7
8
9
10
11

这种方法比“改到能跑”更能积累能力。

30. 推荐项目顺序 ​

第一,新增 syscall。

第二,系统调用追踪器。

第三,打印 proc 生命周期。

第四,页表打印。

第五,shell 小改造。

第六,文件系统 fd/file/inode 实验。

第七,buffer cache 统计。

第八,调度实验。

第九,日志边界观察。

这个顺序从边界清晰到内部复杂。

先做 syscall 是因为反馈最快。

再做 proc 是因为能理解运行时。

再做页表是因为需要更多结构理解。

最后做调度和文件系统是因为更容易引入隐蔽错误。

31. 常见误解总表 ​

误解一:能读懂代码就等于会操作系统。

不够。

还要能预测运行行为。

误解二:能改代码编译通过就等于正确。

不够。

还要用测试和 GDB 验证。

误解三:系统调用只是加一个函数。

不是。

它跨越用户头文件、stub、号表、分派表、内核实现、测试程序。

误解四:页表只是地址转换表。

不只是。

它还表达权限和隔离。

误解五:调度器只要选一个进程就行。

不够。

它必须维护状态不变量。

误解六:文件系统只是读写磁盘。

不够。

它还要处理命名、缓存、一致性、引用计数。

32. 最小验证清单 ​

做 syscall 项目时,验证用户态能调用。

做 syscall 项目时,验证未知调用仍报错。

做 proc 项目时,验证 pid 和父子关系。

做 proc 项目时,验证 ZOMBIE 到 UNUSED。

做页表项目时,验证 trampoline 和 trapframe 映射。

做页表项目时,验证权限位解释。

做调度项目时,验证只有 RUNNABLE 被选中。

做调度项目时,验证多进程统计符合预期。

做文件系统项目时,验证 fd、file、inode 分层。

做文件系统项目时,验证崩溃敏感路径有日志保护。

做 shell 项目时,验证父 shell 不被错误替换。

做 shell 项目时,验证管道 EOF 不被破坏。

33. 如何写学习笔记 ​

每次实验后写三段。

第一段写现象。

第二段写原因。

第三段写源码位置。

例如:

text
Phenomenon:
  exec does not change pid.

Reason:
  exec replaces user address space of current proc.

Source:
  kernel/exec.c updates trapframe and pagetable.
1
2
3
4
5
6
7
8

笔记不要只贴代码。

代码会忘。

路径和不变量更耐久。

34. 继续学习路线 ​

如果想继续操作系统,下一步可以读 Linux 0.11。

读 Linux 0.11 时用 xv6 做参照。

如果想继续现代内核,可以读现代 Linux 的 VFS、调度、内存管理专题。

但不要急着跳太远。

现代 Linux 抽象很多。

没有 xv6 和 Linux 0.11 的桥,容易只剩函数名。

如果想偏实践,可以做 MIT 6.S081 labs。

这些实验覆盖 syscall、页表、copy-on-write、线程、文件系统等主题。

copy-on-write 是写时复制。

它是 fork 性能优化的核心技术。

如果想偏性能,可以分析锁竞争和 buffer cache。

如果想偏调试,可以系统练习 GDB 和 QEMU。

35. 本篇总结 ​

xv6 专题最终要留下的不是零散函数名。

而是一套能迁移的操作系统问题框架。

进程回答“谁在运行”。

页表回答“它能访问哪里”。

trap 回答“如何进入内核”。

系统调用回答“用户如何请求服务”。

调度回答“谁获得 CPU”。

sleep/wakeup 回答“如何等待事件”。

锁回答“如何保护共享不变量”。

文件系统回答“名字如何变成数据块”。

shell 回答“用户如何组合内核机制”。

最后用一句话收束:

text
xv6 is small enough to hold in your head,
but real enough to teach the shape of an operating system.
1
2

接下来最好的学习方式,是选一个项目动手。

不要只读下一篇。

去改一条路径。

去打一个断点。

去验证一个模型。

这样 xv6 才会从书上的系统,变成你真正理解过的系统。

最后更新于:

Pager
上一篇18. 从 xv6 过渡到 Linux 0.11 / Bridge From xv6 To Linux 0.11

持续记录,持续成长

Copyright © Tidenflow