内存管理-51-lmkd-1-笔记
一、简介
lmkd(LowMemoryKiller Daemon)是一个由 init 进程 fork 出来的守护进程,用于当安卓系统内存不足的时候,挑选合适的进程杀死,以保证系统的稳定流畅性。实现文件:
/android/system/memory/lmkd/lmkd.cpp //实现 /android/system/memory/lmkd/tests/lmkd_test.cpp //测试
lmkd 监听 kernel 的 /proc/pressure/memory 节点,当内存有压力的时候,lmkd 将收到消息并根据策略杀死进程。
# cat /proc/pressure/memory some avg10=2.79 avg60=0.67 avg300=0.18 total=5545238 full avg10=0.22 avg60=0.08 avg300=0.02 total=1826480
avg10、avg60、avg300 分别表示10s、60s、300s 期间因内存阻塞的百分比,some 表示至少有一个任务因内存而阻塞的时间占比,full 表示所有任务都因内存而阻塞的时间占比。
lmkd低压力默认值(some):70ms / 1000ms(7%)
lmkd中压力默认值(some):100ms / 1000ms(10%)
lmkd高压力默认值(full):70ms / 1000ms(7%)
该新机制的名称叫PSI,Android10及更高版本支持,相比旧机制的优势有:误杀、内存碎片。
当 lmkd 收到 kernel 的内存有压力的消息时,由下面的公式来确定是否要杀进程:
/* * /system/core/lmkd/lmkd.c * * lowmem_minfree: [72MB,90MB,108MB,126MB,216MB,315MB] * lowmem_adj: [0, 100, 200, 250, 900, 950] * other_free = proc/meminfo.MemFree - proc/zoneinfo.reserve * other_file = proc/meminfo.Cached + proc/meminfo.Buffers - proc/meminfo.Shmem */ for (i = 0; i < lowmem_adj.size(); i++) { minfree = lowmem_minfree[i]; if (other_free < minfree && other_file < minfree) { min_score_adj = lowmem_adj[i]; goto do_kill; } }
说明:
(1) lowmem_minfree、lowmem_adj 数值,由 AMS(ProcessList.mOomMinFreeHigh)提供。
(2) proc/zoneinfo.reserve 是内存回收(kswapd)阈值,由 AMS(/proc/sys/vm/extra_free_kbytes)提供。
(3) proc/meminfo.Cached 借助文件系统访问,/proc/meminfo.Buffers 块设备直接访问,本质是硬盘上的文件。
(4) proc/meminfo.Shmem 是共享内存,因为基于 tmpfs 实现,被统计到 /proc/meminfo.Cached 中,需要排除。
二、lmkd杀进程日志
1. lowmemorykiller: Kill
在 main log 中:
406 406 I lowmemorykiller: Kill 'com.android.speech' (20208), uid 10064, oom_score_adj 905 to free 126400kB rss, 21724kB swap; reason: low watermark is breached and swap is low (684560kB < 698356kB)
oom_score_adj: 是进程的 OOM 调整分值,范围通常 [-1000, 1000],数值越大,越"容易被杀",905 已经是很容易被回收的等级(典型后台/缓存类)。
rss: 预期释放该进程占用的内存:rss=126400kB:常驻物理内存。
swap: swap=21724kB:该进程已换出到 swap/zram 的内存。
reason: low watermark is breached 跌破低水位/最小水位(min),说明系统可回收余量不够,进入高压状态。
swap is low: 剩余可用 swap 容量低。(684560kB < 698356kB) 表示当前剩余 swap:684560kB,lmkd 判定"swap 低"的阈值:698356kB, 因为当前值小于阈值,所以触发了 swap is low 条件。
换句话说,这次 kill 的判定是"双条件成立":(1) 内存水位线告急; (2) swap 余量也低于安全阈值;
1.1 swap is low 中的 swap
/* In the case of ZRAM, mi->field.free_swap can't be used directly, because swap space is taken from the free memory */ static inline int64_t get_free_swap(union meminfo *mi) { /* swap_compression_ratio 可调,ro.lmk.swap_compression_ratio,设0就退化成直接用 SwapFree。*/ if (swap_compression_ratio) { /* * field.free_swap == /proc/meminfo 的 SwapFree * mi->field.easy_available = mi->field.nr_free_pages + mi->field.inactive_file, 等于 /proc/meminfo 的 MemFree + Inactive(file) * TODO: 为啥不是 Inactive(anon) */ return std::min(mi->field.free_swap, mi->field.easy_available * swap_compression_ratio); } return mi->field.free_swap; }
这里判断使用的 swap 来自当前上面函数。
道理:ZRAM 的 swap 是从内存里偷出来的,SwapFree 再大,没有 MemFree+可回收file给你放压缩块也白搭。这正好解释了你的 2898M vs 755M:SwapFree=2898M 是名义值
但 MemFree+Inactive(file) 太小,乘完压缩比只剩 755M,lmkd 认 755M,直接判 "swap is low" 杀进程了。
三、lmk杀进程逻辑
1. 只看 oom_adj
Android lmkd/LMK 杀进程只看 oom_adj, 越小越重要,越大越先杀:
-900: system_server / Native -800: PERSISTENT_PROC_ADJ <- persist App -700: PERSISTENT_SERVICE_ADJ 0: FOREGROUND_APP 100: VISIBLE_APP 200: PERCEPTIBLE_APP (前台Service、音乐等) 300~900: SERVICE_A/B、CACHED (后台、空进程) 900+: CACHED_EMPTY
查法:
dumpsys activity oom
cat /proc/<pid>/oom_score_adj
执行 dumpsys activity oom 的例子:
/* * oom: cur=-800 state: * cur=PER: PER = PERSISTENT,android:persistent=true 的那个,fixed 表示 adj 被钉死。 * -800 = PERSISTENT_PROC_ADJ,lmkd最后一个才杀它,杀了system_server也会马上拉起。 * u0a144,这个是主用户的SystemUI。 */ PERS # 96: pers F/ /PER LCMNFUATI t: 0 22288:com.android.systemui/u0a144 (fixed) //user 0, PER,真身 oom: max=-800 curRaw=-800 setRaw=-800 cur=-800 set=-800 state: cur=PER set=PER lastRss=264MB lastCachedRss=0.00 cached=false empty=false hasAboveClient=false /* * IMPF = IMPORTANT_FOREGROUND,因为它被 com.android.car 以Service bind住:hasAboveClient逻辑、有TI标记。 * adj=100 = VISIBLE_APP级别,很重要,低内存不会优先杀。 * 这根本不是 com.android.systemui,别混了。 */ Proc # 16: vis F/ /IMPF -------TI t: 0 4359:com.sam.systemui/u10s1000 (service) //IMPF: 前台重要进程 com.sam.systemui/.SystemUIService<=Proc{2569:com.android.car/1000} oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100 state: cur=IMPF set=IMPF lastRss=253MB lastCachedRss=0.00 cached=false empty=false hasAboveClient=false /* * cch-empty,炮灰,最容易被杀. CEM = CACHED_EMPTY,空缓存进程,adj=905,lmkd 第一批杀的就是它。 * 同包名不同用户:u10a144,是给 user 10 起的 SystemUI 进程,但现在没窗口、没 Service,就是个空壳占 161MB,所以被降级到 cch。 */ Proc # 23: cch + 5 b/ /CEM --------- t: 0 22390:com.android.systemui/u10a144 (cch-empty) //user 10 oom: max=1001 curRaw=905 setRaw=905 cur=905 set=905 state: cur=CEM set=CEM lastRss=161MB lastCachedRss=161MB cached=true empty=true hasAboveClient=false
多用户+双 SystemUI 共存, 很正常:u0 是机头主用户,u10 是第二个用户/后排。
2. persist App 比较难杀,因为:
(1) adj 最低一档: persist=true 的进程固定在 -800/-700,lmkd 按从大到小杀,轮到它时系统基本已经挂了。
(2) 杀了马上拉起: ActivityManagerService 对 persist 进程有保护,onProcessDied 发现是 persist 会立即 startProcessLocked 重启。非 persist 杀了就没了。
(3) 不进 Cached 队列: 非 persist 的后台 App 很快降级到 CACHED_APP (900+),是低内存第一批被收割的。
所以同样后台,非persist App 更容易被杀。
posted on 2023-11-18 16:06 Hello-World3 阅读(102) 评论(0) 收藏 举报
浙公网安备 33010602011771号