目录
摘要
Swap机制 是 linux 内核在内存紧张的时候,将不活跃的匿名页换出至磁盘或者压缩内存的机制,
kswapd 是负责后台异步回收的内核线程,依据内存水位触发,判断 swap 是否启用使用 swapon --show,使用活跃的则看 free 或者 vmstat 的 si/so 指标,
Android 平台通常使用 ZRAM 压缩内存作为 Swap 的主要实现形式,而非传统磁盘
核心定义和关键概念
Swap(交换空间) 是 Linux 系统用于暂存被换出内存页的存储区域,物理形态可以是专用磁盘分区、普通文件、或者 Android 上压缩内存块设备,其核心作用是物理内存不足的时候提供缓冲,避免直接触发 OOM 终止进程
换出(Swap out) 或者换入(Swap In) 是方向相反的两个操作:
换出(SO)指的是将内核的物理内存中的匿名页 写入 Swap 区域并且释放该内存页
换入(SI)指进程再次访问该页的时候,内核将其 Swap 区域读回物理内存并恢复页表映射,
频繁的换入和换出(thrasing) 是系统内存严重不足的信号
ZRAM 是 Android 以及部分 Linux 发行版采用的压缩内存机制,它在 RAM 中划出一块区域,将换出的页压缩后存放于该区域,而非写入磁盘,相比于 磁盘的 Swap,ZRAM 的读写延迟显著更低,且不损耗闪存寿命,但会消耗 CPU 进行压缩解压
原理以及机制详解
内存水位线和 kswapd 的触发逻辑
Linux 内核为每个内存区域(zone) 维护三个水位标志:page_min、page_low、page_high;当空闲页的数量降到 page_low 以下的时候,页面分配器唤醒 kswapd 线程;kswapd 开始扫描 LRU 链表,尝试回收页面,直到空闲页的数量回升到 page_high 以上才重新开始休眠。这种异步的机制的设计目的是为了让内存释放工作在后台完成,避免用户进程在申请内存的时候被迫等待回收,从而维持系统的吞吐量,如果 kswapd 的回收速度跟不上分配速度,申请内存的进程将进入回收路径(direct recliam),自己参与页面释放,造成明显的延迟抖动
页面回收的候选对象与 Swap 的介入时机
页面回收并非对所有的页面都一视同仁,内核主要扫描两类页面:文件页(page cache 和磁盘文件对象的缓存)和匿名页(anonymous page,进程堆栈,堆分配等无文件后端的内存)。
文件页的回收相对简单,如果为干净页,直接丢弃即可,下次分配时从文件重新读取,如果为脏页,则需要先写回到磁盘。匿名页则没有文件后端,如果直接丢弃,数据将永久丢失,因此必须为其寻找到一个备份位置 ------ 这正是 Swap 存在的意义,只有存在 Swap 空间可用的时候,内核才能将不活跃的匿名页面换出并释放物理页,如果 Swap 没有启用或者已满,匿名页将无法回收,系统智能依赖文件页回收或者触发 OOM
Swap 空间的分配和页表项转换
当内核决定换出一个匿名页的时候,它需要在 Swap 分区中分配一个空闲槽位,并将该页面内容写入,随后,该页对应的页表项(PTE) 被修改,Present 位清零,原本记录物理页帧号的高位被替换成 Swap 槽位的索引(就是 swap entry)。此后进程访问该虚拟地址的时候,CPU 触发缺页异常,内核识别出这个是一个 Swap Entry,便从 Swap 区域读回页面内容,重新建立物理映射,并释放 Swap 槽位,这一过程对用户完全透明,为了提升效率,内核还维护 Swap Cache,暂存刚换出或者换入的页面,减少对 Swap 设备的重复读写
swappiness 参数对回收参数的调节
vm.swappiness 是取值 0 到 100 的内核参数,控制内核在回收内存的时候对匿名页面(需要 Swap) 与文件页的相对倾向。值越高,内核越积极的换出匿名页面,值越低,则优先丢弃文件页缓存,默认值通常为 60,在物理页比较多的桌面环境和服务器环境中,适当降低该值可以减少不必要的 Swap 写入,避免因为后台换出造成的偶发性的 I/O 延迟,而在内存确实紧张的情况下,维持默认值或者提高该值有助于更充分的利用 Swap 缓冲
判断Swap 启用或者使用状态的方法
确定 Swap 是否已经启用
直接的命令是 swapon --show 或者读取 /proc/swaps,如果输出为空,说明系统当前没有使用任何的 swap 空间,输出为非空的时候,会列出设备名(或文件路径)、类型、总大小,已用量和优先级
free -h 命令的 swap 行给出总量和使用量概览,也可以看出 swap 空间大小
工作电脑上的配置显示:
shell
wangchen@asr-System:~$ swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 16G 0B -2
/dev/zram0 partition 1.6G 23.5M 100
/dev/zram1 partition 1.6G 23.2M 100
/dev/zram2 partition 1.6G 23.2M 100
/dev/zram3 partition 1.6G 23.2M 100
/dev/zram4 partition 1.6G 23.2M 100
/dev/zram5 partition 1.6G 23M 100
/dev/zram6 partition 1.6G 22.8M 100
/dev/zram7 partition 1.6G 23M 100
/dev/zram8 partition 1.6G 23M 100
/dev/zram9 partition 1.6G 23.1M 100
/dev/zram10 partition 1.6G 23.1M 100
/dev/zram11 partition 1.6G 22.9M 100
/dev/zram12 partition 1.6G 22.9M 100
/dev/zram13 partition 1.6G 23M 100
/dev/zram14 partition 1.6G 22.7M 100
/dev/zram15 partition 1.6G 22.9M 100
/dev/zram16 partition 1.6G 22.7M 100
/dev/zram17 partition 1.6G 22.7M 100
/dev/zram18 partition 1.6G 23M 100
/dev/zram19 partition 1.6G 23.1M 100
这里表示的是一个 16GiB 的磁盘交换文件 /swapfile,以及 20 个容量各约 1.6 GiB 的 ZRAM 压缩内存设备,其中,ZRAM 设备的优先级(PRIO 100) 远高于磁盘交换文件(PRIO -2),这意味着内核会优先使用 ZRAM 进行页面的换出,全部的 ZRAM 设备都启用且各有约 23MiB 的占用,说要压缩交换功能正在正常运作,但是当前负载很低
为什么存在 20个 ZRAM 设备:
Ubuntu 上部分 ZRAM 配置工具(zram-config等)默认策略是为每个 CPU 核心创建一个 ZRAM 设备,其设计初衷是在多喝系统上将压缩/解压的计算负载分散到不同的 CPU 核心上进行处理,避免单一设备成为瓶颈
优先级机制的含义:
内核交换设备的顺序由优先级决定:数值越高,越优先被使用,我的 ZRAM的 优先级为 100,磁盘交换文件为 -2,因此页面换出会首先写入 ZRAM,只有当所有的 ZRAM 设备被占满了之后,内核才会考虑使用 /swapfile
shell
wangchen@asr-System:~$ free -h
total used free shared buff/cache available
Mem: 62Gi 15Gi 1.0Gi 300Mi 45Gi 45Gi
Swap: 47Gi 460Mi 46Gi
判断 Swap 是被活跃使用
free -h 中的 used 非零仅说明有页面驻留在 Swap 中,未必代表当前正在频繁的换入换出,要判断活跃程度,应该使用 vmstat 1 观察 si 和 so 两列,若两者持续为 0 或者极低值,说明 Swap 中的页面处于冷存放的状态,对性能没有实质性的影响,如果 si/so 持续出现较大的数值,则表面系统内存压力较大,页面在内存和 Swap 之间迁移频繁,可能引发性能下降
使用 vmstat -s | grep swap 可以快速查看 Swap 的总量和空闲用量
shell
wangchen@asr-System:~$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
5 0 471040 1095336 2621372 45235524 0 0 9 1864 3 1 5 7 88 0 0
7 0 471040 1099452 2621372 45235528 0 0 0 0 10200 42619 6 15 79 0 0
3 0 471040 1093784 2621372 45235528 0 0 0 129 10186 41225 7 14 79 0 0
11 0 471040 1092792 2621372 45235528 0 0 0 0 10254 41993 7 16 77 0
Android 中Swap 的使用方式
ZRAM 作为默认方案
和传统的 Linux 发行版不同,Android 设备极少使用磁盘或者闪存上的 Swap 分区,主流 Android 系统采用的是 ZRAM,内核将其中一部分物理内存划做作压缩块设备(/dev/block/zram0),换出的页面经压缩后存放其中
Google 官方文档指出,ZRAM 的压缩比通常在 30% ~ 50% 的范围内,就是 1GB 的 ZRAM 空间可以诶容纳 2 ~ 3GB 的未压缩数据。由于压缩和解压均在 RAM 中完成,读写延迟远低于内存,且避免了频繁写入对闪存寿命的损耗,代价是压缩和解压操作会占用 CPU 时间,在低端设备上可以带来可以感知的调度开销
Android 中启用 ZRAM 的配置路径
在 Android 的启动流程中,ZRAM 通常通过 fstab 的条目或者 init.rc 脚本配置,传入的方式死在 fstab 中添加类似 /dev/block/zram0 none swap defaults zramsize=<bytes> 的行,并通过 swapoon_all 命令激活
较新的 Android 版本则推荐由 mmd(内存管理守护程序) 统一管理 ZRAM,通过系统属性比如 mmd.zram.size(默认是 RAM 的 50%) 和 mmd.zram.comp_algorithm 进行配置
编译 android 17 采用的 swap 方案
扩容 swap 文件:
shell
sudo swapoff /swapfile
sud rm /swapfile
# 3. 创建 16G 的新 swap 文件(bs*count = 16G)
sudo dd if=/dev/zero of=/swapfile bs=1M count=16384 status=progress
# 4. 设置严格权限(安全必须)
sudo chmod 600 /swapfile
# 5. 格式化为 swap 格式
sudo mkswap /swapfile
# 6. 重新启用
sudo swapon /swapfile
# 7. 重新验证大小
free -h