起因

我一直习惯自己调 zram:换个压缩算法、把容量开大点、把 swappinesswatermark_scale_factor 调到自己觉得舒服的数值。问题是,这些改动都是运行时状态,重启就没了。于是很长一段时间里,我的日常是这样的:开机、进终端、su,然后敲一遍这个:


su

swapoff /dev/block/zram0

echo 1 > /sys/block/zram0/reset

echo zstdn_o > /sys/block/zram0/comp_algorithm

echo 25769803776 > /sys/block/zram0/disksize

mkswap /dev/block/zram0

swapon -p 32767 /dev/block/zram0

echo 150 > /proc/sys/vm/swappiness

echo 100 > /proc/sys/vm/watermark_scale_factor

不难,但真的很烦。每次重启都要重复一遍,稍微手滑打错一个数字还得从头来。作为一个天天在用 KernelSU 的人,很自然会想:这种事应该交给一个模块自动干。

于是有了 ZRAMod

为什么不用 Scene、fas-rs 这类现成方案

社区里其实已经有不少功能全面的调优模块,Scene、fas-rs 都是这类。但我最后还是决定自己写一个,主要是两个原因:

  1. 不够轻。 这些方案大多是"全家桶"式的调优框架,CPU 调度、帧率控制、内存管理等等揉在一起。我只是想解决 zram 这一件事,用一个大而全的框架去做一件很小的事,感觉不太对,反而增加了理解和维护的负担——真出问题的时候,我得先搞清楚是不是别的功能干扰了 zram 这部分,排查成本高了一截。

  2. 有些做不到 nomount。 我给自己定的硬性要求是模块必须是 nomount 的——不改系统里的任何文件,只负责自动帮我敲命令。但综合性调优模块往往要挂载/替换系统文件才能实现那一整套更复杂的功能,这跟我想要的"最小、可控、随时能干干净净卸载"是冲突的。

所以干脆自己写一个:只做 zram 这一件事,做到 nomount,其余什么都不管。

ZRAMod 是什么

一句话:一个 KernelSU 模块,让你在 WebUI 里配置好压缩算法、容量、swap 优先级、swappinesswatermark_scale_factor,开机自动应用,也可以不重启直接热应用。

设计上我给自己定了几条不能妥协的原则。

必须是 nomount

模块的唯一职责是"帮我把开机该敲的命令自动敲一遍,再给我一个敲命令的界面",不应该碰系统里的任何一个文件。所以 ZRAMod 的模块目录里根本没有 system/ 目录,只有脚本和一个 WebUI,外加一个空的 skip_mount 文件明确声明这一点。KernelSU(以及 Magisk、APatch)原生支持这种"纯脚本模块",不需要什么特殊技巧。

没配置过,就什么都不做

装完模块、还没在 WebUI 里保存过一次配置之前,开机脚本是完全静默的,不会拿一套"我觉得合理"的默认值去动用户的 zram。这是我认为一个改内核参数的工具最基本的自我约束——工具的作用是把你已经决定好的东西自动化,而不是替你做决定。

算法列表是现场探测的,不是写死的

不同设备、不同内核支持的压缩算法差异很大(我自己设备上是 lzo lzo-rle lz4 zstd lz4k zstdn zstdn_o)。ZRAMod 每次打开 WebUI 都会读一遍 /sys/block/zram0/comp_algorithm,把设备真实支持的列表现场渲染出来,而不是在代码里猜一个通用列表。

不用重启也能生效

所有操作本质上就是几条 sysfs 写入加 swapoff/swapon,没理由非要重启才能试新参数。WebUI 里点一下"保存并立即应用"就直接生效,这个体验比我原来在终端里反复敲命令舒服太多。

开发过程中踩的几个坑

这部分我觉得比"功能介绍"更值得写下来,因为每一个坑都推翻了我一开始的想当然。

坑一:swapon -p 在模块脚本环境里直接报错。 我在终端手动敲命令时 swapon -p 32767 一直好好的,装进模块后同样的命令却报 invalid option -- p。查下来是模块脚本执行环境里 swapon 解析到的是 KernelSU 自带的 BusyBox 实现,而这个 BusyBox 的 swapon 压根没有 -p 这个参数——跟我在终端里手动执行时走的是完全不同的二进制。解法是优先尝试 /system/bin/swapon,失败了再退化成不带优先级重试,保证至少能把 swap 打开。

坑二:swappiness 开机后"莫名其妙"被改回默认值。 脚本明明执行成功、日志也没报错,但开机完成之后过个十来秒,swappiness 又变回了系统默认值。后来确认是设备/ROM 自己在开机流程更靠后的阶段(很可能是某个 on bootsys.boot_completed 触发的 init 脚本)用默认值把这两个 sysctl 又刷了一遍。既然拼的是"谁写得晚",那就干脆比它更晚——现在会在 BOOT_COMPLETED 之后再额外等一段可调的延迟(默认 20 秒)才补写一次,确保 ZRAMod 是最后写入的那个。

坑三,也是最意外的一个:KernelSU 对模块的 post-fs-data.sh 执行竟然没有任何超时保护。 一开始我想当然地以为"反正有平台兜底,脚本卡住了系统也不会跟着卡",直到有人较真地问了一句"卡住到底会不会拖住开机",我才去翻了 KernelSU 的实际源码(userspace/ksud/src/init_event.rs)。结果发现调用模块 post-fs-data.sh 的那一行代码上面就写着一行注释:// TODO: Add timeout。也就是说,如果 swapoff/reset 这类操作真的卡进了内核不可中断睡眠(比如系统内存本身已经很紧张的极端情况),整个开机流程会被结结实实地拖住,没有任何平台级的保险丝。这个发现让我把原本"顺手包一层 timeout 就够了"的想法推翻重来——单纯的 timeout 命令对内核态的不可中断睡眠是没用的(进程根本不响应信号),真正管用的是从源头降低触发概率:重建前先检查 /proc/swaps 里 zram 当前的已用量,用量太大(意味着 swapoff 要把很多数据解压回内存,本身就慢)就直接跳过这次重建,而不是硬着头皮上。这个检查默认阈值 1GiB,也可以在 WebUI 里关掉,但关掉之前页面会明确告诉你:真出问题的话,“重启"可能都救不了你,只能强制断电。

这三个坑串起来其实是同一个教训:自己设备上"能跑"和"设计上正确"是两回事,很多假设不去查真实行为或者源码根本不会知道是错的。

现在长什么样

  • 开机自动应用配置好的 zram 参数

  • WebUI 里实时看到当前算法、容量、swap 优先级、swappinesswatermark_scale_factor、物理内存

  • 保存即热应用,不用重启

  • 「停用并恢复安装前状态」一键复原,卸载模块时也会自动还原

  • 用量安全检查(可关闭,关闭前有明确风险提示)

  • 全部日志落地在 /data/adb/zramod/zramod.log,出问题能直接查

已知限制

目前只在一加 13(OnePlus 13)+ ColorOS 16 上验证过完整可用。核心逻辑都是标准的 zram sysfs 操作,理论上通用,但下面这几点在别的设备上可能表现不同,欢迎装了之后告诉我结果:

  • swapon 是否支持 -p 优先级参数因设备而异(已做兼容处理)

  • swappiness/watermark_scale_factor 被开机流程覆盖的时间点因 ROM 而异(延迟补写的秒数可调)

  • 支持的压缩算法因设备/内核而异(现场探测,不需要改代码)

怎么用

  1. KernelSU 管理器里安装模块,重启一次。

  2. 打开模块的 WebUI,选算法、填容量、调 swappiness/watermark_scale_factor

  3. 点「保存并立即应用」,立刻生效,同时下次开机也会自动应用。

  4. 不想要了就点「停用并恢复安装前状态」,或者直接卸载模块。

仓库地址:ZRAMod