Low Memory Killer Daemon(LMKD)早在2013年被提交进AOSP代码库,其一开始就有两个部分的功能:1、基于Memory的CGroup进行进程的回收;2、作为frameworks与kernel的沟通桥梁传递参数与信息;但由于kernel始终存在lowmemorykiller驱动,因此LMKD的第一个功能始终没有被启用。
//判断节点"/sys/module/lowmemorykiller/parameters/minfree"
use_inkernel_interface = !access(INKERNEL_MINFREE_PATH, W_OK);
if (use_inkernel_interface) {
ALOGI("Using in-kernel low memory killer interface");
} else {
//只有节点无效时才会初始化事件监听与回调
ret = init_mp(MEMPRESSURE_WATCH_LEVEL, (void *)&mp_event);
if (ret)
ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");
}
随着代码的不断完善、充实,现在的LMKD已经不仅可以完成kernel中lowmemorykiller负责的工作,并且还新增了很多可配置项,配置组合与定制性非常之高,而Google也在2018年4月在system/core/lmkd仓库中添加README.md,并宣判了lowmemorykiller的死期——kernel-4.12。即从kernel-4.12以后,内核层面将不在包括lowmemorykiller.c及其相关代码,而我们一直使用的低内存进程回收机制则会全权交由用户态的LMKD完成。
以下是system/core/lmkd/README.md的官方描述:
Android Low Memory Killer Daemon ================================ Introduction ------------ Android Low Memory Killer Daemon (lmkd) is a process monitoring memory state of a running Android system and reacting to high memory pressure by killing the least essential process(es) to keep system performing at acceptable levels. Background ---------- Historically on Android systems memory monitoring and killing of non-essential processes was handled by a kernel lowmemorykiller driver. Since Linux Kernel 4.12 the lowmemorykiller driver has been removed and instead userspace lmkd daemon performs these tasks. Android Properties ------------------ lmkd can be configured on a particular system using the following Android properties: ro.config.low_ram: choose between low-memory vs high-performance device. Default = false. ro.lmk.use_minfree_levels: use free memory and file cache thresholds for making decisions when to kill. This mode works the same way kernel lowmemorykiller driver used to work. Default = false ro.lmk.low: min oom_adj score for processes eligible to be killed at low vmpressure level. Default = 1001 (disabled) ro.lmk.medium: min oom_adj score for processes eligible to be killed at medium vmpressure level. Default = 800 (non-essential processes) ro.lmk.critical: min oom_adj score for processes eligible to be killed at critical vmpressure level. Default = 0 (all processes) ro.lmk.critical_upgrade: enables upgrade to critical level. Default = false ro.lmk.upgrade_pressure: max mem_pressure at which level will be upgraded because system is swapping too much. Default = 100 (disabled) ro.lmk.downgrade_pressure: min mem_pressure at which vmpressure event will be ignored because enough free memory is still available. Default = 100 (disabled) ro.lmk.kill_heaviest_task: kill heaviest eligible task (best decision) vs. any eligible task (fast decision). Default = false ro.lmk.kill_timeout_ms: duration in ms after a kill when no additional kill will be done, Default = 0 (disabled) ro.lmk.debug: enable lmkd debug logs, Default = false
目前上游SoC厂商释放的基于Android P的代码普遍为kernel4.4与kernel4.9,这两个版本都没有剔除lowmemorykiller驱动,因此这些版本上的配置变得更为复杂。
首先撇开LMKD作为与frameworks(ProcessList.java)通信的桥梁的功能不谈,LMKD的杀进程功能在kernel中lowmemorykiller驱动工作的情况下,是不会工作的。
//判断节点"/sys/module/lowmemorykiller/parameters/minfree"
has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
use_inkernel_interface = has_inkernel_module;
if (use_inkernel_interface) {
ALOGI("Using in-kernel low memory killer interface");
} else {
//只有use_inkernel_interface为假时才会初始化并注册回调
if (!init_mp_common(VMPRESS_LEVEL_LOW) ||
!init_mp_common(VMPRESS_LEVEL_MEDIUM) ||
!init_mp_common(VMPRESS_LEVEL_CRITICAL)) {
ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");
return -1;
}
}
要想启用LMKD的进程回收功能,那么需要做的第一件事就是禁用kernel中的lowmemorykiller驱动:
修改项目的defconfig:
#CONFIG_ANDROID_LOW_MEMORY_KILLER is not set
增量编译前记得删除out下的KERNEL_OBJ缓存。
以下针对上述文档中所列的属性作一一解释:
ro.lmk.use_minfree_levels
LMKD支持传统的基于minfree的判断机制,也支持更为客观的vmpressure,这两种判断机制通过属性ro.lmk.use_minfree_levels控制——true使用传统minfree机制,默认false使用vmpressure;
但是需要注意的是,在MTK的kernel-4.4下,使用vmpressure需要使能如下宏:
CONFIG_MEMCG=y
QCOM的kernel-4.9根据目前观察则不需要。
ro.lmk.low / ro.lmk.medium / ro.lmk.critical
这三个属性分别表示LMKD在vmpressure上报0/1/2是需要遍历并杀死进程的最小oom_adj。
例如ro.lmk.low默认1001,则表示当vmpressure==0时,不进行杀进程前的遍历行为(因为所有进程的oom_adj都小于等于1000);
ro.lmk.medium默认800,则表示当vmpressure==1时,默认从oom_adj大于等于800的进程中选择出相应进程杀死以释放足够内存空间;
ro.lmk.critical默认0,则表示当vmpressure==2时,默认从oom_adj大于等于0的进程中选择出相应进程杀死以释放足够内存空间;
ro.lmk.critical_upgrade
这个属性很鸡肋,且是否生效还取决于ro.lmk.upgrade_pressure属性的值,全程只有如下代码有效,请根据实际情况决定是否开启:
if (enable_pressure_upgrade && level != VMPRESS_LEVEL_CRITICAL) {
// We are swapping too much.
// mem_pressure值越小,表示swap越多,侧面表示内存压力越大
if (mem_pressure < upgrade_pressure) {
level = upgrade_level(level);
if (debug_process_killing) {
ALOGI("Event upgraded to %s", level_name[level]);
}
}
}
ro.lmk.upgrade_pressure / ro.lmk.downgrade_pressure
若想通过属性ro.lmk.upgrade_pressure与ro.lmk.downgrade_pressure来获取更为灵活的判断机制,则需要使能如下宏:
CONFIG_MEMCG_SWAP=y
ro.lmk.critical_upgrade属性的实际效果取决于ro.lmk.upgrade_pressure,但是ro.lmk.upgrade_pressure的作用并不完全取决于ro.lmk.critical_upgrade,因为除了上述代码以外ro.lmk.upgrade_pressure 与 ro.lmk.downgrade_pressure还在其他地方被LMKD用于判断是否需要杀进程;
// If the pressure is larger than downgrade_pressure lmk will not
// kill any process, since enough memory is available.
if (mem_pressure > downgrade_pressure) {
if (debug_process_killing) {
ALOGI("Ignore %s memory pressure", level_name[level]);
}
return;
} else if (level == VMPRESS_LEVEL_CRITICAL &&
mem_pressure > upgrade_pressure) {
if (debug_process_killing) {
ALOGI("Downgrade critical memory pressure");
}
// Downgrade event, since enough memory available.
level = downgrade_level(level);
}
这两个属性的取值用于解决vmpressure档位过少的缺陷,通过mem_pressure来辅助判断当前内存使用是否紧张。
mem_pressure的算法如下:
//Physical memory size used
if ((mem_usage = get_memory_usage(&mem_usage_file_data)) < 0) {
goto do_kill;
}
//Physical memory size used + Swap size
if ((memsw_usage = get_memory_usage(&memsw_usage_file_data)) < 0) {
goto do_kill;
}
// Calculate percent for swappinness.
mem_pressure = (mem_usage * 100) / memsw_usage;
可见这个值不会大于100,越小说明swap使用越高。
ro.lmk.kill_heaviest_task
默认false - 每次需要杀进程时,则从高oom_adj开始遍历,同oom_adj时从最后加入列表的开始杀,直到释放出足够内存为止;
true - 每次需要杀进程时,则从高oom_adj开始遍历,同oom_adj时从rss最高的进程开始杀(参考节点/proc/<$pid>/statm中的第二个数值),直到释放出足够内存为止;
ro.lmk.kill_timeout_ms
默认0 - 进行过一次杀进程的操作后,间隔多少毫秒之后才会再一次执行检查与杀进程操作。
ro.lmk.debug
默认false - 当LMKD工作遇到异常情况时,是否开启一些调试log输出。
以上就是对LMKD的可配置参数进行了一个列举和简述,下一节会就QCOM与MTK的差异做个简单的对比,并列举更多属性之间的冲突或者协作情况。