内存管理-77-kernel-internals系列-Memory专题-Caching & Reclaim-9-zswap
一、zswap Internals: Compressed Swap Cache 章节翻译
注: 翻译自 https://kernel-internals.org/mm/zswap/
zswap 内部:压缩 Swap 缓存
在去往 swap 设备的路上拦截页面的压缩 RAM 缓存 —— 用 CPU 换 I/O
1. zswap 是什么(以及不是什么)
zswap 是一个回写式压缩缓存,坐在 swap 设备前面。当内核决定换出一个匿名页时,zswap 把它拦下来,压缩后存进基于 RAM 的池里。压缩池满了,就把最冷的条目逐出回后端 swap 设备。
核心洞察:从压缩后的 RAM 读,比读磁盘甚至 SSD 快数个量级。zswap 用 CPU 换 I/O 缩减,这笔交易通常很划算。
zswap vs zram —— 它们不是一回事
这两者经常被混淆:
(1) zswap:任何 swap 设备前面的压缩缓存。它本身不是块设备。后端 swap 设备(磁盘、SSD,甚至 zram 设备)依然存在。
(2) zram:本身就是 swap 设备的压缩块设备。它在内存里完全替代 swap 设备。
两者可以叠加:用 zram 做 swap 设备,前面再加 zswap。但这意味着页面被压缩两次 —— zswap 一次、zram 一次 —— 浪费 CPU,额外收益极小。见与 zram 的交互。
2. 架构总览
注: 可见原文图
(1) 存路径(swap-out):匿名页需回收 → zswap 是否启用且池未满?否 → 直接写 swap 设备;是 → zswap_store → zswap_compress(每 CPU acomp_ctx)→ 能压到小于 PAGE_SIZE?是 → zs_malloc + zs_obj_write 进 zsmalloc 池;否 → 存未压缩(length == PAGE_SIZE)→ zswap_entry 加入 xarray + LRU。
(2) 缺页路径(swap-in):swap PTE 缺页 → zswap_load → xarray 里有条目?否 → 从 swap 设备读;是 → zswap_decompress 到目标 folio → 失效条目、从池里释放。
(3) 回写路径(池满):shrink_worker / zswap_shrinker → 遍历 LRU(shrink_memcg_cb)→ 条目 referenced?是 → 清 referenced、在 LRU 里轮转;否 → zswap_writeback_entry → 解压到 swap cache folio → __swap_writepage 写后端设备 + zswap_entry_free。池满会触发回写。
Frontswap 已经没了:
在 Linux 6.x 之前,zswap 经 frontswap 接口(frontswap_store、frontswap_load 等)挂进内核。该接口已删除。zswap 现在直接集成:swap 写回和换入路径显式调用 zswap_store() 和 zswap_load()。入口在 include/linux/zswap.h 声明。
3. 关键数据结构
3.1 struct zswap_pool
struct zswap_pool {
struct zs_pool *zs_pool; /* 存压缩数据的 zsmalloc 池 */
struct crypto_acomp_ctx __percpu *acomp_ctx; /* 每 CPU 压缩上下文 */
struct percpu_ref ref; /* 引用计数 */
struct list_head list; /* 在全局 zswap_pools 链表中的节点 */
struct work_struct release_work;
struct hlist_node node; /* cpuhp 回调用 */
char tfm_name[CRYPTO_MAX_ALG_NAME]; /* 压缩器名 */
};
每个池绑定单一压缩算法。运行时换压缩器会建新池。旧池一直活到里面条目被逐出或载入完,再经 zswap_pool_destroy() 销毁。
3.2 struct zswap_entry
struct zswap_entry {
swp_entry_t swpentry; /* swap type + offset —— 查找键 */
unsigned int length; /* 压缩后字节数;== PAGE_SIZE 表示不可压缩 */
bool referenced; /* LRU 回写的第二次机会位 */
struct zswap_pool *pool; /* 所属池 */
unsigned long handle; /* zsmalloc 分配句柄 */
struct obj_cgroup *objcg; /* cgroup 计费 */
struct list_head lru; /* 在全局 zswap_list_lru 中的位置 */
};
zswap 里存的每个压缩页对应一个 zswap_entry。条目存在按 swap type 分的 xarray 里,按 64MiB 分片:
#define ZSWAP_ADDRESS_SPACE_SHIFT 14 /* 2^14 = 16384 页 = 64 MiB */ static struct xarray *zswap_trees[MAX_SWAPFILES];
swap_zswap_tree() 把 swp_entry_t 映射到正确的 xarray 分片。
3.3 struct crypto_acomp_ctx
struct crypto_acomp_ctx {
struct crypto_acomp *acomp; /* 异步压缩变换 */
struct acomp_req *req; /* 预分配的请求 */
struct crypto_wait wait; /* 完成等待 */
u8 *buffer; /* PAGE_SIZE 暂存 buffer */
struct mutex mutex; /* 每 CPU 一次只做一个操作 */
};
每 CPU 每池一个上下文。acomp_ctx_get_cpu_lock() 拿当前 CPU 上下文的锁;acomp_ctx_put_unlock() 放锁。
4. 压缩流水线
4.1 存路径:zswap_store()
当 MM 子系统要换出一个 folio,调用 zswap_store(struct folio *folio):
zswap_store(folio)
├── 查 zswap_enabled
├── obj_cgroup_may_zswap() ← cgroup zswap.max 限制
├── zswap_check_limits() ← 全局 max_pool_percent 限制
├── zswap_pool_current_get() ← 当前池的 RCU 安全引用
└── folio 里每页:zswap_store_page(page, objcg, pool)
├── zswap_entry_cache_alloc() ← kmem_cache 分配
├── zswap_compress(page, entry, pool)
│ ├── acomp_ctx_get_cpu_lock()
│ ├── crypto_acomp_compress() + crypto_wait_req()
│ ├── 若 dlen >= PAGE_SIZE 且该 cgroup 允许回写:
│ │ 存未压缩(dlen = PAGE_SIZE)
│ └── zs_malloc() + zs_obj_write()
├── xa_store() 进 swap xarray
└── zswap_lru_add() ← entry.referenced = true
任何原因存失败(池满、分配失败、压缩错误),该 swap slot 之前存的条目经 xa_erase() + zswap_entry_free() 失效。防止旧压缩数据被新版本页面覆盖。
4.2 压缩:zswap_compress()
zswap 用内核异步压缩 API(crypto/acompress.h):
(1) 输入 scatter-gather 指向源页面。
(2) 调 crypto_acomp_compress() —— 虽是异步 API,zswap 经 crypto_wait_req() 同步等。
(3) 若 dlen >= PAGE_SIZE(压不动)且该 cgroup 允许回写,就存未压缩(entry->length == PAGE_SIZE)。保留 LRU 顺序,冷的不可压缩页以后还能写回。
(4) zs_malloc() 在 zsmalloc 池里分配空间;zs_obj_write() 写入压缩字节。
不可压缩页:
页面压不到 PAGE_SIZE 以下时,zswap 用 kmap_local_page() 直接拷贝存原文。经 zswap_stored_incompressible_pages 跟踪,暴露在 debugfs。
若该 cgroup 关了回写(memory.zswap.writeback=0),不可压缩页直接拒绝 —— 存一个永远逐不出去的压不动的页没有意义。
4.3 解压:zswap_decompress()
zswap_load()(缺页路径)和 zswap_writeback_entry()(回写路径)都会调:
zs_obj_read_sg_begin() // 把 zsmalloc 对象映射成 scatter-gather
if entry->length == PAGE_SIZE:
memcpy_from_sglist() // 不可压缩:直接拷贝
else:
crypto_acomp_decompress() + crypto_wait_req()
zs_obj_read_sg_end()
zsmalloc 对象可能跨页边界,所以输入恒为 1~2 个条目的 scatter-gather 链表。
4.4 载入路径:zswap_load()
缺页时,换入代码调 zswap_load(struct folio *folio):
(1) 在 xarray 查 swp_entry_t → 拿到 zswap_entry。
(2) zswap_decompress() 到缺页的目标 folio。
(3) 若载入到 swap cache(常见情况),立即失效 zswap 条目 —— swap cache 成为权威所有者。
(4) folio 标记 uptodate 并解锁。
大 folio 限制:
zswap_load() 显式拒绝大 folio:WARN_ON_ONCE(folio_test_large(folio)) 后返回 -EINVAL。大 folio 可能只有部分子页存在 zswap(每子页独立存),载入路径处理不了。结果是 do_swap_page() 触发 SIGBUS。
5. 池后端:zsmalloc
当前内核 zswap 只用 zsmalloc 做内存分配器。老后端(zbud、z3fold)zswap 已不用。CONFIG_ZSWAP 自动选中 CONFIG_ZSMALLOC。
5.1 zsmalloc 如何工作
zsmalloc 是为存压缩页设计的变长 slab 分配器。关键属性:
----------------------------------------------------------------------------------------------------------------------------- 属性 细节 ----------------------------------------------------------------------------------------------------------------------------- 分配粒度 8 字节对齐的 size class,从约 32 字节到 PAGE_SIZE 内部结构 叫 zspage 的物理页组 寻址 对象不能直接寻址 —— 经不透明 handle 访问 碎片控制 按满度(0%、10%、…、99%、100%)分组 每 zspage 最大页数 经 CONFIG_ZSMALLOC_CHAIN_SIZE 可配 -----------------------------------------------------------------------------------------------------------------------------
zsmalloc 对象不能直接寻址,zs_obj_write() 和 zs_obj_read_sg_begin() 在内部处理映射和 scatter-gather 搭建。

zsmalloc 对象密度高(尤其压缩输出很小时),这就是它替代 zbud(每物理页存 2 页)和 z3fold(每物理页最多 3 页)的原因 —— 压缩比好的负载受益巨大。
6. 池管理与回写
6.1 全局池大小限制
static unsigned int zswap_max_pool_percent = 20;
池限制为 totalram_pages() * zswap_max_pool_percent / 100 个物理页的压缩存储。每次存都调 zswap_check_limits():
static bool zswap_check_limits(void)
{
unsigned long cur_pages = zswap_total_pages();
unsigned long max_pages = zswap_max_pages();
if (cur_pages >= max_pages) {
zswap_pool_limit_hit++;
zswap_pool_reached_full = true;
} else if (zswap_pool_reached_full && cur_pages <= zswap_accept_thr_pages()) {
zswap_pool_reached_full = false;
}
return zswap_pool_reached_full;
}
池触顶时 zswap_pool_reached_full = true。新存被拒绝,直到池排到 accept_threshold_percent 以下(默认最大值的 90%)。该回滞避免在边界抖动。
池满导致存失败时,zswap_store() 排队 zswap_shrink_work 触发后台回写。
6.2 全局 LRU
所有池的 zswap_entry 共享一个全局 list_lru:
static struct list_lru zswap_list_lru;
该 LRU 是 NUMA 感知、memcg 感知的。条目存时加进 LRU,entry->referenced = true。
6.3 Shrinker 与回写:shrink_worker()
两种机制触发向后端 swap 设备回写:
(1) 内存压力 shrinker(zswap_shrinker):注册为标准内核 shrinker(SHRINKER_NUMA_AWARE | SHRINKER_MEMCG_AWARE)。分配器感到压力时调 zswap_shrinker_scan() → list_lru_shrink_walk() → 每个 LRU 条目调 shrink_memcg_cb()。
(2) 池满 workqueue(shrink_worker):zswap_pool_reached_full 置位且存失败时,在 zswap-shrink workqueue 上排 shrink_worker()。轮询 memcg 调 shrink_memcg(),直到池排到 zswap_accept_thr_pages() 以下。
6.4 第二次机会 LRU 算法
shrink_memcg_cb() 实现第二次机会逐出:
if (entry->referenced) {
entry->referenced = false;
return LRU_ROTATE; /* 再给一次机会 */
}
/* referenced == false:写回 swap 设备 */
writeback_result = zswap_writeback_entry(entry, swpentry);
新条目 referenced = true。shrinker 第一次遇到清标志并转到尾部。第二次遇到(referenced 已是 false)才真正回写。
6.5 Shrinker 校准
zswap_shrinker_count() 按几个因子缩放可收缩对象数,避免过度收缩:
(1) 压缩比:mult_frac(nr_freeable, nr_backing, nr_stored) —— 若池压缩比 4:1,每次回释放的物理页少,所以上报的候选也少。
(2) 磁盘 swapin 惩罚:减去 nr_disk_swapins(在 zswap_lruvec_state.nr_disk_swapins 跟踪)。若观察到从磁盘 swapin,说明之前从 zswap 逐出过度,放慢。
zswap_writeback_entry()
6.5 zswap_writeback_entry()
单个条目的回写:
zswap_writeback_entry(entry, swpentry) ├── swap_cache_alloc_folio() ← 在 swap cache 分配 folio ├── xa_load() 确认条目仍有效 ├── zswap_decompress(entry, folio) ├── xa_erase() 出 xarray ├── zswap_entry_free() ← 从池里释放 ├── folio_mark_uptodate() └── __swap_writepage() ← 发写到后端 swap 设备
若并发换入先分配了 folio(!folio_was_allocated),跳过回写 —— 页面刚变热,逐出就错了。
7. 相同填充页检测
关于本内核版本的说明
老版本上游文档描述过同值填充(零页)优化:压缩前检查页面是否内容均匀,只存填充模式。但搜当前 mm/zswap.c 源码,该优化在本版本不存在。zswap_store_page() 或 zswap_compress() 里没有 page_same_filled() 或类似逻辑。
不可压缩页有处理(允许回写时按完整 PAGE_SIZE 存未压缩),但没有单独的同值检测路径。stored_incompressible_pages debugfs 计数跟踪按完整大小存的页。
8. Per-Cgroup 限制
三个 cgroup v2 文件控制 zswap 按 cgroup:
------------------------------------------------------------------------------------------------------------------------------------- 文件 说明 ------------------------------------------------------------------------------------------------------------------------------------- memory.zswap.max 该 cgroup 在 zswap 最多用多少压缩内存字节。默认 max(不限) memory.zswap.current 只读:该 cgroup 当前在 zswap 用了多少字节 memory.zswap.writeback 能否从 zswap 写回到 swap 设备。0 关该 cgroup 回写。默认 1 -------------------------------------------------------------------------------------------------------------------------------------
每次存前 obj_cgroup_may_zswap() 查 memory.zswap.max。超限先 shrink_memcg() 收该 cgroup 的页。收不动就拒绝存,folio 直接走 swap 设备。
# 限制某 cgroup 最多 512MiB zswap echo 536870912 > /sys/fs/cgroup/myapp/memory.zswap.max # 关某 cgroup 的 zswap 回写(页要么留 zswap、要么被拒绝) echo 0 > /sys/fs/cgroup/myapp/memory.zswap.writeback
writeback=0 与不可压缩页:
若 memory.zswap.writeback=0 又遇到不可压缩页,zswap 整个拒绝存(zswap_compress() 返回 false)。页面穿透到 swap 设备。这可能形成反馈环:同一不可压缩页被 zswap 反复拒绝、反复触发 swap I/O。
9. Sysfs 可调项
都在 /sys/module/zswap/parameters/ 下:
------------------------------------------------------------------------------------------------------------------------------------- 参数 默认 说明 ------------------------------------------------------------------------------------------------------------------------------------- enabled CONFIG_ZSWAP_DEFAULT_ON 运行时开/关 zswap。关只停新存,不刷已有条目 compressor CONFIG_ZSWAP_COMPRESSOR_DEFAULT(lzo) 压缩算法。换会建新池;旧池排空后释放 max_pool_percent 20 压缩池最多占总 RAM 百分比 accept_threshold_percent 90 触顶后池降到最大值的该百分比才恢复接受。设 100 关回滞 shrinker_enabled CONFIG_ZSWAP_SHRINKER_DEFAULT_ON(off) 开内存压力驱动的 shrinker。关则只在池满时回写 -------------------------------------------------------------------------------------------------------------------------------------
节点配置:
# 运行时开 zswap echo 1 > /sys/module/zswap/parameters/enabled # 换 zstd(建新池;旧条目仍在 lzo 池,用老算法解压直到逐出) echo zstd > /sys/module/zswap/parameters/compressor # 池上限提到 RAM 的 30% echo 30 > /sys/module/zswap/parameters/max_pool_percent # 开内存压力 shrinker echo Y > /sys/module/zswap/parameters/shrinker_enabled # 减小回滞 —— 池到最大的 70% 就恢复接受 echo 70 > /sys/module/zswap/parameters/accept_threshold_percent
也可在启动时设:
zswap.enabled=1 zswap.compressor=zstd zswap.max_pool_percent=25
10. Kconfig 选项
------------------------------------------------------------------------------------------------------------------------------------- 选项 说明 ------------------------------------------------------------------------------------------------------------------------------------- CONFIG_ZSWAP 开 zswap。自动选中 CONFIG_ZSMALLOC CONFIG_ZSWAP_DEFAULT_ON 开机默认开 zswap CONFIG_ZSWAP_SHRINKER_DEFAULT_ON 默认开内存压力 shrinker CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZO/LZ4/LZ4HC/ZSTD/DEFLATE/842 默认压缩器 CONFIG_ZSMALLOC zsmalloc 分配器(zswap 必需) CONFIG_ZSMALLOC_STAT 经 debugfs 导出按 size class 统计 -------------------------------------------------------------------------------------------------------------------------------------
11. 可观测性
11.1 debugfs:/sys/kernel/debug/zswap/
所有计数只读、近似(相互之间非原子一致,各自 u64 准确):
------------------------------------------------------------------------------------------------------------------------------------- 文件 说明 ------------------------------------------------------------------------------------------------------------------------------------- pool_total_size 所有 zswap 池占的物理 RAM 字节(仅压缩数据) stored_pages 当前 zswap 存的页数(压缩 + 不可压缩) stored_incompressible_pages 按完整 PAGE_SIZE 存的页(压不动) pool_limit_hit 因到 max_pool_percent 拒绝存的次数 written_back_pages 成功写回后端 swap 设备的页 reject_reclaim_fail 拒绝存:池满且后台回写失败 reject_alloc_fail 拒绝存:zs_malloc() 报错(池耗尽) reject_kmemcache_fail 拒绝存:zswap_entry 元数据分配失败(少见) reject_compress_fail 拒绝存:压缩算法报错 reject_compress_poor 拒绝存:压缩输出分配器装不下(ENOSPC) decompress_fail 载入或回写时解压失败(数据损坏信号) -------------------------------------------------------------------------------------------------------------------------------------
zswap 健康速查:
for f in /sys/kernel/debug/zswap/*; do printf "%-40s %s\n" "$(basename $f)" "$(cat $f)"; done
11.2 vmstat 事件
/proc/vmstat 三个事件:
------------------------------------------------------------------------------------------------------------------------------------- 事件 说明 ------------------------------------------------------------------------------------------------------------------------------------- zswpout 存进 zswap 的页(zswap_store() 成功) zswpin 从 zswap 载入的页(zswap_load() 成功) zswpwb 从 zswap 写回 swap 设备的页 -------------------------------------------------------------------------------------------------------------------------------------
实时看 zswap 活动:
watch -n1 'grep -E "^zswp" /proc/vmstat'
11.3 按 Cgroup 统计
cgroup v2 的 memory.stat 含:
------------------------------------------------------------------------------------------------------------------------------------- 字段 说明 ------------------------------------------------------------------------------------------------------------------------------------- zswap 该 cgroup 在 zswap 用的压缩内存字节 zswapped 该 cgroup 当前在 zswap 存的页数 -------------------------------------------------------------------------------------------------------------------------------------
cat /sys/fs/cgroup/myapp/memory.stat | grep zswap
11.4 估算压缩比
stored=$(cat /sys/kernel/debug/zswap/stored_pages) pool_bytes=$(cat /sys/kernel/debug/zswap/pool_total_size) page_size=4096 if [ "$stored" -gt 0 ]; then uncompressed=$((stored * page_size)) echo "Stored pages: $stored" echo "Uncompressed: $((uncompressed / 1048576)) MiB" echo "Compressed: $((pool_bytes / 1048576)) MiB" echo "Ratio: $(echo "scale=2; $uncompressed / $pool_bytes" | bc):1" fi
12. 与 zram 的交互
zswap 可以坐在 zram 设备前面。完整栈:
匿名页需换出 → zswap_store → 压住存 zswap 池;池满/拒绝 → swap_writepage → zram 块设备 /dev/zram0 → 再压一次 → zram 池;zswap 回写也走这条路。
注: 网页上是流程图。
双重压缩:
zswap 在 zram 前面时,漏过 zswap 池(或被 shrinker 写回)的页会被 zram 第二次压缩。意味着:
这些页 CPU 开销翻倍。
额外压缩收益极小 —— zswap 压过的数据再压几乎压不动。
省内存有限,因为 zswap 已经压过了。
建议:二选一。用真 swap 设备 + 前面 zswap 减 I/O,或只要 zram 彻底无盘,二选一更简单高效。两者都有(既有 zram swap 又有磁盘 swap)非叠不可时,考虑对 zram swap type 禁用 zswap。
13. 性能权衡
13.1 CPU vs 内存 vs I/O
压缩比低(随机、已压缩数据):→ zswap 按 PAGE_SIZE 存不可压缩 → 池很快填满(相对未压缩 swap 无密度优势)→ 反正很快写回 swap 设备 → 净结果:白花 CPU。
压缩比高(文本、代码、零页):→ 4:1 或更好很常见 → 池装下 4 倍页 → swap I/O 大减 → 净结果:CPU 花得值。
13.2 压缩器对比
------------------------------------------------------------------------------------------------------------------------------------- 算法 速度 压缩比 适合 ------------------------------------------------------------------------------------------------------------------------------------- lzo 最快 中 交互桌面、延迟敏感 lz4 很快 中 高吞吐服务器、低开销环境 lz4hc 中 比 lz4 好 均衡;压缩慢、解压同 lz4 zstd 中 最好 高内存压力;值得花 CPU 换密度 deflate 慢 好 老旧;一般被 zstd 取代 842 硬件加速(IBM POWER) 中 有硬件 offload 的 POWER -------------------------------------------------------------------------------------------------------------------------------------
选压缩器:
延迟敏感:lzo 或 lz4 —— 解压快,缺页延迟低。
内存最要紧:zstd —— 密度最高。
默认 lzo 是大多数场景的安全中间值。
运行时换压缩器不用刷已有条目:
echo zstd > /sys/module/zswap/parameters/compressor
旧条目留在旧池,用老算法解压直到逐出。
13.3 何时开 Shrinker
默认情况下(CONFIG_ZSWAP_SHRINKER_DEFAULT_ON=n),只有当池达到 max_pool_percent 时才会触发回写。这意味着冷页会在池里无限积压,直到触顶。
启用 shrinker(shrinker_enabled=Y)后,内存分配器可以在内存压力下主动把冷的 zswap 页写回到后端 swap 设备 —— 在池满之前。这:
降低池变成冷内存“黑洞”的风险。
在持续内存压力下引入更多 swap I/O。
受压缩比和磁盘 swapin 惩罚约束,避免过度逐出。
echo Y > /sys/module/zswap/parameters/shrinker_enabled
13.4 池大小调优
默认 max_pool_percent=20 保守。RAM 充裕、swap 慢(机械盘)的系统调大有益:
# RAM 快、swap 慢的系统 echo 40 > /sys/module/zswap/parameters/max_pool_percent
RAM 更金贵(容器、嵌入式)保持默认或调小。
14. 初始化与生命周期
zswap 经 late_initcall(zswap_init) 懒初始化,保证 crypto 子系统可用:
zswap_init()
└── zswap_setup()
├── KMEM_CACHE(zswap_entry, 0)
├── cpuhp_setup_state_multi(CPUHP_MM_ZSWP_POOL_PREPARE,
│ zswap_cpu_comp_prepare, zswap_cpu_comp_dead)
├── alloc_workqueue("zswap-shrink", WQ_UNBOUND|WQ_MEM_RECLAIM, 1)
├── zswap_alloc_shrinker()
├── list_lru_init_memcg(&zswap_list_lru, zswap_shrinker)
├── shrinker_register(zswap_shrinker)
├── __zswap_pool_create_fallback() ← creates the initial pool
└── zswap_debugfs_init()
CPU 热插拔回调(zswap_cpu_comp_prepare / zswap_cpu_comp_dead)随 CPU 上下线分配释放每 CPU crypto_acomp_ctx。swapon 时 zswap_swapon() 给该 swap type 分配 xarray 分片。swapoff 时 zswap_swapoff() 释放(所有条目须先被 try_to_unuse() 失效)。
15. 关键源文件
mm/zswap.c: 主实现:存、载、回写、shrinker、池管理、debugfs
include/linux/zswap.h: 公开 API:zswap_store()、zswap_load()、zswap_invalidate()、zswap_swapon()、zswap_swapoff();带 nr_disk_swapins 的 struct zswap_lruvec_state
mm/zsmalloc.c: zsmalloc 分配器:zswap 用的变长压缩对象池
include/linux/zsmalloc.h: zsmalloc API:zs_create_pool()、zs_malloc()、zs_obj_write()、zs_obj_read_sg_begin()、zs_free()、zs_destroy_pool()
mm/memcontrol.c: 按 cgroup zswap 限制:memory.zswap.max、memory.zswap.current、memory.zswap.writeback、obj_cgroup_may_zswap()
mm/Kconfig: Kconfig:CONFIG_ZSWAP、CONFIG_ZSWAP_DEFAULT_ON、CONFIG_ZSWAP_SHRINKER_DEFAULT_ON、压缩器默认
include/linux/vm_event_item.h: vmstat 事件:ZSWPIN、ZSWPOUT、ZSWPWB
include/linux/memcontrol.h: MEMCG_ZSWAP_B、MEMCG_ZSWAPPED、obj_cgroup_may_zswap()、mem_cgroup_zswap_writeback_enabled();struct mem_cgroup 的 zswap_max 字段
Documentation/admin-guide/mm/zswap.rst: 上游管理员指南(可能滞后于源码)
16. 延伸阅读
mm/zswap.c —— 完整的 zswap 实现:存储、载入、回写、shrinker、池管理和 debugfs 计数器;
Documentation/admin-guide/mm/zswap.rst —— 上游管理员指南,涵盖可调参数、cgroup 控制和可观测性;
swap —— swap 架构总览,包括 zswap 在与 zram、磁盘 swap 组成的更大 swap 栈中的位置;
swap-thrashing —— 启用 zswap 或 zram 如何通过把压缩页留在 RAM 来减轻 I/O 驱动的抖动;
slab —— zswap 内部用来高密度存放变长压缩对象的 zsmalloc 分配器;
《zswap: compressed swap caching》(LWN,2013)—— v3.11 合入时对 Seth Jennings zswap 补丁的原始 LWN 报道;
《Cleancache and frontswap》(LWN,2011)—— zswap 最初使用的 frontswap 接口的背景(现已移除,改为直接集成);
17. 小节
使用 ZRAM 做 swap 设备的情况下就不需要 zswap 了,后端使用存储设备的时候才需要 zswap。
posted on 2026-10-09 10:43 Hello-World3 阅读(3) 评论(0) 收藏 举报
浙公网安备 33010602011771号