# 第 2 章实验:操作系统的三个主题,各跑一遍 原书第 2 章用 4 个小程序分别演示 **CPU 虚拟化、内存虚拟化、并发、持久化**。 这里是它们的可编译版本(`common.h` / `common_threads.h` 与原书配套代码等价)。 ```bash cd labs/02-intro make # 编译 cpu mem threads io ``` | 目标 | 做什么 | 该观察到什么 | |---|---|---| | `./cpu A` | 每秒打印一次 A,永远循环,Ctrl-C 结束 | 一个程序独占 CPU 时的样子 | | `make cpu4` | 用 `taskset -c 0` 把 `cpu A/B/C/D` 四个进程绑到**同一个** CPU,跑 4 秒 | 只有一个 CPU,四个程序却"同时"在输出 —— OS 在它们之间快速切换(CPU 虚拟化) | | `make mem2` | 两个 `mem` 同时跑;`setarch -R` 关掉地址空间随机化(ASLR) | 两个进程打印出**完全相同**的地址,却各自独立地从 1 数起 —— 每个进程有自己的私有虚拟地址空间 | | `make mem2-aslr` | 同上但不关 ASLR | 地址每次都不一样:现代 Linux 默认随机化布局(原书脚注 5 说的就是这个) | | `make race` | `./threads N`,N 从 1000 到 1000000,各跑 3 次 | N 小时结果常常正确(2N);N 大时结果比 2N 小,而且每次都不同 —— `counter++` 不是原子操作 | | `make io-run` | `open → write → close` 写出 `/tmp/file` | 三个系统调用把数据交给文件系统,数据在断电后依然存在(持久化) | ## 为什么 - **cpu4**:`Spin(1)` 是忙等(一直占着 CPU 读时钟)。四个进程都想要 CPU,但 0 号 CPU 只有一个, 操作系统用**时分共享(time sharing)**让它们轮流运行,于是看起来每个程序都有一个"自己的 CPU"。 打印顺序(A C B D …)由调度器决定,每次运行可能都不同。 - **mem2**:打印的是**虚拟地址**。两个进程的虚拟地址 `0x5555555592a0` 相同,但被操作系统映射到**不同的物理内存**, 所以一个进程写 `*p` 不会影响另一个。第 13–23 章会讲清楚这种映射是怎么做的。 - **race**:`counter++` 编译成 load / add / store 三条指令。两个线程在多核上真正并行, 可能都读到同一个旧值、各自加 1、再先后写回 —— 两次加法只生效一次(丢失更新)。 N 越大,两个线程运行时间重叠得越多,出错越明显。第 26–28 章会讲如何用锁解决。 - **io-run**:`write()` 返回并不代表数据已在磁盘上 —— 文件系统通常会先把写入缓存起来再批量写盘; 需要立即落盘时要用 `fsync()`(第 39 章)。 ## 示例输出(你的机器上 PID、地址、数字会不同) ``` $ make cpu4 A C B D C A D B ... $ make mem2 (342669) address pointed to by p: 0x5555555592a0 (342668) address pointed to by p: 0x5555555592a0 (342668) p: 1 (342669) p: 1 (342668) p: 2 (342669) p: 2 $ make race loops=1000 Final value : 2000 loops=100000 Final value : 186011 loops=1000000 Final value : 1334958 ```