背景
swap 子系统为每个 swap slot 维护若干元数据:它属于哪个 cgroup(swap cgroup,用一张静态数组)、是否是零页(zeromap,一张独立映射)、落在哪个 cluster。这几套结构各自为政,静态数组的开销随 swap 设备大小线性增长,大约 per-slot 2 字节,挂一个 1TB 的 swap 设备就要吃掉约 512MB 内存,哪怕一个 slot 都没用到。
┌──────────────────────────────────────┐
│ swap 元数据分散管理 │
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────────┐
│ 计费数组、零页映射、cluster 表各自为 │
│ 政 │ per-slot 约 2 字节,1TB swap 占约 512MB
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────────┐ 高阶 swapin 查状态与分配分开,留竞态窗
│ 匿名与 shmem 分配计费各自实现 │ 口
└──────────────────────────────────────┘
分配与计费的路径也不统一。匿名页和 shmem 各自实现 swap folio 的分配与 fallback;高阶 swapin 要先查 swap cache 状态、再分配、再 fallback,几步分开做,中间留着 TOCTOU 竞态窗口。memcg 的查找与计费也散落在各处。同步上的不足,在重压下还会让 VM_FAULT_OOM 偶发泄漏到缺页处理路径。
问题
- swap 元数据分散:计费数组、零页映射、cluster 表各自为政
- 静态数组开销随 swap 大小线性增长(per-slot 约 2 字节)
- 匿名与 shmem 分配计费各自实现,留竞态窗口
- 压力下同步不足,偶发缺页 OOM 泄漏
方案
整系列围绕一个"统一"展开:把分散的元数据收敛进 swap table,把各自实现的分配与计费并成一条路径。
旧:元数据分散,路径各自实现
┌──────────────────────────────────────┐
│ 计费数组、零页映射、cluster 表多套结 │
│ 构 │
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────────┐
│ 匿名与 shmem 分配计费各走各的 │ 压力下同步不足,偶发缺页 OOM 泄漏
└──────────────────────────────────────┘
新:收敛进统一 swap 表
┌──────────────────────────────────────┐
│ cgroup 编号存进 cluster 表,零页映射 │
│ 并入 swap 表 │
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────────┐ 静态元数据降到近零,高阶 swapin 在紧凑
│ 匿名与 shmem 走统一分配与计费 │ 循环内完成
└──────────────────────────────────────┘
分配上,让 swap cache 直接支持稳定的大阶分配后,匿名与 shmem 都改走它,不再各写一套。原先高阶 swapin 的"查 swap cache 状态、分配、fallback"几步合并进同一个紧凑循环,TOCTOU 窗口随之收窄;cgroup 与零页检查也挪进这个循环,进一步去冗余。order 的 fallback 与统计一并统一,调用方只需给出可接受的 order 位掩码。
计费上,swapin 时把 memcg 的查找与计费延迟并统一起来,不再散落各处。
元数据上,把 cgroup 编号直接存进 cluster table,于是那张独立的 swap cgroup 静态数组可以整张删掉,这正是 512MB 内存节省的来源;zeromap 也合并进 swap table,又少一张独立映射。这几处合起来,静态元数据开销降到接近零(per-slot 约 0.09375 字节),同步也更稳。
收益
作者在四个 workload 上做了对比,外加内存节省与 thrashing 统计。
静态元数据内存节省(挂载 1TB swap 设备):
| 指标 | 改动前 | 改动后 |
|---|---|---|
| 内存占用(used) | ~805MB | ~277MB(省 ~512MB) |
| per-slot 静态元数据 | ~2 bytes | ~0.09375 bytes |
Redis(2G VM + 6G ZRAM swap,redis-benchmark get):
| 指标 | 改动前 | 改动后 |
|---|---|---|
| RPS | 3,385,017 | 3,433,309(+1.42%) |
内核构建(48c96t,8G 内存 + 12G ZRAM + THP,make -j96 defconfig,24 次):
| 指标 | 改动前 | 改动后 |
|---|---|---|
| user time | 2904.59s | 2909.38s(持平) |
| system time | 4773.99s | 4641.55s(-2.77%) |
usemem(32c,48G brd ramdisk + 16G RAM,12 次):
| 指标 | 改动前 | 改动后 |
|---|---|---|
| Throughput | 6482.58 MB/s | 6539.28 MB/s |
| Free Latency | 371371.67us | 363059.88us |
thrashing(压力测试中 VM_FAULT_OOM 泄漏计数):
| 指标 | 改动前 | 改动后 |
|---|---|---|
| VM_FAULT_OOM 泄漏计数 | 18 | 0 |
主要收益是静态元数据近乎清零(1TB swap 省 512MB)与压力下的稳健性(缺页 OOM 泄漏 18→0);各 workload 性能持平或略升(Redis +1.42%、内核构建 sys time -2.77%)。