第 6 章 机制:受限直接执行

操作系统通过时分共享(Time Sharing)(即轮流运行进程)来实现 CPU 虚拟化。

这一机制的核心挑战在于平衡高性能控制权:OS 既要最小化虚拟化带来的开销,又要确保能有效管理资源,防止进程独占 CPU 或越权访问。

受限的直接执行

为了使程序尽可能快地运行,操作系统采用了 受限直接执行(Limited Direct Execution, LDE 技术。“直接执行”意味着程序直接在 CPU 上运行。OS 只需完成进程初始化(如分配内存、加载代码、设置栈等),便跳转至程序的入口点开始执行。如下表所示:

操作系统程序
在进程列表上创建条目
为程序分配内存
将程序加载到内存中
根据 argc/argv 设置程序栈
清除寄存器
执行 call main() 方法
执行 main()
main 中执行 return
释放进程的内存
将进程从进程列表中清除

然而,这种简单的方法面临两个核心问题:

  1. 受限操作:如何在保证高效运行的同时,防止程序执行不被允许的操作?
  2. 控制权切换:操作系统如何暂停一个正在运行的进程并切换到另一个进程,以实现时分共享?

解决这些问题正是“受限”二字的由来——如果没有限制,操作系统将失去对机器的控制权,沦为一个普通的库。

受限制的操作

直接执行虽快,但若进程需要执行受限操作(如 I/O 请求或申请更多资源),直接运行会导致系统失控。

核心方案:受保护的控制权转移

硬件通过两种执行模式保障系统安全:

  • 用户模式(User Mode):应用程序运行于此,权限受限,无法执行特权指令或直接访问硬件。
  • 内核模式(Kernel Mode):操作系统运行于此,拥有最高权限,可访问所有资源。

为了安全地执行特权操作(如读写磁盘),程序需使用 系统调用(System Call) 机制,其核心流程为:

  1. 陷入(Trap):程序执行 trap 指令,CPU 提升权限至内核模式,并跳转至内核的陷阱处理程序。
  2. 内核处理:操作系统执行请求的操作(如 open()read())。
  3. 返回(Return-from-Trap):内核执行特权指令,恢复寄存器并降级回用户模式,程序从陷阱后的指令继续执行。
系统调用为何像普通函数调用?

C 库将系统调用封装为普通函数(如 open())。库函数内部通过汇编代码处理参数传递、执行 trap 指令以及处理返回值。对程序员而言,系统调用就像调用普通 C 函数一样简单。

陷阱表(Trap Table)

陷阱表(Trap Table),在某些架构中也被称为中断向量表(Interrupt Vector Table),本质上是硬件用于查找异常处理程序地址的映射表。

你可以把它想象成操作系统的“应急预案清单”或“内部电话簿”。当发生特定事件(如程序请求系统调用、发生除零错误或硬件中断)时,硬件不知道该如何处理,它只能去查这张表,找到对应的“处理程序”在哪里。

内核必须严格控制这些跳转的目标地址。如果允许用户程序指定跳转地址,恶意程序就可以让内核执行任意指令(例如跳转到内核中删除文件的代码片段),从而接管机器。因此,操作系统采用“预设入口”的策略:

  • 启动时初始化:操作系统在启动时(内核模式)配置陷阱表,告知硬件发生异常(如系统调用、中断)时应跳转到的处理程序地址。
  • 硬件记忆:硬件记住这些地址,直到机器重启。这意味着一旦机器启动,用户程序就只能通过这些被“官方认证”的入口进入内核。

进程切换

解决了直接执行的受限问题后,下一个核心挑战是:操作系统如何暂停当前进程并切换到另一个进程? 如果进程占用了 CPU,操作系统就无法运行,也就无法执行切换操作。这是获取 CPU 控制权的关键问题。

关键问题:如何重获 CPU 的控制权

操作系统如何重新获得 CPU 的控制权(regain control),以便它可以在进程之间切换?

1. 协作方式:等待系统调用(Cooperative)

早期系统(如早期版本的 Macintosh 操作系统或 Xerox Alto 系统)采用此方式,依赖进程主动放弃 CPU。

  • 主动让出:进程通过系统调用(如 yield、I/O 操作)将控制权交还给 OS。
  • 异常处理:若进程执行非法操作(如除以零、非法访问内存),硬件会触发异常,将控制权转移给 OS。
  • 致命缺陷:如果进程陷入死循环且不进行系统调用,OS 将无法重获控制权,只能重启机器。

2. 非协作方式:时钟中断(Timer Interrupt)

为了解决协作方式的缺陷,现代系统利用硬件机制——时钟中断

  • 机制:硬件每隔几毫秒产生一次中断,强制暂停当前进程,跳转到 OS 的预设中断处理程序。
  • 效果:OS 周期性地重获 CPU 控制权,从而能够停止当前进程并调度其他进程。
硬件保障控制权

时钟中断是操作系统维持机器控制权的根本保障,确保即使面对恶意或失控的进程,OS 仍能掌控全局。

3. 上下文切换(Context Switch)

当 OS 重获控制权并决定切换进程时,会执行上下文切换

  1. 保存上下文:将当前进程的通用寄存器PC 等保存到其内核栈或进程结构中。
  2. 恢复上下文:从下一个进程的结构中恢复其寄存器和内核栈。
  3. 切换栈:切换内核栈指针,使代码执行环境变为下一个进程的环境。
  4. 返回:执行 return-from-trap,CPU 加载新进程的上下文并开始执行。

上下文切换和系统调用的耗时随着硬件性能提升而显著减少。例如,从 1996 年的微秒级(~6μs)提升至现代系统的亚微秒级。但需注意,内存密集型操作的性能提升幅度不如处理器速度显著。

担心并发吗

如果系统调用期间发生时钟中断,或者处理一个中断时发生另一个中断,会发生什么? 这确实是操作系统需要解决的复杂问题。操作系统通常采用以下策略:

  1. 禁用中断(Disable Interrupts):在处理中断期间,暂时屏蔽其他中断,确保当前处理过程的原子性。但需谨慎使用,过长时间禁用可能导致中断丢失。
  2. 锁机制(Locking):为了支持多处理器并发访问内核数据结构,操作系统引入了复杂的锁方案。

这正是本书第二部分“并发”将深入探讨的主题。

小结

本章介绍了实现 CPU 虚拟化的关键机制:受限直接执行(Limited Direct Execution, LDE。 其核心思想类似于“婴儿防护(Baby Proofing)”:

  1. 设置限制:在启动时配置陷阱表和时钟中断,确保危险操作(如直接访问硬件)被禁止。
  2. 自由运行:在受限模式下让进程直接在 CPU 上高效运行。
  3. 介入干预:仅在进程请求特权操作(系统调用)或独占 CPU 时间过长(时钟中断)时,操作系统才介入接管控制权。
重启的价值

在协作式调度中,面对死循环进程,重启(Reboot) 往往是唯一解。实际上,重启是构建健壮系统的有效工具:它能将软件重置为已知状态,回收泄漏资源,且易于自动化。在大型分布式系统中,定期重启服务是一种常见的运维策略。

至此,我们掌握了 CPU 虚拟化的底层机制。接下来的问题是:在特定时间,应该运行哪个进程? 这将是下一章“调度”的主题。