跳到正文

番外篇:中国古代的操作系统——类比、边界与真实机制

35 分钟

从“皇帝与小李子”的类比走进操作系统

这一讲用一个古代宫廷故事帮助你第一次区分用户态、内核态、中断、异常和系统调用。但先立一条规矩:类比只负责建立直觉,定义和做题必须回到计算机机制。“皇帝像内核”不等于古代真的存在操作系统,“小李子像 CPU”也不意味着 CPU 会理解命令的社会含义。

一、先把故事映射成计算机对象

故事角色或事件 计算机中的对象 类比能说明什么 类比不能说明什么
小李子执行命令 CPU 执行指令 处理器按指令序列工作 CPU 不是某个进程,也不会自己决定政策
普通大臣交办事务 用户程序提出计算或服务请求 应用程序只能在权限边界内活动 用户程序不能直接命令 CPU 切换到内核态
皇帝管理国家 操作系统内核管理资源 内核拥有更高权限并统一管理资源 内核仍是软件,真正检查当前特权级的是硬件
违规命令触发处置 异常 当前指令可同步触发控制转移 并非所有异常都是“犯罪”,缺页也可能是正常机制
边关传来急报 外部中断 与当前执行指令无直接因果的设备事件会打断 CPU 中断不保证当前进程终止,处理后可能继续运行

做选择题时,最容易被类比带偏的是把“内核”和“CPU”当成同一种东西。CPU 是执行指令的硬件;内核是运行在高特权级的软件。CPU 既能执行用户程序,也能执行内核程序,区别在于当前处理器状态和正在执行的代码

二、用户态和内核态到底限制什么

处理器至少提供两种权限状态:用户态和内核态。不同体系结构名称可能不同,但考试中的核心关系不变。

  • 用户态只能执行非特权指令,不能直接修改中断开关、页表基址等关键机器状态,也不能直接控制 I/O 设备。
  • 内核态既能执行非特权指令,也能执行特权指令。
  • “某条指令能否执行”由硬件结合当前特权级检查,不是应用程序自觉遵守。
  • 用户程序需要文件、设备或进程管理服务时,必须通过系统调用进入内核,由内核代为完成受保护操作。

一个常见错项是:“用户程序执行特权指令,从而进入内核态。”错在因果关系。用户态直接执行不允许的特权指令,会触发异常;正常的系统调用应执行体系结构规定的陷入指令,让硬件沿受控入口切换状态。陷入指令能在用户态执行,不等于它本身替用户完成了特权操作。

三、内中断、外中断与系统调用

王道教材常把中断分为内中断和外中断:

  1. 内中断(异常):事件与当前执行的指令有关,通常是同步的。例如除零、非法指令、缺页异常、系统调用陷入。
  2. 外中断:事件来自 CPU 外部,和当前指令没有必然因果关系,通常是异步的。例如时钟中断、网卡收包、磁盘 I/O 完成。

“同步”不是说每次都在相同现实时间发生,而是说给定同一条指令和同一机器状态,异常与该指令存在确定关系;“异步”则表示设备事件可能落在指令流的不同位置。

系统调用是一条受控服务路径,而不是普通函数调用的另一个名字。典型路径如下:

  1. 用户程序把系统调用号和参数放入约定的寄存器或内存位置。
  2. 用户程序执行陷入指令。
  3. 硬件保存必要现场,切换到内核态,并跳到预先登记的处理入口。
  4. 内核检查系统调用号、参数地址和权限,调用相应服务例程。
  5. 内核完成操作或把进程阻塞等待设备;服务完成后准备返回值。
  6. 执行返回指令,恢复用户现场和用户态,用户程序从约定位置继续。

这里有两个必须说清的边界。第一,系统调用参数不能因为“来自用户”就直接信任,内核必须校验。第二,系统调用不一定立刻返回:读磁盘时,当前进程可能阻塞,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

本课练习

2

先独立作答再提交;编程题会在隔离沙箱中真实编译、运行并对拍。

1系统调用的受控入口 2

用户进程希望读取磁盘文件。下列哪条是正常且受保护的执行路径?

登录 后答题可以领积分
2I/O 完成中断的状态判断 3

CPU 正在用户态运行进程 Q,此时磁盘报告进程 P 的 I/O 完成。下列说法正确的是哪些?

多选题:必须选全正确项,漏选或多选均不得分。

登录 后答题可以领积分