CH2 补充读物:把 OS Structure 和 System Call 读成可运行代码¶
1. 这份读物怎么用¶
这份文档不是课件提纲,而是给你课后自己读代码用的“导航图”。
你读完后,应该能独立讲清 3 件事:
- 你在 shell 里敲一条命令后,系统到底做了什么。
- 一个用户态函数(如
getpid)如何进入内核。 - 这套内核在“结构设计”上属于哪种风格,为什么。
2. 从一个真实动作开始:在 shell 里输入 pid¶
建议你先在系统里实际运行一次:
然后按这条路径阅读代码:
user/init.c:系统启动后为什么会拉起 shell。user/sh.c:shell 怎么解析命令、怎么fork + exec。user/pid.c:pid程序本体只做了什么。user/syscall.c:getpid()最终如何触发ecall。kernel/core/trap.c:内核如何识别这是一次用户态系统调用。kernel/core/syscall.c:如何分发到sys_getpid。
如果你能把上面 6 步串起来,CH2 的主线就已经真正掌握了。
3. OS 服务在这套内核里的具体体现¶
操作系统课常说“OS 提供服务”,在这套代码里可以落成下面几类:
- 程序执行服务:
fork/exec/wait/exit。 - I/O 服务:
read/write,以及控制台与磁盘路径。 - 文件系统服务:
open/close/fstat/mkdir/unlink/link/chdir。 - 进程通信服务:
pipe。 - 时间和系统信息服务:
sleep/uptime/getpid。 - 保护与隔离:用户态不能直接碰内核对象,必须走系统调用边界。
你可以在 kernel/include/syscall.h 和 kernel/core/syscall.c 中看到这些服务是如何被注册和分发的。
4. CLI 不是“命令合集”,而是“命令调度器”¶
user/sh.c 很适合反复读,因为它把“用户接口”这个抽象概念写成了很实在的代码:
- 读取一行输入。
- 解析参数。
- 内建命令(如
cd)在 shell 自己内部处理。 - 外部命令通过
fork创建子进程,然后子进程exec目标程序。 - 父进程
wait子进程结束。
这就是你日常和 OS 交互的核心机制。
5. API 和系统调用:两层不要混¶
在这套仓库里,这两层的分工很清楚:
user/user.h:给应用程序调用的函数声明(API 入口)。user/syscall.c:把 API 变成寄存器约定 +ecall。kernel/core/syscall.c:内核侧分发表和具体实现。
简化理解:
- API 是“你写 C 程序时调用的函数名”。
- 系统调用是“CPU 进入内核态后的服务分发机制”。
6. 参数是怎么跨边界传进去的¶
这个项目当前采用寄存器传参路径:
a7放系统调用号。a0/a1/a2放参数。ecall触发 trap。uservec把寄存器保存进 trapframe。syscall()从 trapframe 读取参数。- 返回值再写回 trapframe 的
a0,最终带回用户态。
建议你对照这几个文件连读:
user/syscall.ckernel/arch/riscv/trampoline.Skernel/include/proc.h(struct trapframe)kernel/core/trap.ckernel/core/syscall.c
7. 系统调用类别在代码里的位置¶
你可以把当前系统调用粗分成:
- 进程控制类:
fork/exec/wait/exit/kill/yield/getpid/sleep。 - 文件类:
open/read/write/close/dup/fstat/mkdir/unlink/link/chdir。 - 通信类:
pipe。 - 系统信息类:
uptime。
做代码阅读时,建议每类至少挑一个函数,做一次“用户态 -> 内核态 -> 返回”的闭环跟踪。
8. 这套内核在结构上属于什么风格¶
如果你学过“simple / monolithic / layered / microkernel / modules”这些术语,这个项目可这样理解:
- 执行模型上是单内核(核心功能都在内核态同一地址空间)。
- 工程组织上是模块化目录(
arch/core/drivers/fs/lib)。 - 不是 microkernel(文件系统与驱动不是用户态服务进程)。
所以最贴切的描述是:单内核实现 + 模块化代码组织。
9. 启动链路:从硬件到调度器¶
建议你至少完整读一遍这条路径:
kernel/arch/riscv/entry.S:最初入口。kernel/arch/riscv/start.c:M 态初始化,切换到 S 态。kernel/core/main.c:初始化内存、进程、中断、文件系统。scheduler():开始调度。
这能把“操作系统是怎么起来的”从概念变成你能看到的真实代码。
10. 读这章时最容易踩的坑¶
- 以为 API 调用等于系统调用。
- 只看
sys_xxx,忽略了 trap 入口和返回路径。 - 只看用户程序,不看 shell 如何组织
fork/exec/wait。 - 只记术语,不把术语映射到具体文件和函数。
11. 最小自测(不交作业,仅自检)¶
你可以问自己这 5 个问题:
a7的作用是什么?ecall后先到哪个汇编入口?syscall()怎么找到sys_getpid()?- 为什么
usertrap()里要把epc前移? - shell 为什么要
fork后再exec,而不是直接exec?
如果你能用代码位置回答这些问题,CH2 就已经学到位了。