内存管理-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)    收藏  举报

导航