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。
- 创建进程。操作系统给 p1 建进程表项、分配内存、把代码从磁盘
读进来、初始化栈(填好
argc/argv)、把内核栈填上寄存器和 PC, 然后执行一条return-from-trap2。硬件此刻恢复寄存器、切到用户态、 跳到main()——p1 开始裸跑。 - 系统调用。p1 想读文件,调用
read()。C 库里藏着trap指令: 参数和系统调用号放进约定好的位置,执行 trap——硬件保存寄存器到 p1 的内核栈、切内核态、按陷阱表跳进内核的 read 处理代码3。 内核查系统调用号、检查参数(比如那个 buffer 地址合不合法4), 办完事 return-from-trap:p1 的寄存器从内核栈弹回来,用户态,继续跑。 - 时钟抢权。p1 正跑在死循环里,几毫秒到了——时钟中断: 硬件再次把 p1 的寄存器存到它的内核栈、切内核态、跳进时钟处理程序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. 可带走的¶
- 虚拟化 CPU 的总机制 = 受限直接执行:平时裸跑(效率), 系统调用与时钟中断两个口子介入(控制)。任何「既要快又要管」的问题 都值得先想这个模板。
- 系统调用不是函数调用:它换特权级别、跳陷阱表、由硬件保存现场;
C 库把 trap 指令藏在了你熟悉的
open()背后。 - 控制权只能来自硬件(时钟中断),不能来自软件的自觉—— 合作式调度的历史教训对任何「信任上层」的设计都适用。
- 上下文切换 = 两次存档:硬件存到内核栈(暂停),操作系统搬到进程结构(存档)。
- 陷阱表是操作系统在开机时和硬件立的契约;装表本身是特权操作。
- 处理用户传入的每个参数都要当它是恶意的——内核里一次地址检查的 疏忽就能让任意程序读光全部内存。
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」) |
-
出处:「6. Mechanism: Limited Direct Execution」第 149–154 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:149,搜「trap table at boot time」)。原文:内核在引导时初始化陷阱表,硬件此后一直记住各事件对应的处理代码地址。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 173–178 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:173,搜「Fill kernel stack」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 132–141 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:132,搜「trap instruction」)。原文:trap 同时跳进内核并抬升特权级别;x86 把 PC、标志位等压入该进程的内核栈。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 216–222 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:216,搜「write()」)。旁注「警惕安全系统里的用户输入」:write 的 buffer 地址若落在内核区,必须检出并拒绝,否则任一读光内核内存。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 319–324 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:319,搜「timer interrupt」)。原文:定时器每隔若干毫秒发起中断,正在运行的进程被停下,操作系统预设的中断处理程序运行,OS 重获控制权。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 357–368 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:358,搜「context switch」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 406–413 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:406,搜「two types of register saves」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 97–103 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:102,搜「read or write the entire disk」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 113–114 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:114,搜「Atlas」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 144–148 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:147,搜「Very Bad Idea」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 79–95 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:79,搜「WHY SYSTEM CALLS LOOK LIKE PROCEDURE CALLS」)。 ↩ -
出处:「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」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 274–296 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:274,搜「Cooperative Approach」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 297–310 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:381,搜「reboot the machine」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 313–324 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:313,搜「WITHOUT COOPERATION」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 379–392 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:379,搜「REBOOT IS USEFUL」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 454–509 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:466,搜「interrupt processing」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 233–237 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:233,搜「powerful capability」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 59–62 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:61,搜「just a library」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 521–529 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:523,搜「lmbench」)。 ↩ -
出处:「6. Mechanism: Limited Direct Execution」第 530–534 段(
text/08-ch06-6-mechanism-limited-direct-execution.txt:530,搜「Ousterhout」)。 ↩