你每天都在用操作系统,但你大概说不清它到底在做什么。

这不怪你。“管理硬件资源"这种说法等于没说——它把一个非常具体、非常聪明的东西,说成了一句听不出内容的口号。

这门课换一种说法。操作系统只做三件事:

把一个 CPU 变成很多个——你的机器有 10 个核,却能同时跑几百个进程,每个进程都以为 CPU 是自己的。

让很多人同时动同一份东西而不打架——两个线程同时给一个计数器加一,结果必须是加了二。

把断电就消失的东西留下来——内存断电即空,你却指望文件明天还在。

这就是虚拟化、并发、持久化三支柱。这门课的三大部分,就是这三句话各自的展开。

一个反复出现的手法

三支柱看起来是三件不相干的事。但操作系统解决它们,用的是同一招——这一招你会在这门课里见到十几次:

加一层中间人,然后在这一层里撒一个善意的谎。

虚拟内存这一层告诉每个进程"这块内存是你的”(谎言:它是共享的,随时可能被换到磁盘上)。进程这一层告诉每段程序"CPU 是你的"(谎言:它每几毫秒就被抢走一次)。文件这一层告诉你"你在往硬盘写"(谎言:数据还躺在内存里,一断电就没了)。

这些谎言不是缺陷,是设计。没有它们,你写的每一行代码都得知道机器上还有谁在跑。

但每个谎言都要付两笔钱,这门课有一半篇幅在讲这两笔:

这门课真正教的,是识别每一层的谎言,以及知道它在哪里漏。 记住机制的名字是最容易的部分。

这门课不讲什么

实验环境

正文里的每一段代码都真的跑过,而且是在真实的 Linux 内核上跑的——不是我写的模拟器。这一点在这门课里格外重要:“我的程序输出了这个"和"操作系统就是这样工作的"是两件事,只有让真内核当裁判,前者才能推出后者。

为什么必须用容器

如果你和我一样用 Apple Silicon 的 Mac,直接在 macOS 上跑这门课的代码会处处对不上:

macOS(Apple Silicon) 容器里的 Linux
页大小 16384 字节 4096 字节(教材里所有数字的前提)
/proc/self/maps 不存在 有——能直接看到你的地址空间长什么样
strace 没有(dtruss 受 SIP 限制)

页大小这一条尤其致命:讲分页时算出来的每一个数都会差四倍。所以这门课统一在容器里跑。

一次性准备

新建一个目录,放一个 Dockerfile

FROM debian:13-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
      python3 gcc libc6-dev strace procps util-linux time \
 && rm -rf /var/lib/apt/lists/*
WORKDIR /lab
CMD ["bash"]

构建一次(只需一次):

docker build -t oslab .

之后每一篇的代码,都在这个环境里跑:

docker run --rm -it --privileged -v "$PWD:/lab" oslab

--privileged 是为了让 strace 和后面几篇的调度、cgroup 实验能用;-v "$PWD:/lab" 把当前目录挂进去,你在宿主机上编辑、在容器里运行。

代码以 Python 3 为主——它的 os 模块几乎把系统调用原样暴露出来(os.forkos.pipeos.open),不用编译,复制即跑。只有在必须看见内存布局的地方(第 9 篇的指针与 malloc、第 72/73 号实验)才落到 C。

每篇怎么读

每篇都走同一条线:

问题从哪来(一个你能想象的场景)
    → 系统怎么解(机制本身)
    → 跑一遍看看(能运行的代码,真内核裁判)
    → 代价与取舍(它放弃了什么)

看到 ⭐ 的地方是这一篇的核心断言,看到 ⚠️ 的地方是常见的错误理解。


第零部分 · 起点

第一部分 · 虚拟化 CPU

一个核,几百个进程,每个都以为核是自己的。这是怎么做到的,以及为什么"公平"这个词比看上去难得多。

第二部分 · 虚拟化内存

每个进程都以为自己独占整个内存。这一层谎撒得最大,机制也最精巧。

第三部分 · 并发

前两部分是操作系统骗你,这一部分是你自己骗自己:你以为一行代码是一步做完的。

第四部分 · 持久化

内存断电即空。文件系统要在一个随时可能断电的设备上,维持一个"永远是对的"的结构。

第五部分 · 隔离

把三支柱再做一遍,这次是为了把人和人隔开。

动手实验

六个实验,全部在上面那个容器里跑。每个实验都有一个不由我裁判的验收标准——要么真内核认,要么它不认。

习题与参考