读书笔记 · 约 18,063 字 · 预计推送 2 部分

《Operating Systems: Three Easy Pieces》选读精读:虚拟化·并发·持久性三大主题核心章 未开始

目录

题记与说明


0. TL;DR

OSTEP 把操作系统拆成三个"容易的片段"(书名戏仿 Feynman《物理学讲义》):虚拟化(把少量物理 CPU/内存变成每个进程独享的无限资源)、并发(多线程共享地址空间带来的原子性问题)、持久性(断电崩溃下如何更新磁盘数据结构)。精读的 7 章给出一条贯穿全书的分析骨架:抽象 → 机制(mechanism)→ 策略(policy)。CPU 虚拟化 = 进程抽象 + 受限直接执行(用户态/内核态、trap、时钟中断)+ 调度策略;内存虚拟化 = 分页(灵活性 + 空闲管理简单,代价是页表又大又慢);并发 = 竞态条件 + "硬件原子指令 + OS 睡眠/唤醒"逐级搭建锁的阶梯(关中断 → flag 失败 → test-and-set → CAS/LL-SC/ticket → yield → 队列 park/unpark → futex 两阶段);持久性 = "把磁盘当磁盘"的 FFS 分组布局 + 日志(write-ahead logging)把崩溃恢复从 O(整个磁盘) 降到 O(日志)。全书方法论最大的特色是每章一个 THE CRUX 问题、机制与策略分离、以及大量可运行模拟器作业。


1. 核心问题

这组材料共同回答的问题是:操作系统的三大资源(CPU、内存、磁盘)各自面临什么根本矛盾,用什么抽象 + 机制 + 策略组合去化解? 拆成三个子问题:

  1. 虚拟化(CPU + 内存):只有几个物理 CPU、一份物理内存,如何给每个进程"近乎无限"的错觉?核心张力是 Ch6 开篇点明的两个目标——性能(不加过多开销)与控制(OS 必须 retain control,"否则一个进程可以永远占着机器");内存侧则是分页如何同时做到灵活、不外部碎片,又不像朴素实现那样"又慢又占内存"。
  2. 并发:多个执行流共享同一地址空间时,counter = counter + 1 这种三指令序列为什么会被中断撕碎?需要硬件提供什么最小支持、OS 提供什么支持,才能既互斥、又公平、又高性能?
  3. 持久性:断电/崩溃可能发生在任意两次写之间,如何保证磁盘上的元数据自洽?旧文件系统为什么只跑出 2% 磁盘带宽,怎样让文件系统"磁盘感知"?崩溃后恢复为什么 fsck 不可持续,日志凭什么更快?

2. 主要主张

2.1 虚拟化·CPU:抽象(进程)+ 机制(受限直接执行)双支柱

"The definition of a process, informally, is quite simple: it is a running program."(Ch4 开篇)
"THE CRUX OF THE PROBLEM: HOW TO PROVIDE THE ILLUSION OF MANY CPUS? ... The OS creates this illusion by virtualizing the CPU... This basic technique, known as time sharing of the CPU, allows users to run as many concurrent processes as they would like; the potential cost is performance."(Ch4, p.1–2)
"The mechanism as providing the answer to a *how* question... The policy provides the answer to a *which* question... Separating the two allows one easily to change policies without having to rethink the mechanism and is thus a form of modularity."(Ch4 TIP 框)
"user code cannot specify an exact address to jump to, but rather must request a particular service via number. This level of indirection serves as a form of protection."(Ch6, §6.2)
"without limits on running programs, the OS wouldn't be in control of anything and thus would be 'just a library' -- a very sad state of affairs for an aspiring operating system!"(Ch6, p.2)
"a timer device can be programmed to raise an interrupt every so many milliseconds; when the interrupt is raised, the currently running process is halted, and a pre-configured interrupt handler in the OS runs."(Ch6, §6.3)

2.2 虚拟化·内存:分页用固定大小换掉外部碎片,但引入"又大又慢"两难题

"we divide it into fixed-sized units, each of which we call a page. Correspondingly, we view physical memory as an array of fixed-sized slots called page frames."(Ch18 开篇;思想源自 Atlas [KE+62])
"we get an immense 4MB of memory needed for each page table! ... this means the OS would need 400MB of memory just for all those address translations!"(Ch18, §18.2)
"paging requires us to perform one extra memory reference in order to first fetch the translation from the page table. That is a lot of work! Extra memory references are costly, and in this case will likely slow down the process by a factor of two or more."(Ch18, §18.4)

5 行循环数组的内存 trace 里每轮循环 10 次访存(4 次取指 + 1 次显式写 + 5 次页表访问,Ch18 图 18.7)。

2.3 并发:竞态是"未受控调度"的必然,锁 = 硬件原子原语 + OS 睡眠支持的渐进搭建

"each thread is very much like a separate process, except for one difference: they share the same address space and thus can access the same data."(Ch26 开篇)

上下文切换不换页表;每个线程一个栈("this ruins our beautiful address space layout",Ch26 §26 开头图 26.1 讨论)。

"What we have demonstrated here is called a race condition (or, more specifically, a data race): the results depend on the timing of the code's execution."(Ch26, §26.4)

术语(critical section / race condition / indeterminate / mutual exclusion)皆出自 Dijkstra [D65,D68];"the OS was the first concurrent program"(Ch26, §26.7)解释了为什么在 OS 课讲并发。

2.4 持久性:先"把磁盘当磁盘"(FFS),再"把崩溃当常态"(日志)

"performance started off bad and got worse over time, to the point where the file system was delivering only 2% of overall disk bandwidth!"(Ch41, §41.1,引 McKusick 等 [MJLF84] 的 Berkeley 实测)

病根:"the old UNIX file system treated the disk like it was a random-access memory"——数据与 inode 相距遥远、free list 碎片化、512B 块太小。

"keep related stuff together (and its corollary, keep unrelated stuff far apart)."(Ch41, §41.4)

目录放"目录少、空闲 inode 多"的组;文件数据与 inode 同组;同目录文件同组。

"Making a system usable is often as or more important than its deep technical innovations."(Ch41 TIP 框)

章末一句话总结:"treat the disk like it's a disk."

"THE CRUX: HOW TO UPDATE THE DISK DESPITE CRASHES ... crashes can occur at arbitrary points in time, how do we ensure the file system keeps the on-disk image in a reasonable state?"(Ch42 开篇)
"This situation is akin to dropping your keys on the floor in your bedroom, and then commencing a search-the-entire-house-for-keys recovery algorithm, starting in the basement and working your way through every room."(Ch42, §42.2)

3. 方法


4. 数据与证据

数据点数值出处(本次精读原文)
旧 UNIX FS 峰值磁盘带宽利用率2%(且随时间恶化)Ch41 §41.1,引 [MJLF84] Berkeley 实测
朴素线性页表大小4MB/进程(32 位 AS + 4KB 页 + 4B PTE);100 进程 = 400MBCh18 §18.2
分页无 TLB 的访存放大每次访存多 1 次额外访存,"slow down ... by a factor of two or more";示例循环每轮 10 次访存Ch18 §18.4–18.5
系统调用/上下文切换成本(1996, Linux 1.3.37, 200MHz P6)≈4μs / ≈6μs;现代系统亚微秒Ch6 ASIDE,引 lmbench [MS96]
竞态实验期望 20,000,000,实测 19345221 / 19221041,且逐次不同Ch26 §26.3
SEER trace 的名字空间局部性≈7% 重开上一文件;≈40% 距离≤1;另有 ≈25% 距离=2(FFS 未覆盖)Ch41 §41.5
摊销块大小(40MB/s、10ms 定位)50% 带宽→409.6KB;90%→≈3.6MB;99%→≈39.6MBCh41 式 41.1、图 41.2
FFS 代码量原版 ≈1200 行;BSD 后裔 ≈5 万行Ch41 参考文献 [MJLF84] 注
日志恢复复杂度O(磁盘体积) → O(日志体积)Ch42 §42.5

充分性评价:证据链是"经典论文实测 + 作者组自己的 trace/系统研究 + 可复算的算例",对一本教材而言自洽且可核查(每条数字都能追到文末参考文献);但它是教科书式论证而非系统性实验报告——数字多为单点示例(一个 trace、一个盘参数),不是跨工作负载的系统评测;竞态实验只在作者机器上跑过两个样本。


5. 结论

  1. 虚拟化的统一答案是"抽象 + 机制 + 策略"三层解耦:CPU 侧进程抽象靠 LDE(双模式 + trap table + 时钟中断 + 上下文切换)落地,性能与控制缺一不可;内存侧分页用定长切分换来灵活与简单的空闲管理,代价(页表大而慢)正是后续 TLB、多级页表、大页要解决的。
  2. 并发的本质是"未受控调度撕碎非原子序列":正确解 = 硬件最小原子原语(test-and-set/CAS/LL-SC/fetch-and-add)负责互斥正确性 + OS(yield/park/unpark/futex)负责不自旋不饿死;现实锁是"两阶段"混合体。评价锁永远用三把尺:互斥、公平、性能。
  3. 持久性的两条主线:性能上"把磁盘当磁盘"(FFS 柱面组/块组 + 相关性放置 + 摊销),正确性上"把崩溃当常态"(write-ahead logging 把恢复降为 O(日志),ordered metadata journaling 是工业界默认折中)。"先写被指者、后写指人者"是贯穿一切崩溃一致性方案的元规则。
  4. 全书真正的元结论是设计范式本身:机制/策略分离、失败方案显式化、每个抽象都付一次可量化的代价。

6. 局限


7. 对我的启发

  1. "机制/策略分离"可以直接搬进我的系统设计与工作流工程:how(怎么切、怎么存、怎么重试)与 which(选哪个、给谁、何时触发)分开演进,是所有"可替换策略层"的母型——从调度器到 LLM agent 的工具选择策略皆然。
  2. "treat the disk like it's a disk"是一切性能工程的原话:性能问题先问介质特性再谈代码。这条在现代依然成立——vLLM 的 PagedAttention 把 OS 分页思想搬进 KV cache 管理、数据库把 WAL 思想搬进存储,都是同一个"抽象跨层移植"套路;我之前精读的 SQLite WAL(2026-10-31 笔记)正是 Ch42 write-ahead logging 在单文件数据库里的直系后代,两边对"commit 时刻 = 写一条提交记录"的定义完全同构。
  3. 受限直接执行 = "fast path + 受控慢路径"模式的原型:futex 的无竞争 fast path、vDSO、用户态运行时 fast path 全是它的变体——先让 99% 的情况零干预跑过去,把控制权问题留给 1% 的 trap。
  4. 锁的构建阶梯是绝佳的"从第一性原理推导工程"教学样本:每个失败方案都有具体的 interleaving 证据;这提示我在评审并发代码时,与其背规则,不如随手画 interleaving 表格(Ch28 图 28.2 的做法)。
  5. 崩溃一致性的"先写被指者"规则可迁移到任何持久化管道:写 checkpoint/写索引/写元数据的顺序设计,本质都是这条规则;日志"用顺序追加的便宜写,买恢复时只看日志的便宜读"是经典的摊销交易。
  6. 读书方法收获:OSTEP 的 CRUX 框 + 失败方案阶梯 + 模拟器作业,值得套用到我读任何系统论文/源码——先找"crux",再列失败方案,最后跑一个最小模拟验证。

附 A:关键原文引述对照表(章节 → 定位)

#引述(英文原文)出处
1"it is a running program"Ch4 开篇
2"HOW TO PROVIDE THE ILLUSION OF MANY CPUS?" / "time sharing of the CPU"Ch4 p.1–2 CRUX
3"Separating the two allows one easily to change policies without having to rethink the mechanism"Ch4 TIP(引 [L+75] Hydra)
4"HOW TO EFFICIENTLY VIRTUALIZE THE CPU WITH CONTROL"Ch6 p.1 CRUX
5"the OS wouldn't be in control of anything and thus would be 'just a library'"Ch6 §6.1 末
6"user code cannot specify an exact address to jump to, but rather must request a particular service via number"Ch6 §6.2
7"a timer device can be programmed to raise an interrupt every so many milliseconds..."Ch6 §6.3
8"we divide it into fixed-sized units, each of which we call a page"Ch18 开篇
9"an immense 4MB of memory needed for each page table!... 400MB ... just for all those address translations!"Ch18 §18.2
10"slow down the process by a factor of two or more"Ch18 §18.4
11"each thread is very much like a separate process, except for one difference: they share the same address space"Ch26 开篇
12"race condition (or, more specifically, a data race): the results depend on the timing of the code's execution"Ch26 §26.4
13"the OS was the first concurrent program"Ch26 §26.7
14"HOW TO BUILD A LOCK"(CRUX)+ 评价三标准(互斥/公平/性能)Ch28 §28.3–28.4
15"don't work on modern hardware (due to relaxed memory consistency models)"(Peterson/Dekker)Ch28 ASIDE
16"compare-and-swap is a more powerful instruction than test-and-set"Ch28 §28.9
17"a two-phase lock realizes that spinning can be useful..."Ch28 §28.16
18"delivering only 2% of overall disk bandwidth!"Ch41 §41.1
19"keep related stuff together (and its corollary, keep unrelated stuff far apart)"Ch41 §41.4
20"treat the disk like it's a disk."Ch41 §41.8
21"HOW TO UPDATE THE DISK DESPITE CRASHES"(CRUX)Ch42 开篇
22"search-the-entire-house-for-keys recovery algorithm"(评 fsck)Ch42 §42.2
23"write the pointed-to object before the object that points to it"Ch42 §42.3
24"reduces recovery time from O(size-of-the-disk-volume) to O(size-of-the-log)"Ch42 §42.5

附 B:本次运行的实际命令记录

← 资料库今日播报 →