跳转至

03 受限直接执行 — 高效与控制的第一次交锋

1. 这一章讲什么

上一章讲了进程是什么,留下一个问题:操作系统怎么把 CPU 从一个进程手里 抢回来给另一个?直接让程序在硬件上跑当然最快——但那样程序可以为所欲为, 永远不放权。本章就是解这个结:一套叫受限直接执行(Limited Direct Execution) 的协议,四件武器——陷阱表、系统调用、时钟中断、上下文切换—— 外加一个贯穿全书的操作系统性格:偏执。

在全书链条里的位置:这是 CPU 虚拟化的机制底座。机制(本章)立住之后, 接下来两章讨论策略:调度器在「换人」时刻决定换谁。

2. 顶层全景

  开机时(内核态):
    操作系统告诉硬件一张「陷阱表」:系统调用/时钟中断/出错时,分别跳到哪段代码
                                   │
  运行时(用户态):                 │
    程序直接跑在 CPU 上 ──想干受限的事?──▶ 执行 trap 指令 ──▶ 硬件进内核态、跳陷阱表
              ▲                                                │ 办完了 return-from-trap 回去
              │                                                │
              └──────── 每隔几毫秒,时钟中断强行插一脚 ◀───────┘
                                   │
              中断处理中决定换人?──▶ 上下文切换:把 A 的寄存器存好、换上 B 的

图说:程序大部分时间在裸跑(快),只有两类时刻操作系统介入—— 程序自愿(系统调用)和硬件强拉(时钟中断)。

3. 核心原理

3.1 主线例子:一个进程的一生,走完整套协议

书里用一张时序图(Figure 6.2/6.3)当主线例子,我们把它翻成具体场景: 你按回车运行 ./p1,然后过了几十毫秒,调度器决定换人。 每一步的状态都在旁边:

  1. 开机。机器以内核态启动,操作系统做的第一批事里有一件: 用特权指令初始化陷阱表——告诉硬件「系统调用来了跳这段、 时钟中断来了跳那段、出错了跳另外一段」。硬件记住这张表, 直到下次重启1。
  2. 创建进程。操作系统给 p1 建进程表项、分配内存、把代码从磁盘 读进来、初始化栈(填好 argc/argv)、把内核栈填上寄存器和 PC, 然后执行一条 return-from-trap2。硬件此刻恢复寄存器、切到用户态、 跳到 main()——p1 开始裸跑。
  3. 系统调用。p1 想读文件,调用 read()。C 库里藏着 trap 指令: 参数和系统调用号放进约定好的位置,执行 trap——硬件保存寄存器到 p1 的内核栈、切内核态、按陷阱表跳进内核的 read 处理代码3。 内核查系统调用号、检查参数(比如那个 buffer 地址合不合法4), 办完事 return-from-trap:p1 的寄存器从内核栈弹回来,用户态,继续跑。
  4. 时钟抢权。p1 正跑在死循环里,几毫秒到了——时钟中断: 硬件再次把 p1 的寄存器存到它的内核栈、切内核态、跳进时钟处理程序5。 此刻操作系统重新坐上了驾驶位,尽管它刚才根本没在运行。
  5. 上下文切换,换人。时钟处理程序问调度器:继续 p1,还是换 p2? 假如决定换,switch() 例程干三件事:把寄存器和栈指针从 p1 的 内核栈搬进 p1 的进程结构;从 p2 的进程结构取出它当年的寄存器; 把栈指针切到 p2 的内核栈6。最后 return-from-trap—— 弹回来的是 p2 的寄存器,跳到 p2 当年的 PC,p2 从它被中断的那一行继续跑, 对刚才的换人无感。

注意第 4、5 步里发生了两次不同的存寄存器:中断时硬件存(到内核栈), 切换时软件再存(到进程结构)。前者是「暂停」,后者才是「存档」—— 书里特意点出这两次存档的区别7。

3.2 问题一:受限操作怎么办

直接执行的第一个窟窿:程序要做受限操作(发磁盘 I/O、要更多内存)时, 不能让它直接做——否则文件权限检查就是摆设,任何进程都能读写全盘8。

解法三件套:用户态(硬件限制 I/O 等特权指令,违反就触发异常、 进程被击毙)、系统调用(Atlas 系统 1960 年代首创的正规出口9)、 陷阱表(程序不能指定跳进内核的哪个地址——能指定就是「非常糟糕的主意」, 等于绕过所有检查;所以程序只能报系统调用号,由陷阱表决定跳哪)10。

这里有一个必须知道的分层:你在 C 里调 open(),看着和普通函数调用一样, 其实 C 库里那段是手工写的汇编——按约定摆好参数、执行 trap、 拆包返回值11。「系统调用看起来跟普通调用一样」是库制造的幻觉。

3.3 问题二:不合作怎么办——哲学死结与它的硬件解

第二个窟窿更根本:进程在 CPU 上跑 ⟹ 操作系统没在跑 ⟹ 操作系统想管也管不了12。两种历史解法:

办法 做法 死穴
合作式(早期 Mac、Xerox Alto) 信程序自觉,等它做系统调用或出错时顺手收权;还有专门的 yield 调用供程序主动让贤13 程序死循环里不做任何调用,操作系统只能重启14
抢占式(今天的主流) 硬件配一个定时器:每隔几毫秒强行中断,把控制权塞回操作系统手里15 中断本身有开销;中断处理时又来中断怎么办(本库 14 章)

图说:这张表是全书最重要的历史教训之一——「信任程序自觉」这条路被 实践判了死刑,控制权的唯一可靠来源是硬件的时钟。书里给合作式留了 一个体面的注脚:重启不丢人,它把软件拉回一个「已知且经过更多测试的状态」, 大集群里定期重启机器是正经运维手段16。

3.4 内核里的并发暗线

系统调用处理到一半,时钟中断来了怎么办?中断处理时又来一个中断呢? 书里此刻只给两条线索:关中断(处理中断期间不接收新的——简单, 但关太久会丢中断)和内核锁(让内核的多个活动可以真正并行—— 复杂,会引出很难找的 bug)17。整本书第二部分(本库 11–14 章) 就是把这个暗线拉到台前。

4. 作者的判断与证据

书里给了证据的判断:

  • LDE 协议完整(两次寄存器保存、陷阱表初始化、时钟启动顺序), 配 xv6 真实汇编代码佐证,可核可查。
  • 用户态/内核态、trap 表必须由特权指令装载(用户程序自己装表会怎样? 书里留作思考题:大概能接管整机18)。

作者的判断(立场):

  • 「没有这些限制,操作系统就成了『只是个库』——这对一个有抱负的 操作系统是很悲哀的境地」19——作者明确站在「控制优先」一侧。
  • 性能旁注(1996 年一次系统调用约 4 微秒、上下文切换约 6 微秒; 现代系统快近十倍)引 lmbench 实测20——但作者同时引 Ousterhout 提醒: OS 操作多为访存密集,光买更快的 CPU 提速有限21。

5. 边界与局限

  • 本章只解决「单个 CPU 上怎么换人」。多核、多 CPU 的调度复杂性 (缓存(把常用数据留在手边的高速小抄)、同步)推到第 10 章和本库 05 章——是刻意的顺序安排。
  • 上下文切换的真实成本(缓存失效、TLB 冲刷)本章没有量化展开, 只给了微秒级数字;在多核一章才补「换到另一个核上代价更大」。
  • 「关中断」作为内核并发手段只是点到为止,真正的坑(锁、死锁) 在并发部分;读者在这里容易误以为关中断是个无害的万能药。

6. 可带走的

  1. 虚拟化 CPU 的总机制 = 受限直接执行:平时裸跑(效率), 系统调用与时钟中断两个口子介入(控制)。任何「既要快又要管」的问题 都值得先想这个模板。
  2. 系统调用不是函数调用:它换特权级别、跳陷阱表、由硬件保存现场; C 库把 trap 指令藏在了你熟悉的 open() 背后。
  3. 控制权只能来自硬件(时钟中断),不能来自软件的自觉—— 合作式调度的历史教训对任何「信任上层」的设计都适用。
  4. 上下文切换 = 两次存档:硬件存到内核栈(暂停),操作系统搬到进程结构(存档)。
  5. 陷阱表是操作系统在开机时和硬件立的契约;装表本身是特权操作。
  6. 处理用户传入的每个参数都要当它是恶意的——内核里一次地址检查的 疏忽就能让任意程序读光全部内存。

7. 原文地图

主题 原书章 原文位置
难题:高效地虚拟化 CPU 且保持控制 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:268(搜「crux of the problem」)
直接执行协议(无限制版) 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:39(搜「Execute call main」)
两个问题的提出 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:52(搜「how can the OS make sure」)
用户态/内核态与 I/O 限制 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:105(搜「user mode」)
系统调用像过程调用的原因 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:79(搜「WHY SYSTEM CALLS」)
陷阱表开机初始化 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:149(搜「trap table at boot time」)
系统调用号:间接层即保护 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:227(搜「system-call number」)
检查参数,别信用户 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:209(搜「WARY OF USER INPUTS」)
哲学死结:OS 不运行怎么做事 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:264(搜「by definition means」)
合作式调度与它的死穴 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:274(搜「Cooperative Approach」)与:302(搜「infinite loop」)
时钟中断夺回控制 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:319(搜「timer interrupt」)
上下文切换的三步 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:358(搜「context switch」)
两次寄存器保存的区别 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:406(搜「two types」)
关中断与内核锁的预告 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:466(搜「interrupt processing」)
上下文切换的实测耗时 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:523(搜「lmbench」)
重启是有用的 6. Mechanism: Limited Direct Execution text/08-ch06-6-mechanism-limited-direct-execution.txt:379(搜「REBOOT IS USEFUL」)


  1. 出处:「6. Mechanism: Limited Direct Execution」第 149–154 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:149,搜「trap table at boot time」)。原文:内核在引导时初始化陷阱表,硬件此后一直记住各事件对应的处理代码地址。 ↩

  2. 出处:「6. Mechanism: Limited Direct Execution」第 173–178 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:173,搜「Fill kernel stack」)。 ↩

  3. 出处:「6. Mechanism: Limited Direct Execution」第 132–141 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:132,搜「trap instruction」)。原文:trap 同时跳进内核并抬升特权级别;x86 把 PC、标志位等压入该进程的内核栈。 ↩

  4. 出处:「6. Mechanism: Limited Direct Execution」第 216–222 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:216,搜「write()」)。旁注「警惕安全系统里的用户输入」:write 的 buffer 地址若落在内核区,必须检出并拒绝,否则任一读光内核内存。 ↩

  5. 出处:「6. Mechanism: Limited Direct Execution」第 319–324 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:319,搜「timer interrupt」)。原文:定时器每隔若干毫秒发起中断,正在运行的进程被停下,操作系统预设的中断处理程序运行,OS 重获控制权。 ↩

  6. 出处:「6. Mechanism: Limited Direct Execution」第 357–368 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:358,搜「context switch」)。 ↩

  7. 出处:「6. Mechanism: Limited Direct Execution」第 406–413 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:406,搜「two types of register saves」)。 ↩

  8. 出处:「6. Mechanism: Limited Direct Execution」第 97–103 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:102,搜「read or write the entire disk」)。 ↩

  9. 出处:「6. Mechanism: Limited Direct Execution」第 113–114 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:114,搜「Atlas」)。 ↩

  10. 出处:「6. Mechanism: Limited Direct Execution」第 144–148 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:147,搜「Very Bad Idea」)。 ↩

  11. 出处:「6. Mechanism: Limited Direct Execution」第 79–95 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:79,搜「WHY SYSTEM CALLS LOOK LIKE PROCEDURE CALLS」)。 ↩

  12. 出处:「6. Mechanism: Limited Direct Execution」第 264–268 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:264,搜「by definition means the OS is not running」)。 ↩

  13. 出处:「6. Mechanism: Limited Direct Execution」第 274–296 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:274,搜「Cooperative Approach」)。 ↩

  14. 出处:「6. Mechanism: Limited Direct Execution」第 297–310 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:381,搜「reboot the machine」)。 ↩

  15. 出处:「6. Mechanism: Limited Direct Execution」第 313–324 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:313,搜「WITHOUT COOPERATION」)。 ↩

  16. 出处:「6. Mechanism: Limited Direct Execution」第 379–392 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:379,搜「REBOOT IS USEFUL」)。 ↩

  17. 出处:「6. Mechanism: Limited Direct Execution」第 454–509 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:466,搜「interrupt processing」)。 ↩

  18. 出处:「6. Mechanism: Limited Direct Execution」第 233–237 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:233,搜「powerful capability」)。 ↩

  19. 出处:「6. Mechanism: Limited Direct Execution」第 59–62 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:61,搜「just a library」)。 ↩

  20. 出处:「6. Mechanism: Limited Direct Execution」第 521–529 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:523,搜「lmbench」)。 ↩

  21. 出处:「6. Mechanism: Limited Direct Execution」第 530–534 段(text/08-ch06-6-mechanism-limited-direct-execution.txt:530,搜「Ousterhout」)。 ↩