内存管理-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 搭建。

9-1

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)    收藏  举报

导航