Skip to content

xv6

操作系统要做三件事:复用、隔离、交互

操作系统必须满足三件事:multiplexing(多路复用)、isolation(隔离)、interaction(交互)。xv6 走的是主流的宏内核(monolithic kernel)。

RISC-V 有 machine / supervisor / user 三档。开机在 machine mode,xv6 很快切到 supervisor。用户程序用 ecall 进入内核指定入口,用 sret 回去。

宏内核:整个 OS 都在内核,系统调用都在 supervisor 里跑,一处错误可能致命。微内核:尽量少的代码跑在高特权,其余在用户态。

隔离的单位是进程:不让一个进程毁掉或偷看另一个的内存、CPU、文件描述符,也不能轻易毁掉内核自己的隔离机制。内核靠 user/supervisor 标志、地址空间、时间片来管进程。

每个进程一张页表。虚拟地址从 0 开始:代码、全局变量、栈(向下)、堆(向上,malloc)。高处还有 trampoline(进出内核的跳板代码)和 trapframe(保存用户寄存器)。

进程在内核里的关键状态在 struct proc:页表、内核栈、运行状态(分配 / 就绪 / 在跑 / 等 I/O / 退出)。

一个进程绑了两个幻觉:地址空间让你以为内存是自己的;线程让你以为 CPU 是自己的。xv6 里一个进程 = 一块地址空间 + 一条线程。

启动顺序大致是:上电 → bootloader 装内核 → _entry 设栈 → start(M-mode)→ main(S-mode)初始化 → userinit 做出 1 号进程 → exec("/init") → 开 console → exec("/sh")。

fork() 刚结束时,父子连着同一块内核管道缓冲区。不剪线,读写会打架。子进程典型操作:close(0) 拔掉键盘,dup(p[0]) 让 0 号接到管道出口,再关掉临时的 p[0]/p[1]。之后父写 p[1],子进程里的 wc 以为自己在 read(0) 读键盘,其实吸到的是父亲灌进去的数据。

父写子读是逻辑上的流向。这根水管一头连着父的 p[1],一头连着子的 0。Unix 进程间通信的浪漫大概就是这个。