本页面介绍了 Android 上 kswapd 和 lmkd 之间的交互,展示了它们如何协同管理内存压力。
背景
当系统内存不足时,Android 会使用两种主要机制来回收内存:
kswapd:一个标准的 Linux 内核守护程序,通过逐出干净的文件支持页面或将匿名页面交换到 ZRAM 来回收页面。lmkd:Android 的用户空间低内存终止守护程序。当kswapd无法满足内存需求且系统压力增加(由 PSI 发出信号)时,lmkd会介入终止进程,以释放更大的内存块。
kswapd 和 lmkd 是两个互补的内存回收系统。它们致力于实现相同的目标(创建可用内存),但采用不同的方式。比较和对比:
- 可恢复与破坏性:
kswapd会丢弃可从存储空间中读回的内存页,或将内存页交换到其他内存分区或可按需读回的存储空间。lmkd会终止进程,从而销毁非易失性状态。 - 回收带宽:与
lmkd相比,kswapd需要消耗更多 CPU 才能回收给定数量的可用内存,并且回收这些内存所需的时间也更长。lmkd可以在短时间内以相对较少的努力回收大量内存。请注意,在移动设备上,CPU 耗费不仅会转化为延迟时间,还会转化为电池电量。
OOM 得分区间
Android 会根据进程对用户的重要性,为进程分配内存不足 (OOM) 分数调整值 (oom_score_adj)。得分较高的进程会先被终止。
常见存储桶包括:
- 缓存(得分 >= 900):当前不需要的进程,保留这些进程是为了加快重启速度。
- 服务(得分 500):托管已启动服务的进程。
- 前台服务(得分 200):托管前台服务的进程。
- 前台(得分 0):用户当前正在与之互动的应用。
深入探究:PSI 和 LMKD 触发
什么是 PSI?
压力失速信息 (PSI) 是一项 Linux 内核功能,提供了一种衡量资源短缺的标准方法。它会跟踪任务因 CPU、内存和 I/O 短缺而延迟的时间。
内存 PSI:“部分”与“完全”
内存 PSI 专门跟踪由内存不足引起的延迟(例如,等待页面回收或重新故障)。它提供两个指标:
some:至少有一个任务因内存而停滞的时间百分比。这表明内存不足正在影响性能,但某些工作仍在进行中。full:所有非空闲任务同时停滞的时间百分比。这表示系统处于完全阻塞状态,正在等待内存。
PSI 如何触发 LMKD
用户空间 lmkd 守护程序会向内核注册,以便在超出 PSI 阈值时接收通知(通过监控 /proc/pressure/memory)。当系统遇到高内存压力(例如,full 停滞时间超过配置的时长)时,内核会唤醒 lmkd。然后,lmkd 会评估系统状态,并根据进程的 OOM 得分和大小决定是否终止进程以及终止哪些进程。
LMKD 调优
您可以使用系统属性来调整 LMKD 行为。
在设备上查找调优值
您可以通过运行以下命令,在已连接的设备上找到当前的 LMKD 调优值:
adb shell getprop | grep lmk
常见 LMKD 属性
以下是用于调整 LMKD 的一些关键属性(如需查看完整列表,请参阅 Android 文档):
ro.lmk.use_psi:设置为true,以使用 PSI 监控器进行内存压力检测(在 Android 10 及更高版本中默认启用)。ro.lmk.psi_partial_stall_ms:some(部分)卡顿持续时间(以毫秒为单位)的阈值,用于触发终止(在普通设备上默认为 70 毫秒,在低内存设备上默认为 200 毫秒)。ro.lmk.psi_complete_stall_ms:触发终止操作的full停滞时长阈值(以毫秒为单位,默认值为 700 毫秒)。ro.lmk.kill_heaviest_task:如果为true,LMKD 会终止同一 OOM 存储分区中候选进程中占用内存最多的进程。
测试设备 (Google Pixel 8 Pro) 上的相关调优值
(注意:搭载 Android 15 的高端设备的典型默认值)
ro.lmk.use_psi:truero.lmk.psi_partial_stall_ms:70(典型值)ro.lmk.psi_complete_stall_ms:700(典型值)ro.lmk.kill_heaviest_task:true
ZRAM 和交换区验证
ZRAM 容量会严重影响交换空间填满和触发 LMKD 终止的速度。 您可以使用以下命令查看设备上的 ZRAM 和交换状态:
# Check ZRAM sizing and usage
adb shell free -m
# Check swap partition details
adb shell cat /proc/swaps
实践练习:触发 kswapd 和 lmkd
在本练习中,您将使用 KswapdLmkdLab 示例应用在不同的 OOM 存储桶中创建进程,然后在主进程中消耗内存以触发 kswapd 和 lmkd。
该应用配置为自动生成子进程并分配内存,使其成为 LMKD 的目标。
1. 创建子进程
应用在启动时会自动生成以下子进程:
- 缓存的进程 (oom_score_adj >= 900)
- 服务进程 (oom_score_adj 500)
- 前台服务进程 (oom_score_adj 200)
使用 ADB 验证进程及其 OOM 分数:
adb shell dumpsys activity processes | grep com.android.kswapdlmkdlab
示例输出代码段:
Proc # 0: fg T/A/TOP LCMNFUATI t: 0 18959:com.android.kswapdlmkdlab/u0a358 (top-activity)
Proc # 1: prcp b/ /FGS -------TI t: 0 19050:com.android.kswapdlmkdlab:foreground/u0a358 (fg-service)
Proc # 2: svc b/ /SVC -------TI t: 0 19077:com.android.kswapdlmkdlab:service/u0a358 (started-services)
Proc # 3: cch b/ /CACC -------TI t: 0 19014:com.android.kswapdlmkdlab:cached/u0a358 (cch-client-act)
进程状态缩写对应于:
fg:前台(OOM 得分为 0)prcp:可感知(前台服务,OOM 得分 200)svc:已启动的服务(OOM 得分 500)cch:缓存的进程(OOM 得分为 900)
2. 触发内存压力
该应用将自动开始在主进程中分配内存。为了使测试在高 RAM 设备上有效,该应用还会命令子进程分配内存(例如,每个子进程分配 500MB),使其成为 LMKD 的更佳目标。
监控子进程终止
该应用使用 IBinder.linkToDeath 机制监控这些子进程的终止:
- 主进程使用
bindService绑定到目标进程中的服务。 - 连接后,它会获取表示服务的
IBinder对象。 - 它通过在 binder 上调用
linkToDeath来注册IBinder.DeathRecipient实现。 - 当目标进程终止时(例如,被 LMKD 终止),系统会调用
binderDied()方法,以便应用异步记录终止事件。
为防止此绑定提升目标进程的优先级(这会违背练习的目的),应用使用了 Context.BIND_WAIVE_PRIORITY 标志。
示例应用实际运行
以下是应用在 Google Pixel 8 Pro(总 RAM 约为 12 GB)上完成测试运行后的屏幕截图。
Perfetto 跟踪记录分析
如需捕获互动,请使用以下 Perfetto 配置来捕获调度、内存计数器、LMK 终止和压力失速信息 (PSI)。
Perfetto 配置
通过运行 Perfetto 并启用调度、内存计数器、LMK 终止和压力失速信息 (PSI) 来捕获交互:
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/kswapd_lmkd.perfetto-trace <<EOF
buffers: { size_kb: 131072 }
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "lowmemorykiller/lowmemory_kill"
ftrace_events: "vmscan/mm_vmscan_kswapd_wake"
ftrace_events: "vmscan/mm_vmscan_kswapd_sleep"
ftrace_events: "psi/psi_event"
}
}
}
duration_ms: 180000
EOF
查看跟踪记录
以下屏幕截图取自一个跟踪记录,显示了从 :cached(最可终止)进程被终止前不久到 :foreground(最不可终止)进程被终止的时间段。
您可以看到,MemFree 已经相当低,并且持续面临压力。
kswapd 会持续运行,并通过从所有 kswapdlmkdlab 进程(主进程和子进程)以及可能未在此屏幕截图中显示的其他进程中丢弃 mem.rss.file 页面并将 mem.rss.anon 移至 mem.swap 来缓解压力。与此同时,kswapdlmkdlab 应用会继续分配内存,导致内存压力增加的速度快于 kswapd 回收内存的速度。
当压力过高时(如 psi.mem 在足够长的时间内保持高位),lmkd 会唤醒并终止一些进程。您可以看到,lmkd 会遵循 OOM 分数,先终止 kswapdlmkdlab:cached,然后终止 :service,最后终止 :foreground。
查看事件时间轴,并思考以下问题:
kswapd和lmkd如何相辅相成:kswapd会首先响应内存压力,但当kswapd的回收带宽无法满足分配需求时,lmkd会提供帮助。- 内存释放速率:观察在
kswapd中花费的 CPU 时间,以及通过丢弃mem.rss.file或将mem.rss.anon交换到mem.swap来回收的内存。将此与lmkd中导致大量进程私有内存被释放的短时间活动进行比较。
查询
在 Perfetto 界面(查询 SQL)中运行这些查询,以从跟踪记录中提取数据。
1. kswapd 活动
如需查找内核的交换守护程序何时处于活跃状态并正在运行,请执行以下命令:
SELECT ts, dur, state
FROM thread_state
JOIN thread USING (utid)
WHERE thread.name LIKE 'kswapd%'
AND state = 'Running'
LIMIT 5;
示例输出:
| ts | dur | state |
|---|---|---|
| 30317633052969 | 159180 | 正在运行 |
| 30317633398347 | 3999186 | 正在运行 |
| 30317638448111 | 1847941 | 正在运行 |
| 30317641455354 | 1031006 | 正在运行 |
| 30317643272615 | 888387 | 正在运行 |
2. LMKD 终止
LMKD 会将低内存终止事件写入 instant 表。如需查询所有 LMKD 终止事件,请运行以下命令:
SELECT ts, name
FROM instant
WHERE name LIKE 'lmk,%'
ORDER BY ts ASC;
事件名称格式为:lmk,<pid>,<kill_reason>,<oom_score_adj>,<min_oom_score>,<max_thrashing>。
kill_reason:一个整数,表示lmkd决定终止进程的原因(在system/memory/lmkd/statslog.h中定义):0:PRESSURE_AFTER_KILL1:NOT_RESPONDING2:LOW_SWAP_AND_THRASHING(ZRAM 交换空间极低,并且发生了抖动)3:LOW_MEM_AND_SWAP(系统的可用内存和 ZRAM 交换空间均不足)4:LOW_MEM_AND_THRASHING5:DIRECT_RECL_AND_THRASHING6:LOW_MEM_AND_SWAP_UTIL7:LOW_FILECACHE_AFTER_THRASHING8:LOW_MEM9:DIRECT_RECL_STUCK
max_thrashing:终止时的最大页面抖动百分比。
示例输出:
| ts | name |
|---|---|
| 30326148679967 | lmk,2308,3,900,201,0 |
| 30330439763710 | lmk,2309,3,500,201,0 |
| 30334985999837 | lmk,2310,2,200,0,307 |
可视化
如需直观呈现数据,您可以使用以下 gnuplot 脚本在单个时间序列上绘制系统可用内存、kswapd 活动、lmkd 终止和 PSI。该脚本假定它是从 Android build 顶级目录运行的。
输出图示例
以下图表是使用上述脚本生成的,其中包含代表测试运行的示例数据。
图表说明:
此图展示了压力测试期间不同内存管理组件之间的动态交互:
- 可用内存下降:随着主应用分配内存,系统可用内存(紫色线条)稳步下降。
- kswapd 激活:当可用内存低于特定水位线时,
kswapd会唤醒(蓝色脉冲),通过交换到 ZRAM 来回收内存。这表现为间歇性峰值。 - PSI 峰值:由于
kswapd难以跟上高分配速率,线程开始在内存分配上停滞,导致 PSI(压力失速信息)(绿线)出现峰值。 - LMKD 终止:当 PSI 达到临界水平(例如,在概念上超过
ro.lmk.psi_complete_stall_ms阈值)且可用内存极低时,lmkd会介入,通过终止低优先级进程(时间 30 和 45 处的橙色脉冲)来释放大量内存。请注意,在终止进程后,可用内存会立即增加,PSI 会下降,这表明终止进程可有效缓解压力。 ________________________________________________________________________________
← 内存回收:逐出和交换 | ↑ 向上 | 将内存页返回给操作系统 →