ZRAM 与 ZSWAP 在内存受限场景下的行为差异

一台 8GB 物理内存的机器,同时运行 IDEA、Docker、Chromium——内存压力是常态。常见的应对是把 ZRAM 块设备设为最高优先级的 swap。这套配置确实比纯磁盘 swap 快不少,但运行几天后机器照样会出现严重的延迟抖动:鼠标卡顿、窗口切换延迟几秒才响应。

问题不在 ZRAM 本身,在于 回收策略跟存储后端错配了

ZRAM vs ZSWAP:架构形态的根本区别

两者都做内存压缩,但定位完全不同。

ZRAM 是压缩块设备。 它在内核中注册为一个 /dev/zramX 块设备,存储后端是内存中的压缩页。数据通过 swap_writepage() 流入,走的是标准块 I/O 路径。ZRAM 不知道什么是冷页热页——它就是个介质,不参与回收决策。池子满了就按 swap_info 优先级往下一个 swap 设备 fallback,毫无策略可言。

ZSWAP 是压缩缓存层。 它挂在交换子系统内部,在 shrink_page_list() 的回收路径早期就直接拦截页。ZSWAP 维护压缩页的元数据,跟内核 LRU 协同工作。它有个软限制 max_pool_percent,池子满了不是无脑溢出,而是把最冷的页 Writeback 到后台磁盘 swap,把空间腾给活跃数据。

这层差异决定了它们在长期高负载下走向完全不同的结果。

LRU 反转:高速介质装垃圾,热数据困在低速盘

Linux 页回收的预期行为很简单:先驱逐 Inactive 冷页,保住 Active 热页。

但看这个配置:

  • /dev/zram0:优先级 32767(最高)
  • 磁盘 swap:优先级 -1

系统启动阶段产生大量一次性初始化数据,这些冷页在回收时率先进入优先级最高的 ZRAM。等 ZRAM 填满了,核心工作集(IDEA、Docker 的热页)需要换出时,ZRAM 装不下,被强制重定向到磁盘 swap。

冷数据占据高速的 ZRAM,热数据困在低速磁盘上——LRU 的驱逐意图被 swap 优先级配置彻底推翻。这就是 LRU 反转,内核 MM 维护者(如 Chris Down)明确反对这种混合配置。

Writeback 策略:被动拒绝 vs 主动老化

特性 ZRAM ZSWAP
触发机制 手动配置或新版内核支持 zswap_shrink() 自动触发
策略依据 基于存储池满(无策略) 基于 LRU 老化
与 MM 集成度 独立于 MM 子系统 深度嵌入 Reclaim 路径

ZSWAP 能动态把最久未访问的页推到磁盘,释放池空间给当前活跃页;ZRAM 填满之后就只能拒绝服务。

I/O 路径开销

ZRAM 的写入路径上有完整的块层开销:

内存页 → BIO 分配 → 请求队列 → I/O 调度 → ZRAM 驱动 → 压缩

ZSWAP 则绕过了这些:

内存页 → ZSWAP 钩子 → 压缩

不过在桌面场景下,ZSWAP 的实际收益大头不在于路径缩短,而在于 减少了低速磁盘 I/O 的发生频率——LRU 感知的驱逐让大部分换出停留在压缩缓存层内,只有真正的冷页才落到磁盘上。

约束与边界

两者共同受限的场景:

  • HugeTLB 页:不支持。
  • Tmpfs 页:不走 swap 路径,不受管理。
  • Mlocked 页:不可换出。

关键区别:ZSWAP 必须依赖磁盘 swap 作为后备存储,它就是个缓存层;ZRAM 可以独立存在,不要求有盘。

怎么选

有磁盘 swap 的桌面 / 服务器环境,直接用 ZSWAP。在 root 下执行:

swapoff /dev/zram0

echo 1 > /sys/module/zswap/parameters/enabled
echo zstd > /sys/module/zswap/parameters/compressor
echo 50 > /sys/module/zswap/parameters/max_pool_percent

几点经验:

  • ZRAM 正确的使用场景是嵌入式、Android 这类无盘环境,它的设计目标就是"在没有磁盘的情况下提供 swap";
  • 在有盘的机器上,ZRAM 跟磁盘 swap 混用会制造上述 LRU 反转问题,应该关掉 ZRAM;
  • ZSWAP 配磁盘 swap 是目前桌面 Linux 上最稳定的内存压力方案,把压缩当缓存,让真正的冷数据才落盘。

ZRAM 是个好存储后端,但它没有回收智能;ZSWAP 把压缩和驱逐策略绑在了一起。在有盘环境下,让它们各司其职——ZSWAP 做热数据压缩缓存,磁盘 swap 兜底冷数据——才是对的方向。