内存管理-45-zram-3-chatGPT
一、Android中内存Swap交换是如何实现的?
Android 里的 Swap 本质是 Linux 内核虚拟内存机制,但实现上和桌面 Linux 最大区别是:主力后端通常是 zram(压缩内存),不是磁盘分区。
1. 整体链路
(1) 进程申请匿名页(heap/stack/mmap anon)
(2) 内存压力上来后,内核回收器 kswapd / direct reclaim 扫描 LRU
(3) 选中的匿名脏页被换出到 swap 后端
(4) Android 上 swap 后端大多是 zram 设备(如 /dev/block/zram0)
(5) 页先压缩再存入 zram,占用更少物理内存
(6) 访问该页时触发缺页异常,再从 zram 解压换回 RAM(swap-in)
2. 为什么 Android 偏向 zram
(1) 手机/车机存储写放大敏感,不适合频繁磁盘 swap
(2) zram 用 RAM 做压缩池,延迟远低于磁盘 swap
(3) 能在内存紧张时“挤出”更多可用空间,减少 OOM/LMK 触发
3. 内核与用户态各自做什么
(1) 内核 VM 子系统
负责页回收、换入换出、LRU 管理、swap map 记账
关键路径:mm/vmscan.c、mm/swapfile.c、mm/page_io.c
(2) zram 驱动
负责压缩/解压与存储管理
关键路径:drivers/block/zram/
(3) Android 用户态(lmkd 等)
不是直接做 swap I/O,而是根据内存压力(PSI)杀进程、调策略
作用是避免系统陷入过度换页抖动(thrashing)
4. 你在工具里看到的字段对应关系
(1) Swap(procrank/showmap)
逻辑换出量(页被标记在 swap 上)
(2) PSwap
按共享关系分摊后的换出量
(3) ZSwap
在 zram 中实际压缩后占用(物理成本)
ZRAM: physical used for ... in swap
全局压缩比视角:压缩后物理占用 vs 压缩前逻辑换出量
5. 触发条件和副作用
(1) 触发: 可用内存下降、匿名页回收压力升高、PSI memory 上升.
(2) 好处: 延后 OOM,提升系统在高压下存活能力
(3) 代价: swap-in 会产生解压和缺页延迟,过高 swap 会导致卡顿、尾延迟抖动、CPU 额外开销.
6. 常见配套机制
(1) vm.swappiness: 决定回收匿名页 vs 文件页倾向
(2) lmkd + PSI: Android 用它决定何时杀后台,避免系统长期 heavy swapping.
(3) MGLRU(新内核): 提升回收选择准确性,减少无效换页.
7. 一句话总结
Android 的 Swap 机制是 Linux 标准换页机制 + zram 压缩后端 + lmkd 压力治理:内核负责“换”,zram负责“压”,lmkd负责“止损”。
二、Android中内存Zram压缩的实现逻辑,包括上层和内核层?
可以,按“上层控制面 + 内核数据面”给你一张完整图。
1. 先看整体链路
(1) 系统启动时创建并配置 zram 设备.
(2) 把 zram 作为 swap 后端启用.
(3) 内存压力升高时,内核把匿名页换出到 zram
(4) zram 对页面压缩后存入内存池
(5) 进程再次访问该页时触发缺页,从 zram 解压换回
本质是:用 CPU 压缩开销,换取更大的“等效内存容量”。
2. Android 上层逻辑(用户态)
(1) 启动配置
由系统启动流程下发 zram 大小、压缩算法、swappiness 等参数,然后启用 swap。很多平台会按机型内存档位动态设定大小。
(2) 内存压力治理
Android 主要由 lmkd 结合 PSI 监控内存压力。
它不负责压缩本身,而是决定何时杀后台进程,避免系统陷入持续抖动。
(3) 策略协同
常见是三件事配合:
a. zram 扩展可用容量
b. 内核回收做换页
c. lmkd 在压力过高时做进程回收止损
(4) 观测与归因
上层工具会展示 Swap、PSwap、ZSwap、zram 物理占用等指标,用于判断是“还能靠压缩撑住”,还是“该回收进程了”。
3. 内核层实现逻辑(关键机制)
(1) 换出触发
内存回收线程扫描匿名页,把可回收匿名页送到 swap 路径。文件页优先被丢弃,匿名页不足时才更依赖 swap。
(2) zram 存储模型
zram 是一个内存块设备。每个逻辑页写入时会被压缩,压缩数据存放在内核内存分配的对象里,并建立索引映射。
(3) 压缩与解压
swap out:页内容压缩后写入 zram 槽位
swap in:根据索引取压缩块,解压回目标页框
(4) 压缩算法
常见有 lz4、lzo、zstd。
lz4:速度快、压缩率中等.
zstd:压缩率更高、CPU 成本更高.
车机场景常在实时性和压缩率之间做折中。
(5) 记账口径
逻辑换出量:压缩前页面总量.
物理占用量:压缩后实际占用。压缩比越高,单位物理内存能承载的换出页越多。
(6) 失败与回退
如果压缩后仍无法分配内存,或系统压力持续,内核会继续回收并触发更激进路径;用户态 lmkd 通常会介入杀进程,防止系统卡死。
(7) 总结来说:
上层负责策略:配多大、用什么算法、什么时候开始止损(杀后台)。
内核负责执行: 哪一页被换、如何压缩存、如何解压回、如何维护 LRU 与 swap 映射。
三、Android中磁盘Swap交换和Zram压缩可以共存吗?
可以共存,而且在 Linux/Android 内核层面本来就支持多 swap 设备同时在线。
1. 核心结论:
(1) 可以同时启用磁盘 swap 和 zram swap。
(2) 内核会把它们都当作 swap 后端,按优先级和可用性分配换出页。
(3) 共存后通常是 zram 先吃压力,磁盘 swap 兜底,但也可能带来更高尾延迟和 I/O 风险。
2. 共存时的工作逻辑
(1) 多后端注册
系统可以同时有多个 swap 区,比如 zram0 和某个磁盘分区/文件。
(2) 优先级生效
每个 swap 设备有 priority。通常会把 zram 设更高优先级,让匿名页先换到 zram。
(3) 换出路径
内存回收触发后,内核优先往高优先级 swap 写。zram 满了或不够用时,再落到低优先级磁盘 swap。
(4) 换入路径
缺页时从对应后端读回。来自 zram 的页需要解压;来自磁盘的页需要块 I/O 读取。
3. 为什么有人会同时开
(1) zram 响应快
纯内存后端,延迟通常比磁盘 swap 低。
(2) 磁盘容量大
可提供更大的兜底空间,避免极端压力下快速 OOM。
(3) 分层止损
中等压力由 zram 吸收,极端压力交给磁盘 swap,延后进程被杀时间点。
4. 可能影响
(1) 正面:
系统在高压下更不容易立刻 OOM。
后台进程保活时间可能变长。
峰值内存场景容错更高。
(2) 负面:
尾延迟上升: 一旦触发磁盘 swap-in,交互卡顿明显增加,车机场景尤其敏感。
I/O 放大与寿命风险: 频繁磁盘 swap 会增加闪存写放大,长期可能影响寿命和稳定性。
CPU 与能耗开销: zram 压缩解压吃 CPU;磁盘 swap 吃 I/O 和调度成本。
抖动风险: 如果策略不当,可能出现回收-换入-再回收的 thrashing,表现为顿挫和帧率抖动。
5. 车机场景建议
(1) 默认策略
优先 zram,谨慎启用磁盘 swap。若启用磁盘 swap,优先级应低于 zram,且限制其介入程度。
(2) 关键目标
宁可更早回收后台,也不要让前台关键链路频繁磁盘换入。
(3) 重点监控
关注 PSI memory、major fault、前台关键进程的 swap-in 频次、以及语音/导航/主界面 P95-P99 时延。
6. 一句话总结
能共存,逻辑上是分层 swap 后端协同;收益是抗压更强,代价是高压下时延和 I/O 风险上升。对车机来说,要把 zram当主力,把磁盘 swap 当最后兜底,并严格看前台体验指标。
四、Android中Zram的压缩和解压是由谁,在什么条件下触发的?
在 Android 里,Zram 的压缩/解压都发生在内核态,不是用户态进程直接做的。
1. 谁在做压缩
(1) 触发换出时,Linux 内存回收路径会把匿名页写到 swap 设备。
(2) 如果 swap 设备是 zram(如 /dev/block/zram0),就进入 zram 驱动。
(3) zram 驱动调用配置好的压缩算法(常见 lz4/lzo/zstd)把页面压缩后存到内存池(通常是 zsmalloc)里。
简单说:kswapd/direct reclaim -> swap_out -> zram driver -> compressor -> zsmalloc
2. 谁在做解压
当进程再次访问已换出的页,触发缺页异常(page fault)。
内核走 swap-in 路径,从 zram 读出压缩块并解压,还原成普通内存页,再映射回进程地址空间。
简单说:page fault -> swap_in -> zram read -> decompressor -> page mapped
3. 在什么条件下触发“压缩”(swap out)
常见条件是“内存压力”:
(1) 可回收内存不足,达到水位线,kswapd 开始回收。
(2) 或分配路径上直接回收(direct reclaim)。
(3) 系统有可用 swap(zram 已启用且未满)。
(4) swappiness 等 VM 参数允许/倾向把匿名页换出。
(5) 页面通常是低活跃匿名页(inactive anon),文件页优先按策略回收。
4. 在什么条件下触发“解压”(swap in)
(1) 只要访问到了已在 zram swap 里的页,就会触发解压。
(2) 常见于应用切回前台、后台进程被重新调度、冷页重新被访问。
5. Android 侧补充
Android 的 lmkd 是“杀进程”策略,不负责压缩实现本身。
实际常见是:先回收/换出一部分;压力继续升高再触发更激进的 lmkd 杀进程。
所以你看到的 dumpsys meminfo 里 ZRAM: physical used for ... in swap 就是上述内核路径的结果。
五、zram压缩了多少内存,和zram中被压缩后的数据占用了多少内存是如何统计出来的?
这两个量本质上来自两组不同口径的内核计数器,然后在 Android 里汇总显示:
1. zram 压缩了多少内存(压缩前逻辑数据量)
口径是“有多少页被换出到 zram 作为 swap 数据”。在 dumpsys meminfo 里通常对应 in swap。
计算方式通常是:in swap = SwapTotal - SwapFree
这是“压缩前”逻辑容量,不是实际占用物理内存。
2. zram 中压缩后实际占了多少内存(物理占用)
口径是 zram 设备当前真实占用的 RAM。在 dumpsys meminfo 里通常对应 physical used。这个值来自 zram 驱动维护的物理占用计数(等价于 zram 统计里的 mem_used_total 维度)。
3. 两者关系
压缩前逻辑量通常大于压缩后物理占用。
可用压缩比近似看效果:压缩比 = in swap / physical used
例如 2400MB in swap、600MB physical used,大约就是 4:1。
4. 为什么有时对不上直觉
物理占用不只包含纯压缩数据,还会有元数据和分配器开销。
所以你可能看到:
纯压缩数据量 < physical used < 压缩前逻辑量, 这属于正常现象。
5. 一句话总结:
in swap 看“被换出了多少逻辑内存”,physical used 看“zram 为这些数据实际花了多少 RAM”。
六、如何触发和停止zram压缩?
在 Android 里,zram 压缩不是一个可直接“按按钮开始”的服务,它是内核在发生 swap out 时自动做的。所以“触发”和“停止”本质是控制是否发生 swap。
1. 如何触发 zram 压缩
前提:zram 设备已启用并且被 swapon;系统有内存压力,内核开始回收匿名页
2. 常见触发方式(需要 root):
(1) 提高内存压力(启动大内存应用、压力测试)
(2) 提高 swappiness,让内核更愿意换出匿名页, 命令示例:sysctl -w vm.swappiness=180
(3) 主动回收一轮(新内核支持时)
命令示例:echo 1 > /proc/sys/vm/compact_memory
或:echo "all" > /proc/sys/vm/drop_caches(主要回收页缓存,不直接等于 swap)
3. 观察是否触发:
cat /proc/meminfo 里的 SwapFree 下降
cat /sys/block/zram0/mm_stat 里 compressed data 和 mem_used_total 上升
dumpsys meminfo 里 ZRAM: physical used / in swap 增长 //??
4. 如何停止 zram 压缩
(1) 停止“继续产生新的压缩页”
把 swappiness 调低到 0(或很低), 命令示例:sysctl -w vm.swappiness=0
这会显著减少后续 swap out,但不会立刻清空已有 zram 数据.
(2) 彻底停止并清空 zram 里的压缩数据
先 swapoff 对应 zram 设备, 命令示例:swapoff /dev/block/zram0
如需重置设备(可选): 命令示例:echo 1 > /sys/block/zram0/reset
之后若要恢复再 swapon: 命令示例:swapon /dev/block/zram0
5. Android 设备上的现实约束
user 版设备通常无权限执行上述操作;
很多机型会被 init 脚本或性能服务重新写回参数(你改了会被覆盖)
真正长期关闭需要改系统配置(fstab/init/vm 参数),通常是 userdebug/eng 或厂商定制流程
6. 一句话总结:
触发 zram 压缩 = 让系统发生 swap out
停止 zram 压缩 = 降低/禁用 swap(swappiness 仅抑制,swapoff 才是彻底停止并清空)
七、zram是如何注册成swap后端的?
zram 注册成 swap 后端,本质上分两段:先注册为块设备,再被 swap 子系统接管。####
1. 作为块设备出现
zram 驱动加载后会创建 zram 设备(例如 /dev/zram0)。内核里它先完成块设备注册,然后挂上自己的 I/O 处理逻辑(读写请求进入 zram 驱动,而不是物理磁盘)。
2. 初始化成“可做 swap 的块设备”
用户态先设置容量:echo <bytes> > /sys/block/zram0/disksize, 再写 swap 签名:mkswap /dev/zram0, 这一步会在设备头部写入 swap header(含 SWAPSPACE2 等信息)。
3. swapon 把它接入 swap 子系统
执行:swapon /dev/zram0, 内核 swapon 路径会校验 swap header,分配并初始化对应的 swap 管理结构(swap area 元数据),把该设备加入可用 swap 列表。到这一步,zram 就正式成为 swap backend。
4. 后续换页如何落到 zram
内存回收触发换页时,swap 子系统把换出页写到这个 swap area。I/O 请求走块层进入 zram 驱动。zram 在写入路径做压缩并存到 zsmalloc;读取路径再解压返回。
5. 可以直接用这组命令复现完整注册流程:
modprobe zram echo lz4 > /sys/block/zram0/comp_algorithm echo 2G > /sys/block/zram0/disksize mkswap /dev/zram0 swapon /dev/zram0 cat /proc/swaps
cat /proc/swaps 里出现 /dev/zram0,就说明它已经成功注册为 swap 后端。
八、Android中 mkswap 命令的代码实现
在 Android 里,mkswap 通常来自 toybox(AOSP: android/external/toybox####)。你问的“实现逻辑 + 函数调用路径”,可以按这两层看:
1. 命令分发层(toybox 多命令入口)
main --> toybox_main --> toy_exec_which --> toy_init --> toys.which->toy_main()
对 mkswap 来说,最终跳到 mkswap_main()
关键点:
命令注册由 NEWTOY 宏生成,mkswap 在 toys/other/mkswap.c ####里声明,运行时通过命令名查表,找到 mkswap_main 函数指针并调用 mkswap 业务层(mkswap_main),对应 AOSP 文件:external/toybox/toys/other/mkswap.c
2. 核心实现逻辑:
(1) 打开目标设备/文件
fd = xopen(path, O_RDWR)
(2) 获取页大小与目标长度
pagesize = sysconf(_SC_PAGE_SIZE)
len = fdlength(fd)
pages = (len / pagesize) - 1
(3) 在缓冲区构造 swap header
使用 toybuf 作为 header 缓冲
swap[0] = 1(swap version)
swap[1] = pages(最后可用页等信息)
预留/填充 UUID、label 字段
(4) 写 header 到固定偏移
xlseek(fd, 1024, SEEK_SET)
create_uuid(uuid)
可选写 label(-L)
xwrite(fd, swap, 129 * sizeof(unsigned int))
(5) 写签名 SWAPSPACE2(内核 swapon 校验关键)
xlseek(fd, pagesize - 10, SEEK_SET)
xwrite(fd, "SWAPSPACE2", 10)
(6) fsync 持久化并输出结果
fsync(fd)
show_uuid(uuid) 打印 UUID
输出 swapspace 大小等信息
(7) 关键辅助函数调用路径(你调源码时最有用)
mkswap_main -> xopen //封装 open,失败即退出 -> fdlength(优先 fstat/BLKGETSIZE,必要时探测设备长度) -> sysconf(_SC_PAGE_SIZE) -> xlseek -> create_uuid -> xwrite -> fsync -> show_uuid -> printf
这些 helper 主要在:external/toybox/lib/lib.c (xopen/fdlength/xwrite/create_uuid/show_uuid 等)
4. 与内核 swapon 的关系
mkswap 只负责“格式化并写 swap 头 + 签名”。真正激活 swap 发生在 swapon 命令调用 swapon 系统调用之后。内核在 swapon 路径读取并校验 SWAPSPACE2,然后注册 swap area。
注: 实际代码位置如下,other 目录下还有很多其它工具。
/android/external/toybox/toys/other/mkswap.c
对应内核系统调用:
./mm/swapfile.c: SYSCALL_DEFINE1(swapoff, const char __user *, specialfile) ./mm/swapfile.c: SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
九、Android中 swapon 命令的代码实现
Android 中的 swapon 命令实现,当前主线是 Toybox 版本。下面给你精确到函数级别的逻辑和调用路径。
1. 用户态实现逻辑(Toybox)
(1) 命令分发进入 swapon_main()。
解析参数:
-d:开启 discard 相关标志位。在 Toybox 中会转成 discard 相关位,对应 SWAP_FLAG_DISCARD / ONCE / PAGES)。
-p:设置 swap 优先级。会转成 SWAP_FLAG_PREFER + 优先级位移。
组装 flags 后直接调用 libc 的 swapon(path, flags)。
2. 用户态调用路径
main ->toybox_main ->toy_exec_which ->toy_init ->swapon_main ->libc swapon ->syscall(SYS_swapon, path, flags)
3. 内核态实现主路径
系统调用入口在 mm/swapfile.c 的 SYSCALL_DEFINE2(swapon, ...),主流程如下:
(1) 参数与权限检查
(2) 校验 flags 合法性。
(3) 检查 CAP_SYS_ADMIN。
(4) 分配和初始化 swap_info_struct
(5) alloc_swap_info
(6) 初始化 discard_work / reclaim_work
(7) 打开目标 swap 文件或块设备
(8) file_open_name
(9) claim_swapfile(识别块设备/普通文件,检查是否可用)
(10) 读取并校验 swap 头
(11) read_mapping_folio 读首页
(12) read_swap_header 校验签名 SWAPSPACE2、version、last_page 等
(13) 构建交换区元数据
(14) setup_swap_extents(建立 extent 映射)
(15) setup_swap_map(标记 header/bad pages)
(16) setup_clusters(cluster/free/nonfull/frag 链表)
(17) swap_cgroup_swapon(memcg 相关)
(18) 处理可选能力
(19) discard 相关:discard_swap
(20) zswap 相关:zswap_swapon
(21) 激活交换区
(22) enable_swap_info(挂入 swap_active_head 和 avail list)
(23) 记录优先级、更新总 swap 页计数
(24) /proc/swaps 可见并可被分配使用
4. 内核侧关键函数调用路径(精简版)
SYSCALL_DEFINE2(swapon)
alloc_swap_info
file_open_name
claim_swapfile
read_mapping_folio
read_swap_header
setup_swap_extents
setup_swap_map
setup_clusters
zswap_swapon
enable_swap_info
5. 一句话总结
Android 的 swapon 命令本体只是参数转发器;真正的实现核心在内核 swapfile.c:校验 swap 头、建立 swap 元数据、接入全局 swap 分配链表并激活。
posted on 2026-03-30 14:13 Hello-World3 阅读(5) 评论(0) 收藏 举报
浙公网安备 33010602011771号