LowMemoryKiller 是 Android 系统在 Linux kernel 的 OOMKiller 基础上打的一个补丁。OOMKiller 在 kernel 没法再分配内存的时候,寻找一个得分最高的进程来杀掉。LowMemoryKiller 则提前一步,通过把剩余内存划分成不同的级别,内存在消耗的过程中,触发不同的级别,杀死相应的 app 进程。在触发 OOMKiller 前,大量缓存的 app 进程已经被杀死掉了。
先简单说一下 OOMKiller。
我们查看任一进程的 proc 信息 (如:/proc/1),都会看到以下三个参数:
- oom_score -- 是该进程的最终得分,分数越高,越容易被杀死;
- oom_score_adj -- 范围:[-1000, 1000],是 kernel 用来配置进程优先级的。值越低,最终的 oom_score 越低。
- oom_adj -- 范围:[-16, 15],是给用户来配置进程优先级的。为了方便用户配置,提供了范围更小的 oom_adj 参数,数字越小优先级越高,-17 表示该进程被保护,不被 OOMKiller 杀死。
因此,用户设置 oom_adj 后,kernel 会转换并更新该进程实际的 oom_score_adj 值,它们的换算关系是:
#define OOM_DISABLE (-17)
#define OOM_SCORE_ADJ_MAX 1000
oom_score_adj = (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;
oom_adj = (oom_score_adj * - OOM_DISABLE) / OOM_SCORE_ADJ_MAX;
从上面可以看到,这个算法是有损的,所以,有时候会发现读出的 oom_adj 的值同设置得不一样了。如,设置 oomajd=13, 那么 oom_score_adj=764,再次读 oom_adj,764 * 17 / 1000 == 12 了。
当内存耗尽的时候,OOMKiller 会调用 out_of_memory() 来 select_bad_process(),oom_score 最大的值就是那个将要被杀死的 bad process。oom_badness() 以 oom_score_adj 作为基础值,根据是否为管理员进程,占用的内存情况,来计算出最终的 oom_score 值,分值越高,越容易被杀死。
OOMKiller 相应的源码文件:
/fs/proc/base.c
/mm/oom_kill.c
在 Android 系统里面,当 app 的状态发生变化,如:创建,收到广播,唤醒,放入后台等,ActivityManagerService 的 updateOomAdjLocked() 会 computeOomAdjLocked(),然后,通过 applyOomAdjLocked() 把 app 的 oom_adj 值写入到 Linux Kernel。
那么 AN 是如何计算每个 app 最终的 oom_adj 值的呢?
我们知道 oom_adj 的范围是 [-16, 15],AN 根据 app 进程的特性进行了分类,不同的类别,对应不同的数值。
- 9 ~ 15: 是缓存到后台的 app。
- 8: service B 列表,长时间未使用的 service 进程。
- 7: 前一个 app。
- 6: Home app。
- 5: 包含 service 的 app 进程。
- 4: 高权重的应用--隐藏的属性,AN P 以上版本才能配置:android:cantSaveState=”true”
- 3: backup app--正在被备份的 app。
- 2: 用户可感知的后台进程,如:后台背景音乐播放器。
- 1: 前台 app 启动的一些可见的组件,如:弹出的 Email activity。
- 0: 前台 app.
- -11: persistent service -- android:persistent:=true
- -12: 常驻内存的系统 app--如:系统自带的拨号 app。
- -16: 系统进程--如:system_server 进程。
- -17: 不受 oom_adj 的管理,该进程不会被杀死,也 native process 的默认值。
LowMemoryKiller 注册了 shrinker--Linux Kernel 的一个内存管理工具,当 kernel 需要回收内存时,会回调 LowMemoryKiller 的 lowmem_shrink(),它先检查 kernel 剩下多少内存,根据剩下的内存数量来匹配数组 lowmem_minfree[],找到数组索引值,然后,再使用该索引值,从 lowmem_adj[] 这个数组里面就得到目标 oom_adj 值,最终,在大于等于该目标 oom_adj 的进程中,杀死拥有最大 oom_adj 值的进程--send_sig(SIGKILL, selected, 0)。算法其实很简单,就是两个一维数组的映射。
static short lowmem_adj[6] = {
0,
1,
6,
12,
};
static int lowmem_minfree[6] = {
3 * 512, /* 6MB */
2 * 1024, /* 8MB */
4 * 1024, /* 16MB */
16 * 1024, /* 64MB */
};
如:系统剩下的内存为 31024,它 小于 4 1024,对应的数组索引是 2,lowmem_adj[2] 对应的是 6,那么系统将在 oom_adj>= 6 的进程中,找一个最大的 oom_adj 的进程,然后,杀死它释放内存。
Android 在初始化的时候,会通过 ProcessList::updateOomLevels() 来设定上面两个数组的初始值,我们可以通过 framework 的 config.xml,或 /sys 文件系统接口进行调整 lowmem_minfree []。
/sys/module/lowmemorykiller/parameters/minfree
<integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">-1</integer>
<integer name="config_lowMemoryKillerMinFreeKbytesAdjust">0</integer>
config_….KbytesAbsolute:非 - 1 的情况下,是绝对值,AN 使用下面算法,得到实际数组值。
for (int i = 0 ; i < mOomAdj.length; i++) {mOomMinFree[ i] = (int) ((float)minfree_abs * mOomMinFree[i] / mOomMinFree[mOomAdj.length - 1] );
}
config_….KbytesAdjust: 非 0 情况下,直接在每个数组值上 += reserve_adj;
如:<integer name="config_....KbytesAdjust">-512</integer>,表明每个数组值都减少 512。
lowmem_adj[] 可以通过 /sys 文件系统接口来进行调整。
/sys/module/lowmemorykiller/parameters/adj
当然了在实际上开发过程中,也可以直接在这个函数里面打补丁,或者读取系统属性,通过属性来进行配置等等。像 MStar 方案,就定义了两个属性来进行第三方的配置:ro.mstar.lmkd.minfree 和 ro.mstar.lmkd.adj
到这里,基本上 LowMemoryKiller 算是说完了,最后,简单介绍下,AN 是如何把 oom_adj 值传给 kernel 的。
AN 通过 ProcessList:setOomAdj(),用 socket 与 lmkd 通讯,lmkd 通过 /sys 文件系统接口,把 oom_adj 值传递给 LinuxKernel。
/sys/module/lowmemorykiller/parameters/minfree
/sys/module/lowmemorykiller/parameters/adj
相关的源码文件分别在:
system/core/lmkd/ 生成 /system/bin/lmkd
drivers/staging/android/lowmemorykiller.c
framework/base/...../server/am/
欢迎大家来我的网站交流: 般若程序蝉