番外篇:中国古代的操作系统——类比、边界与真实机制
约 35 分钟
从“皇帝与小李子”的类比走进操作系统
这一讲用一个古代宫廷故事帮助你第一次区分用户态、内核态、中断、异常和系统调用。但先立一条规矩:类比只负责建立直觉,定义和做题必须回到计算机机制。“皇帝像内核”不等于古代真的存在操作系统,“小李子像 CPU”也不意味着 CPU 会理解命令的社会含义。
一、先把故事映射成计算机对象
| 故事角色或事件 | 计算机中的对象 | 类比能说明什么 | 类比不能说明什么 |
|---|---|---|---|
| 小李子执行命令 | CPU 执行指令 | 处理器按指令序列工作 | CPU 不是某个进程,也不会自己决定政策 |
| 普通大臣交办事务 | 用户程序提出计算或服务请求 | 应用程序只能在权限边界内活动 | 用户程序不能直接命令 CPU 切换到内核态 |
| 皇帝管理国家 | 操作系统内核管理资源 | 内核拥有更高权限并统一管理资源 | 内核仍是软件,真正检查当前特权级的是硬件 |
| 违规命令触发处置 | 异常 | 当前指令可同步触发控制转移 | 并非所有异常都是“犯罪”,缺页也可能是正常机制 |
| 边关传来急报 | 外部中断 | 与当前执行指令无直接因果的设备事件会打断 CPU | 中断不保证当前进程终止,处理后可能继续运行 |
做选择题时,最容易被类比带偏的是把“内核”和“CPU”当成同一种东西。CPU 是执行指令的硬件;内核是运行在高特权级的软件。CPU 既能执行用户程序,也能执行内核程序,区别在于当前处理器状态和正在执行的代码。
二、用户态和内核态到底限制什么
处理器至少提供两种权限状态:用户态和内核态。不同体系结构名称可能不同,但考试中的核心关系不变。
- 用户态只能执行非特权指令,不能直接修改中断开关、页表基址等关键机器状态,也不能直接控制 I/O 设备。
- 内核态既能执行非特权指令,也能执行特权指令。
- “某条指令能否执行”由硬件结合当前特权级检查,不是应用程序自觉遵守。
- 用户程序需要文件、设备或进程管理服务时,必须通过系统调用进入内核,由内核代为完成受保护操作。
一个常见错项是:“用户程序执行特权指令,从而进入内核态。”错在因果关系。用户态直接执行不允许的特权指令,会触发异常;正常的系统调用应执行体系结构规定的陷入指令,让硬件沿受控入口切换状态。陷入指令能在用户态执行,不等于它本身替用户完成了特权操作。
三、内中断、外中断与系统调用
王道教材常把中断分为内中断和外中断:
- 内中断(异常):事件与当前执行的指令有关,通常是同步的。例如除零、非法指令、缺页异常、系统调用陷入。
- 外中断:事件来自 CPU 外部,和当前指令没有必然因果关系,通常是异步的。例如时钟中断、网卡收包、磁盘 I/O 完成。
“同步”不是说每次都在相同现实时间发生,而是说给定同一条指令和同一机器状态,异常与该指令存在确定关系;“异步”则表示设备事件可能落在指令流的不同位置。
系统调用是一条受控服务路径,而不是普通函数调用的另一个名字。典型路径如下:
- 用户程序把系统调用号和参数放入约定的寄存器或内存位置。
- 用户程序执行陷入指令。
- 硬件保存必要现场,切换到内核态,并跳到预先登记的处理入口。
- 内核检查系统调用号、参数地址和权限,调用相应服务例程。
- 内核完成操作或把进程阻塞等待设备;服务完成后准备返回值。
- 执行返回指令,恢复用户现场和用户态,用户程序从约定位置继续。
这里有两个必须说清的边界。第一,系统调用参数不能因为“来自用户”就直接信任,内核必须校验。第二,系统调用不一定立刻返回:读磁盘时,当前进程可能阻塞,CPU 转去运行别的就绪进程。
四、跟着状态表推一遍
设用户进程 P 调用 read(fd, buf, 4096),所需磁盘块尚未在内存:
| 时刻 | CPU 状态 | 正在执行的代码 | 关键事件 | P 的进程状态 |
|---|---|---|---|---|
| A | 用户态 | P 的用户代码 | 准备系统调用号与参数 | 运行态 |
| B | 用户态→内核态 | 陷入入口 | 执行陷入指令 | 仍可视为运行态 |
| C | 内核态 | read 服务例程 |
校验参数,提交磁盘请求 | 运行态→阻塞态 |
| D | 内核态→用户态 | 调度器选择 Q | 现场切换后运行 Q | P 阻塞,Q 运行 |
| E | 用户态→内核态 | 中断处理程序 | 磁盘完成产生外中断 | P 阻塞→就绪 |
| F | 内核态→用户态 | 调度器以后选中 P | 恢复 P 现场并返回结果 | P 就绪→运行 |
注意:E 时刻磁盘中断到来时,CPU 可能正在运行 Q,而不是 P。设备完成只负责让等待条件满足,通常把 P 从阻塞态移入就绪队列;P 何时真正运行仍由调度决定。
五、可运行的小实验
下面的 Python 程序只模拟“事件是否允许导致某种状态转换”,不是在模拟真实硬件。修改 events,观察非法转换在哪一步被拒绝。
transitions = {
("user", "syscall"): "kernel",
("user", "external_interrupt"): "kernel",
("kernel", "return_to_user"): "user",
}
mode = "user"
events = ["syscall", "return_to_user", "external_interrupt", "return_to_user"]
for event in events:
key = (mode, event)
if key not in transitions:
raise RuntimeError(f"非法转换:{mode} --{event}-->")
new_mode = transitions[key]
print(f"{mode:6} --{event:18}--> {new_mode}")
mode = new_mode
这段程序刻意没有加入“用户程序直接把 mode 改成 kernel”的路径,因为现实机器上的特权级切换必须经过硬件认可的入口。
六、课内练习
练习 1:单选
用户进程希望读取磁盘文件,正确的正常路径是:
A. 在用户态直接执行磁盘控制指令
B. 准备参数并执行陷入指令,由内核完成受保护操作
C. 等待任意外中断后自动取得文件内容
D. 修改处理器状态字,主动切换到内核态
答案:B。A、D 都越过了特权保护;C 把设备事件与服务请求的因果关系颠倒了。
练习 2:多选
CPU 正在用户态运行进程 Q,此时磁盘报告进程 P 的 I/O 完成。下列说法正确的是:
A. 该事件属于外中断
B. 中断处理完成后,P 通常先进入就绪队列
C. P 必然立即抢占 Q 并从用户态继续执行
D. CPU 会经受控入口转去执行内核中的中断处理程序
答案:A、B、D。P 是否立刻运行取决于调度策略和优先级,因此 C 的“必然”错误。
练习 3:状态追踪
若系统调用参数指向一个非法用户地址,请写出:由谁发现、属于什么事件、为什么不能让用户进程直接决定处理方式。
参考答案:内核在校验或访问参数时结合硬件检查发现问题,可能形成系统调用返回错误,也可能触发地址访问异常;处理必须遵循内核与硬件规定的保护路径,否则用户程序可以绕过资源隔离。
本课验收
你应当能够不用故事角色,完整解释以下三句话:用户态为什么不能直接执行特权操作;外中断和异常如何区分;一次阻塞式系统调用为什么可能伴随两次进程切换。能画出上面的六阶段状态表,并能解释每次状态变化的触发者,才算真正完成本课。
Practice
本课练习
先独立作答再提交;编程题会在隔离沙箱中真实编译、运行并对拍。