背景
LMK是Android原生kernel中的一种kill应用进程的机制,其原理是当系统可用内存不足某些阈值时,开始kill一些优先级较低的进程。在某款车机上,发现该机制存在不合理的地方,如
有如下几个问题:
(1)如上图所示,cache为1292476kB,实际上大于后面的524288kB,但依然还是进行了kill动作,为什么?
(2)频繁的kill动作会引发IO问题,从而影响系统性能;
(3)车机系统尽量少kill应用来使其可被快速启动;
原因分析
该款车机是Android9.0的版本(查看kernel/msm-4.14/Makefile,kernel版本为4.14.156),依然使用的是lowmemorykiller.c的逻辑,其主要代码路径为
kernel/msm-4.14/drivers/staging/android/lowmemorykiller.c
对于该逻辑,主要有如下几个逻辑。
kill时机
lowmemorykiller.c中通过register_shrinker(&lowmem_shrinker)和vmpressure_notifier_register(&lmk_vmpr_nb)分别注册了内存页回收的shrink事件和vmpressure的事件,因此,负责正式kill事件的lowmem_scan方法和负责kill前的若干条件判断的lmk_vmpressure_notifier方法会在Android内核进行内存回收(kswapd0进程)时就会被非常频繁的调用,但这两个方法之间仅有变量shift_adj有联系(该变量用于lowmem_scan方法中对min_score_adj的调整),执行的时机是不相关的。
static struct shrinker lowmem_shrinker = {
.scan_objects = lowmem_scan,
.count_objects = lowmem_count,
.seeks = DEFAULT_SEEKS * 16
};
static struct notifier_block lmk_vmpr_nb = {
.notifier_call = lmk_vmpressure_notifier,
};
static int __init lowmem_init(void)
{
register_shrinker(&lowmem_shrinker);
vmpressure_notifier_register(&lmk_vmpr_nb);
if (register_hotmemory_notifier(&lmk_memory_callback_nb))
lowmem_print(1, "Registering memory hotplug notifier failed\n");
return 0;
}
首先回答问题“cache为1292476kB,实际上大于后面的524288kB,但依然还是进行了kill动作,为什么?”,原因是这两个值与kill的逻辑并没有因果的关系,但是当内存的pressure值大于90%(内存回收的压力率),因为lowmem_scan方法的adjust_minadj会根据shift_adj对min_score_adj值进行调整,就可能出现上述看似矛盾的日志;否则LMK还是会按照上述两者的大小比较来kill。实际上,当内存的pressure值大于90%的时候,就算没有及时kill某些进程腾出内存,系统也并不会立即OOM,内核还是会通过shrink快速回收,除非遇到无法及时回收才会导致OOM。在该车机上,经常也会碰到上述矛盾的日志,就是在当时时刻系统的可用内存紧缺(/proc/vmstat中的nr_free_pages,相当于/proc/meminfo中的MemFree的值)导致内存回收的压力大于90%了。但此时的文件页(other_file,主要包含/proc/meminfo中的Cached、SwapCached和Buffers)内存可能还很多,也是大概率可以被及时回收的,此时也并不一定要立即通过LMK的kill方式腾出内存。
static void vmpressure_work_fn(struct work_struct *work)
{
struct vmpressure *vmpr = work_to_vmpressure(work);
unsigned long scanned;
unsigned long reclaimed;
unsigned long pressure;
enum vmpressure_levels level;
bool ancestor = false;
bool signalled = false;
spin_lock(&vmpr->sr_lock);
/*
* Several contexts might be calling vmpressure(), so it is
* possible that the work was rescheduled again before the old
* work context cleared the counters. In that case we will run