一个 swap slot 的计数
匿名页换出后占一个 swap slot
count = 有多少处引用这个 slot
count 归 0,槽位才能释放
计数怎么变
dup
+1
put
-1
count == 0
可释放
每一次引用变化都要落到账本上
为什么要单独记账
共享页、多次映射
计数准,释放时机才对
提前释放会踩内存
漏记会占住槽位
计数是 swap 的核心账本
旧实现把它放在 swap_map 里
新实现把它放进 swap table
旧账本:swap_map
unsigned char *swap_map;
每个 slot 1 字节
per-device vmalloc 数组
count 直接写在这一字节里
随设备大小线性增长
1TB swap 设备
swap_map 约 256MB
与实际使用量无关
挂上设备就占住
计数超上限怎么办
COUNT_CONTINUED
连续页接管计数
还要 SWP_CONTINUED 标志
机制复杂,额外页开销
这还只是 swap_map
swap table 另有一套 slot 状态
phase I 之后
swap table 按 cluster 建立
slot 的状态已经在表里
分配、缓存、坏槽位都能查到
但计数还在旧账本
swap table 更新一次
swap_map 再更新一次
同一个 slot,两套写入
两套状态必须保持一致
复杂度叠加
高计数还要 continuation
坏槽位也要单独标记
两套机制都要维护
静态数组还一直占内存
双重更新的根源
slot 状态已经在 swap table
计数却还留在 swap_map
计数进表项高位
COUNT
高位:计数
FLAGS
标记位
shadow / PFN
低位:内容与标记
表项最多存 SWP_TB_COUNT_MAX - 1 个计数
计数操作统一入口
dup / put
alloc / free
都在 cluster lock 下更新
调用方不再碰 swap_map
唯一 per-slot 账本
状态与计数同源
计数变化与状态变化同锁
不再更新 swap_map
双重维护消失
不是把 swap_map 换个名字
计数字段进入 swap table 表项
计数到 SWP_TB_COUNT_MAX - 1
表项还能放下
再涨,高位放不下了
需要一个溢出区
extend_table
每个 cluster 一个
unsigned int 数组
大小是 cluster 槽位数
需要时才分配
溢出与回收
溢出 -> 计数搬到扩展表
降回 -> 收回扩展表
cluster 锁保护
不改变计数语义
不是全局大数组
只给真正溢出的 cluster
swap table 与 extend_table 都基于 cluster
整张移除
swap_map
SWP_CONTINUED / COUNT_CONTINUED
cont_lock
旧计数路径和它的锁一起消失
坏槽位
不再写 swap_map
SWP_TB_BAD 直接标记
状态与计数同表
少一套坏槽位记账
workingset shadow
anon shadow 预留计数位
高位留给 swap count
影子编码不冲突
回收信息仍可用
扫描边界 / 影子清理
既有逻辑顺势简化
不是新的回收策略
1TB brd swap,free -m
1,051 MB
Before
used
795 MB
After
used
256 MB
省下
静态元数据
swap_map 整张移除,设备越大省得越多
省的是静态元数据
不是 page cache,也不是压缩内存
大小与 swap 设备容量成正比
单场景:1TB brd
不外推到所有 swap 配置
内核构建系统时间(32 次均值)
ZSWAP+NVME · x86_64 VM 5G · -j48 · defconfig
1038.97s -> 1013.75s (-2.4%)
ZRAM · ARM64 VM 1.5G · -j12 · tinyconfig
67.75s -> 66.65s (-1.6%)
Redis/Valkey(ZRAM,ARM64 VM 1.5G,64 次均值)
无持久化 472,705.71 -> 481,197.93 RPS (+1.8%)
BGSAVE 369,451.68 -> 374,922.32 RPS (+1.5%)
全局压力下测吞吐
performance is better in all cases
memory usage is much lower
后续阶段:cgroup 数组再省约 60% 静态元数据
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x