Android Low Memory Killer Daemon (lmkd) 是一个进程,它监视正在运行的 Android 系统的内存状态,并通过杀死最不重要的进程来对高内存压力做出反应,以使系统性能保持在可接受的水平。
从历史上看,在 Android 系统上,内存监控和非必要进程的终止是由内核 lowmemorykiller 驱动程序处理的。自 Linux Kernel 4.12 起,lowmemorykiller 驱动程序已被删除,取而代之的是用户空间 lmkd 守护进程执行这些任务。(摘抄自lmkd README.md)
Android10及以前版本lmkd的源码目录在 system/core/lmkd 中,Android11及以后版本则是在 system/memory/lmkd 中。
翻看历史源码,可以看到lmkd的演变历程:
Android版本 | lmkd版本 |
AndroidO | Kernel lowmemorykiller |
AndroidP | Userspace + memcg(vmpressure ) |
Android10 | userspace + PSI/vmpressure |
Android11 | Userspace + PSI/vmpressure,支持配置psi的监控值 |
lmkd流程
属性配置
属性 | 含义 | 默认值 |
ro.lmk.low | 内存压力为low时,开杀的最小adj | 1001(不杀) |
ro.lmk.medium | 内存压力为medium时,开杀的最小adj | 800(B list service) |
ro.lmk.critical | 内存压力为critical时,开杀的最小adj | 0(前台进程) |
ro.lmk.debug | lmkd是否开启debug信息 | false(不开启) |
ro.lmk.critical_upgrade | 是否允许升级内存压力,主要根据swap使用情况 | false(不允许) |
ro.lmk.upgrade_pressure | 升级内存压力的判断阈值 | 100 |
ro.lmk.downgrade_pressure | 降级内存压力的判断阈值 | 100 |
ro.lmk.kill_heaviest_task | 是否优先选择最重进程开杀 | false |
ro.config.low_ram | 低内存设备还是性能设备 | false(性能设备) |
ro.lmk.kill_timeout_ms | 距离上一次杀进程时间不足timeout,可能放弃本次杀操作 | 0(默认没有间隔时间) |
ro.lmk.use_minfree_levels | 是否使用minfree_levels选择最小开杀adj | false |
ro.config.per_app_memcg | 是否支持每个应用的内存限制 | low_ram_device(Go设备为true,性能设备为false) |
ro.lmk.swap_free_low_percentage | swap使用情况判断阈值 | 10 |
ro.lmk.use_psi | 是否使用psi作为内存压力监测接口 | true |
sys.lmk.minfree_levels | 6档剩余内存和需要被kill的adj对应关系 | lmkd设置 |
lmkd在启动时就会去获取相关的属性对lmkd的流程进行配置,其中有一些属性是我们比较常用的配置,且有些配置实际上是互相关联的,单独使用不一定生效。
ro.config.low_ram:prop值为true时表示为低内存设备,Android10一般指内存为1GB及以下的设备,2GB也可以配置。此配置与ro.config.low_ram和ro.lmk.kill_timeout_ms是关联的,low_ram设备默认会限制每次进程的使用内存,且在kill_timeout_ms时间内只kill一个进程。low_ram设备kill进程的策略与性能设备也不一样,low_ram设备每次只kill一个task。
ro.lmk.use_psi:prop值为true时默认使用PSI机制,实际上设置了此值也不一定就是使用了PSI,也需要kernel支持了PSI机制才行。否则还是会走vmpressure。
ro.lmk.use_minfree_levels:prop值为true时,根据free内存的值来决定需要被kill的adj范围,此属性与ro.lmk.low/ro.lmk.medium/ro.lmk.critical冲突了。因此只要设置了ro.lmk.use_minfree_levels为true,则无需设置ro.lmk.low/ro.lmk.medium/ro.lmk.critical。
ro.lmk.critical_upgrade:prop值为true时,支持根据swap的情况进行升级或降级内存压力等级,此值与 ro.lmk.upgrade_pressure/ro.lmk.downgrade_pressure/ro.lmk.swap_free_low_percentage均有关。但代码中对downgrade_pressure的判断并没有依赖critical_upgrade的值,此处不确定是否是bug。但此值与系统是否使用swap机制有关,如果没开启swap机制,也并不生效。对于低内存设备应该有帮助,可在低内存设备上进行相关配置。
启用PSI机制
Android10开始,lmkd就支持基于PSI机制的监控,PSI机制依赖于内核的支持,PSI 是kernel 4.2 添加进来的,4.2 及以后版本才有PSI 功能。
PSI
那么PSI是什么呢?
当CPU、内存或IO设备被竞争时,工作负载将经历延迟峰值、吞吐量损失,并运行OOM终止的风险。如果没有对这种争用的精确度量,用户就被迫要么安全行事,要么不充分利用其硬件资源,要么孤注一掷,经常遭受由于过度承诺而导致的中断。
PSI(Pressure Stall Information)特性识别并量化这种资源紧张所造成的中断,以及它对复杂工作负载甚至整个系统的时间影响。对资源稀缺造成的生产力损失有一个准确的度量,可以帮助处于根据硬件调整负载或是根据工作负载需求提供硬件的用户。
当PSI 实时聚合上述这些信息,系统可以通过一些技术进行动态管理,例如减负荷、将作业迁移到其他系统或数据中心、战略性暂停或终止低优先级或可重启的批处理作业。
PSI就是一种对CPU、内存和IO设备的竞争数据信息统计工具,对用户空间提供了监听、获取信息的接口,以方便用户程序检测当前系统的资源竞争情况。
而lmkd主要关注的是内存部分的资源竞争信息。
vmpressure
了解了PSI后,我们还需要了解一下vmpressure这个旧版本的内存监控机制,以便我们了解Google使用PSI机制替代vmpressure的理由,已经我们是否适用于我们的产品上。
vmpressure 的计算在每次系统尝试做do_try_to_free_pages 回收内存时进行。其计算方法非常简单:(1 - reclaimed/scanned)*100,也就是说回收失败的内存页越多,内存压力越大。
同时 vmpressure 提供了通知机制,用户态或内核态程序都可以注册事件通知,应对不同等级的压力。默认定义了三级压力:low/medium/critical。
1.low 代表正常回收;
2.medium 代表中等压力,可能存在页交换或回写,默认值是 65%;
3.critical 代表内存压力很大,即将 OOM,建议应用即可采取行动,默认值是 90%。
vmpressure 也有一些缺陷:
1.结果仅体现内存回收压力,不能反映系统在申请内存上的资源等待时间;
2.计算周期比较粗;
3.粗略的几个等级通知,无法精细化管理。
PSI配置
内核如果需要编译PSI 功能,需要确保 CONFIG_PSI=y:
如果希望内核支持PSI但默认不开启,可以配置CONFIG_PSI_DEFAULT_DISABLED=y,这样就可以通过cmdline中的psi=1参数来开启PSI。这种方式适合多产品形态使用同一内核配置或者是在调试阶段以做对比的场景下使用。(如有其他场景,欢迎补充。)
另外,由于需要使用userspace的lowmemorykiller,因此必须将内核的lowmemorykiller关闭了,翻查了Linux source code (v5.17.7) - Bootlin各个Linux版本,确实是从linux-4.12开始,lowmemorykiller就已移除除内核源码,在此Linux版本之前,可到 drivers/staging/android目录下查看是否还有lowmemorykiller.c文件,如有,则需要将配置CONFIG_ANDROID_LOW_MEMORY_KILLER=n。
lmkd方面则是需要配置ro.lmk.use_psi=true,默认值就是为true,即无需配置,只要不是配置成false即可。另外lmkd会向设备端的 /proc/pressure/memory 节点进行访问、注册监听,因此必须保证lmkd具有该节点的访问权限,当然,这部分默认也是支持的,只要不改动到这里即可。
如何确认lmkd是否使用PSI机制呢,可以通过以下两种方式:
1.打开lmkd的log:在lmkd.c中加上#define LOG_NDEBUG 0,打开lmkd的info级别log,使用PSI机制在logcat中可以看到:
lowmemorykiller: Using psi monitors for memory pressure detection
如果是vmpressure,则输出:
lowmemorykiller: Using vmpressure for memory pressure detection
如果是内核的lowmemorykiller,则输出:
lowmemorykiller: Using in-kernel low memory killer interface
2.通过lmkd进程的proc节点查看:
# cd /proc/2739/fd
/proc/2739/fd # ls -l
total 0
lrwx------ 1 lmkd lmkd 64 2022-05-10 04:06 0 -> /dev/null
lrwx------ 1 lmkd lmkd 64 2022-05-10 04:06 1 -> /dev/null
lr-x------ 1 lmkd lmkd 64 1970-01-01 08:00 10 -> /proc/zoneinfo
lrwx------ 1 lmkd lmkd 64 1970-01-01 08:00 11 -> socket:[19398]
l-wx------ 1 lmkd lmkd 64 1970-01-01 08:00 12 -> /sys/kernel/debug/tracing/trace_marker
lrwx------ 1 lmkd lmkd 64 2022-05-10 04:06 14 -> socket:[15950]
l-wx------ 1 lmkd lmkd 64 1970-01-01 08:00 15 -> /dev/stune/foreground/tasks
l-wx------ 1 lmkd lmkd 64 2022-05-10 04:06 16 -> /dev/cpuset/foreground/tasks
l-wx------ 1 lmkd lmkd 64 2022-05-10 04:06 17 -> /dev/blkio/tasks
lrwx------ 1 lmkd lmkd 64 2022-05-10 04:06 2 -> /dev/null
lrwx------ 1 lmkd lmkd 64 1970-01-01 08:00 3 -> anon_inode:[eventpoll]
l-wx------ 1 lmkd lmkd 64 1970-01-01 08:00 4 -> /proc/pressure/memory
l-wx------ 1 lmkd lmkd 64 1970-01-01 08:00 5 -> /proc/pressure/memory
l-wx------ 1 lmkd lmkd 64 1970-01-01 08:00 6 -> /proc/pressure/memory
lrwx------ 1 lmkd lmkd 64 1970-01-01 08:00 7 -> socket:[17632]
l-wx------ 1 lmkd lmkd 64 1970-01-01 08:00 8 -> /dev/pmsg0
lr-x------ 1 lmkd lmkd 64 1970-01-01 08:00 9 -> /proc/meminfo
如果看到lmkd打开了 /proc/pressure/memory 节点,则表明使用了PSI机制。
PSI的内存信息解析
PSI对用户层提供的节点只有三个:cpu、io和memory。
$ cat cpu
some avg10=0.00 avg60=0.00 avg300=0.00 total=3246740
full avg10=0.00 avg60=0.00 avg300=0.00 total=45076
$ cat io
some avg10=0.00 avg60=0.00 avg300=0.00 total=5996623
full avg10=0.00 avg60=0.00 avg300=0.00 total=4112395
cat memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=5895065
full avg10=0.00 avg60=0.00 avg300=0.00 total=3964190
cpu、io 和memroy 有some 行和full 行;分别显示最近10s、60s和300s 的运行平均百分比。total 是总累计时间,以毫秒为单位。
some 这一行,代表至少有一个任务在某个资源上阻塞的时间占比,full 这一行,代表所有的非idle任务同时被阻塞的时间占比,这期间 cpu 被完全浪费,会带来严重的性能问题。
而lmkd也就是以这些信息为主进行监听,在lmkd init_psi_monitor方法中,就打开了/proc/pressure/memory,并往节点中写入:
"%s %d %d" % stall_type, threshold_us, window_us
stall_type取值有两个:some和full。
threshold_us表示在窗口时间内阻塞的时间。
window_us表示窗口时间,默认是1s。
然后这个fd就已经支持上监听了,当threshold_us在窗口时间和stall_type达到条件后,就会触发上报时间,通知lmkd处理,这个fd也就由lmkd进行epoll_wait阻塞等待。
这些参数在Android10上是不可变动的,而在Android11及以后版本上,就支持可配置了。
Android11的变动
Android10是首个使用PSI的Android版本,因此还不是很完善,其中一个地方就是PSI的监听阈值问题。在Android10上,PSI的三个内存压力级别的监听阈值是固定的:
static struct psi_threshold psi_thresholds[VMPRESS_LEVEL_COUNT] = {
{ PSI_SOME, 70 }, /* 70ms out of 1sec for partial stall */
{ PSI_SOME, 100 }, /* 100ms out of 1sec for partial stall */
{ PSI_FULL, 70 }, /* 70ms out of 1sec for complete stall */
};
当某个task在申请内存时在1s内达到70ms的阻塞,则上报low级别的内存压力事件,当某个task在申请内存时1s内达到100ms的阻塞,则上报medium级别的内存压力事件,当所有task在1s内累计达到70ms的阻塞,则认为是critical级别的内存压力。
而这个设定明显是不符合所有的设备和场景的,因此在Android11上,就增加了三个属性来进行配置:
属性 | 含义 | 默认值 |
ro.lmk.use_new_strategy | 是否使用新策略 | low_ram_device || !use_minfree_levels |
ro.lmk.psi_partial_stall_ms | medium级别的阈值 | low_ram:200 非low_ram:70 |
ro.lmk.psi_complete_stall_ms | critical级别的阈值 | 700 |
PSI的阈值需要支持可配置时,必须将ro.lmk.use_new_strategy=true。
static bool init_psi_monitors() {
/*
* When PSI is used on low-ram devices or on high-end devices without memfree levels
* use new kill strategy based on zone watermarks, free swap and thrashing stats
*/
bool use_new_strategy =
property_get_bool("ro.lmk.use_new_strategy", low_ram_device || !use_minfree_levels);
/* In default PSI mode override stall amounts using system properties */
if (use_new_strategy) {
/* Do not use low pressure level */
psi_thresholds[VMPRESS_LEVEL_LOW].threshold_ms = 0;
psi_thresholds[VMPRESS_LEVEL_MEDIUM].threshold_ms = psi_partial_stall_ms;
psi_thresholds[VMPRESS_LEVEL_CRITICAL].threshold_ms = psi_complete_stall_ms;
}
……
实际上Android11/Android12对PSI的支持更好了,策略也不断地完善了。Android10作为一个初始版本,确实功能上还有很多缺陷,而很多策略相关的东西也是固化的,并不好改动,因此如果需要更好的体验,可以将Android12的lmkd移植过来使用(可尝试,但并不建议,不方便维护)。
内存压力级别的判定
此处是以PSI机制来进行说明,vmpressure机制的判断如果需要深入可另外分析。而此节也不深入内核的PSI机制进行分析,本节主要是分析lmkd层的代码。
static bool init_mp_psi(enum vmpressure_level level) {
int fd = init_psi_monitor(psi_thresholds[level].stall_type,
psi_thresholds[level].threshold_ms * US_PER_MS,
PSI_WINDOW_SIZE_MS * US_PER_MS);
if (fd < 0) {
return false;
}
vmpressure_hinfo[level].handler = mp_event_common;
vmpressure_hinfo[level].data = level;
if (register_psi_monitor(epollfd, fd, &vmpressure_hinfo[level]) < 0) {
destroy_psi_monitor(fd);
return false;
}
maxevents++;
mpevfd[level] = fd;
return true;
}
init_psi_monitor方法是打开了/proc/pressure/memory节点并注册了监听器,通过返回的fd加入到epoll队列中。通过register_psi_monitor 将fd的监听回调和内存压力等级保存到epoll_event中。
在mainloop中不断监听epoll队列事件:
static void mainloop(void) {
……
while (1) {
struct epoll_event events[maxevents];
int nevents;
int i;
……
nevents = epoll_wait(epollfd, events, maxevents, -1);
/* Second pass to handle all other events */
for (i = 0, evt = &events[0]; i < nevents; ++i, evt++) {
if (evt->data.ptr) {
handler_info = (struct event_handler_info*)evt->data.ptr;
handler_info->handler(handler_info->data, evt->events);
if (use_psi_monitors && handler_info->handler == mp_event_common) {
/*
* Poll for the duration of PSI_WINDOW_SIZE_MS after the
* initial PSI event because psi events are rate-limited
* at one per sec.
*/
polling = PSI_POLL_COUNT;
poll_handler = handler_info;
clock_gettime(CLOCK_MONOTONIC_COARSE, &last_report_tm);
}
}
}
}
当mainloop中监听到epoll_event后,通过event获取到我们设置的handler和level信息,然后调用mp_event_common来处理我们事件。内存压力级别也就是从这里获取得到。
min_score_adj的确定
min_score_adj的值根据ro.lmk.use_minfree_levels属性走了不同的流程,数据的来源也不一样,当use_minfree_levels为false,则直接使用ro.lmk.low/ro.lmk.medium/ro.lmk.critical的值以及upgrade_pressure/downgrade_pressure来决定。当use_minfree_levels为true,则根据当前的空闲内存信息以及sys.lmk.minfree_levels(默认是由AMS设置下来的)来决定。
static void mp_event_common(int data, uint32_t events __unused) {
if (meminfo_parse(&mi) < 0 || zoneinfo_parse(&zi) < 0) {
ALOGE("Failed to get free memory!");
return;
}
if (use_minfree_levels) {
int i;
other_free = mi.field.nr_free_pages - zi.field.totalreserve_pages;
if (mi.field.nr_file_pages > (mi.field.shmem + mi.field.unevictable + mi.field.swap_cached)) {
other_file = (mi.field.nr_file_pages - mi.field.shmem -
mi.field.unevictable - mi.field.swap_cached);
} else {
other_file = 0;
}
min_score_adj = OOM_SCORE_ADJ_MAX + 1;
for (i = 0; i < lowmem_targets_size; i++) {
minfree = lowmem_minfree[i];
if (other_free < minfree && other_file < minfree) {
min_score_adj = lowmem_adj[i];
break;
}
}
if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
if (debug_process_killing) {
ALOGI("Ignore %s memory pressure event "
"(free memory=%ldkB, cache=%ldkB, limit=%ldkB)",
level_name[level], other_free * page_k, other_file * page_k,
(long)lowmem_minfree[lowmem_targets_size - 1] * page_k);
}
return;
}
goto do_kill;
}
通过meminfo_parse和zoneinfo_parse获取到空闲内存和预留内存的信息,然后通过nr_free_pages - totalreserve_pages来计算当前Android系统可使用的内存大小(other_free),以及计算缓存内存大小(other_file)。当两者都比lowmem_minfree[i](6档的内存水位线)小时,则开始大于该档所对应的adj的进程。
lowmem_minfree的值设定是由AMS通过socket设置到lmkd中的,这个在AMS与lmkd交互部分再详说。
当min_score_adj > OOM_SCORE_ADJ_MAX时,则表示不杀进程。
而当use_minfree_levels为false时,则默认使用三档的adj值:
if ((mem_usage = get_memory_usage(&mem_usage_file_data)) < 0) {
goto do_kill;
}
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;
if (enable_pressure_upgrade && level != VMPRESS_LEVEL_CRITICAL) {
// We are swapping too much.
if (mem_pressure < upgrade_pressure) {
level = upgrade_level(level);
if (debug_process_killing) {
ALOGI("Event upgraded to %s", level_name[level]);
}
}
}
// If we still have enough swap space available, check if we want to
// ignore/downgrade pressure events.
if (mi.field.free_swap >=
mi.field.total_swap * swap_free_low_percentage / 100) {
// 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);
}
}
……
if (!use_minfree_levels) {
/* Free up enough memory to downgrate the memory pressure to low level */
if (mi.field.nr_free_pages >= low_pressure_mem.max_nr_free_pages) {
if (debug_process_killing) {
ALOGI("Ignoring pressure since more memory is "
"available (%" PRId64 ") than watermark (%" PRId64 ")",
mi.field.nr_free_pages, low_pressure_mem.max_nr_free_pages);
}
return;
}
min_score_adj = level_oomadj[level];
}
mem_usage 和memsw_usage 是通过memcg获取的当前内存使用情况,memsw_usage是包括了swap中的内存交换部分,它们两者的百分比值mem_pressure 则表示了交换区的压力,用于升级/降级压力等级的指标。当mem_pressure 比较小时,说明swap分区中的压力也非常大,可以认为压力等级可上一个层次。当free_swap充足且mem_pressure比较大时,说明swap分区仍能交换出更多的内存空间,可降低压力等级。
然后再通过level_oomadj[level]来确定min_score_adj 的值。
kill进程
确认了min_score_adj 的值以及符合杀进程的条件后,lmkd就会通过find_and_kill_process方法来查找一个合适的进程进行杀死并释放内存。
static int find_and_kill_process(int min_score_adj) {
int i;
int killed_size = 0;
for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) {
struct proc *procp;
while (true) {
procp = kill_heaviest_task ?
proc_get_heaviest(i) : proc_adj_lru(i);
if (!procp)
break;
killed_size = kill_one_process(procp, min_score_adj);
if (killed_size >= 0) {
break;
}
}
if (killed_size) {
break;
}
}
return killed_size;
}
find_and_kill_process方法每次只会kill掉一个进程,以从adj值最大的进程开始kill,如果配置了kill_heaviest_task(ro.lmk.kill_heaviest_task)为true,则同一adj值的情况下,优先kill掉heaviest应用(即占用内存最多的应用),否则就是根据lru来选择最早的应用。
找到pid后调用kill_one_process方法来进行kill掉,里面也有一些小细节(代码不展开):
1.通过signal SIGKILL来杀死进程,这个是常规的杀进程方式。
2.提高了进程的优先级,应该是为了系统调度可以更快地执行进程的收尾处理,以便系统可以更快地回收这部分内存。
3.返回杀死进程带来的收益(释放的物理内存大小)。
当杀死进程后带来了大于0,则结束此次的杀进程任务。为什么不确认进程是否被杀死呢?因为lmkd与我们被杀死的进程并无亲属关系,无法监听进程的状态。
PSI方式下并不是只杀一个进程,虽然本次只杀了一个进程,但manloop中监听到内存压力事件就会调整epoll的超时时间。psi窗口1000ms,epoll的超时时间会调整到10ms,一次psi窗口可能有100次的超时触发。每次epoll的超时事件发生,会重新调用最近一次的内存压力处理事件继续处理,即mp_event_common,并且参数data还是最近一次的内存压力等级,以此实现继续杀内存操作。
AMS与lmkd的交互过程
lmkd在rc文件中创建了一个名为lmkd的socket,是用于跨进程使用,AMS就是通过这个socket与lmkd进行命令交互的。
lmkd支持5个命令:
enum lmk_cmd {
LMK_TARGET = 0, /* Associate minfree with oom_adj_score */
LMK_PROCPRIO, /* Register a process and set its oom_adj_score */
LMK_PROCREMOVE, /* Unregister a process */
LMK_PROCPURGE, /* Purge all registered processes */
LMK_GETKILLCNT, /* Get number of kills */
};
这5个命令的值与AMS中的定义是一样的,也必须一致。
LMK_TARGET :设置6档minfree与oom_adj_score对应关系。
LMK_PROCPRIO:设置进程的adj值,如果在lmkd中没有该进程的pid,则注册一个新的。
LMK_PROCREMOVE:在lmkd中移除一个进程,一般是应用进程强制停止、异常退出时被使用到。
LMK_PROCPURGE:重新初始化lmkd的进程信息,一般是AMS启动时执行,例如Android系统重启/初次启动时执行,特别是重启时,system_server和所有的应用进程均被杀死,但lmkd不一定挂了,因此需要重新初始化lmkd的进程信息。
LMK_GETKILLCNT:获取某个adj值被kill的进程个数。
AMS(ProcessList.java类中)与lmkd就是通过以上的命令进行交互的,我们主要对LMK_TARGET 和LMK_PROCPRIO进行分析,其他的比较简单,可自行分析。
LMK_TARGET
在min_score_adj的确定一节中,我们提到当设置了ro.lmk.use_minfree_levels属性值为true时,就会使用6档的minfree和oom_adj_score,而这些值就是AMS传递过来的:
// frameworks/base/services/core/java/com/android/server/am/ProcessList.java
private final int[] mOomAdj = new int[] {
FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
PERCEPTIBLE_LOW_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_LMK_FIRST_ADJ
};
// These are the low-end OOM level limits. This is appropriate for an
// HVGA or smaller phone with less than 512MB. Values are in KB.
private final int[] mOomMinFreeLow = new int[] {
12288, 18432, 24576,
36864, 43008, 49152
};
// These are the high-end OOM level limits. This is appropriate for a
// 1280x800 or larger screen with around 1GB RAM. Values are in KB.
private final int[] mOomMinFreeHigh = new int[] {
73728, 92160, 110592,
129024, 147456, 184320
};
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
// Scale buckets from avail memory: at 300MB we use the lowest values to
// 700MB or more for the top values.
float scaleMem = ((float) (mTotalMemMb - 350)) / (700 - 350);
// Scale buckets from screen size.
int minSize = 480 * 800; // 384000
int maxSize = 1280 * 800; // 1024000 230400 870400 .264
float scaleDisp = ((float)(displayWidth * displayHeight) - minSize) / (maxSize - minSize);
float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
if (scale < 0) scale = 0;
else if (scale > 1) scale = 1;
int minfree_adj = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
int minfree_abs = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
for (int i = 0; i < mOomAdj.length; i++) {
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
if (is64bit) {
// Increase the high min-free levels for cached processes for 64-bit
if (i == 4) high = (high * 3) / 2;
else if (i == 5) high = (high * 7) / 4;
}
mOomMinFree[i] = (int)(low + ((high - low) * scale));
}
if (minfree_abs >= 0) {
for (int i = 0; i < mOomAdj.length; i++) {
mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
}
}
if (minfree_adj != 0) {
for (int i = 0; i < mOomAdj.length; i++) {
mOomMinFree[i] += (int)((float) minfree_adj * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
if (mOomMinFree[i] < 0) {
mOomMinFree[i] = 0;
}
}
}
if (write) {
ByteBuffer buf = ByteBuffer.allocate(4 * (2 * mOomAdj.length + 1));
buf.putInt(LMK_TARGET);
for (int i = 0; i < mOomAdj.length; i++) {
buf.putInt((mOomMinFree[i] * 1024)/PAGE_SIZE);
buf.putInt(mOomAdj[i]);
}
writeLmkd(buf, null);
}
// GB: 2048,3072,4096,6144,7168,8192
// HC: 8192,10240,12288,14336,16384,20480
}
mOomAdj的值是固定的,而mOomMinFree的值则是计算出来的,写入到lmkd时,对应的时空闲内存的页数(4K)。mOomMinFree的计算是依赖于总内存的大小、ui的分辨率等因素。另外提供了两个参数 -- minfree_adj 和 minfree_abs来调整lmkd杀进程的激进程度。
而从git blame信息来看,这部分代码都是2011 ~ 2014年的提交,而当时的手机行业内存仍然是很小的,与现在的大内存设备而言,这部分的设计个人认为是并不是十分适用,且现在的应用占用内存越来越大,而Android的原生思想是希望系统运行过程中能有更多的后台进程运行,保证应用切换时的体验速度。对于小内存设备而言,更多的是关注的是在有限的内存能启动更大的应用,或者能保持更多的后台进程,以保证较为良好的用户体验。对于大内存的性能设备而言,在充足的内存情况下,希望保持更多的后台进程,在应用切换过程,减少应用的冷启动,带来更流畅的用户体验。
因此,个人认为对于low_ram设备,可以使用原生的minfree参数,而大内存的性能设备,则根据业务场景自定义minfree参数。
LMK_PROCPRIO
LMK_PROCPRIO是在应用创建、状态变化时,AMS会调用ProcessList.setOomAdj方法向lmkd更新信息。而AMS计算应用adj的流程比较复杂,可另起文档分析。我们这里主要以lmkd的处理流程进行分析:
static void cmd_procprio(LMKD_CTRL_PACKET packet) {
struct proc *procp;
char path[80];
char val[20];
int soft_limit_mult;
struct lmk_procprio params;
bool is_system_server;
struct passwd *pwdrec;
lmkd_pack_get_procprio(packet, ¶ms);
if (params.oomadj < OOM_SCORE_ADJ_MIN ||
params.oomadj > OOM_SCORE_ADJ_MAX) {
ALOGE("Invalid PROCPRIO oomadj argument %d", params.oomadj);
return;
}
/* gid containing AID_READPROC required */
/* CAP_SYS_RESOURCE required */
/* CAP_DAC_OVERRIDE required */
snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);
snprintf(val, sizeof(val), "%d", params.oomadj);
if (!writefilestring(path, val, false)) {
ALOGW("Failed to open %s; errno=%d: process %d might have been killed",
path, errno, params.pid);
/* If this file does not exist the process is dead. */
return;
}
if (use_inkernel_interface) {
return;
}
if (per_app_memcg) {
if (params.oomadj >= 900) {
soft_limit_mult = 0;
} else if (params.oomadj >= 800) {
soft_limit_mult = 0;
} else if (params.oomadj >= 700) {
soft_limit_mult = 0;
} else if (params.oomadj >= 600) {
// Launcher should be perceptible, don't kill it.
params.oomadj = 200;
soft_limit_mult = 1;
} else if (params.oomadj >= 500) {
soft_limit_mult = 0;
} else if (params.oomadj >= 400) {
soft_limit_mult = 0;
} else if (params.oomadj >= 300) {
soft_limit_mult = 1;
} else if (params.oomadj >= 200) {
soft_limit_mult = 8;
} else if (params.oomadj >= 100) {
soft_limit_mult = 10;
} else if (params.oomadj >= 0) {
soft_limit_mult = 20;
} else {
// Persistent processes will have a large
// soft limit 512MB.
soft_limit_mult = 64;
}
snprintf(path, sizeof(path), MEMCG_SYSFS_PATH
"apps/uid_%d/pid_%d/memory.soft_limit_in_bytes",
params.uid, params.pid);
snprintf(val, sizeof(val), "%d", soft_limit_mult * EIGHT_MEGA);
/*
* system_server process has no memcg under /dev/memcg/apps but should be
* registered with lmkd. This is the best way so far to identify it.
*/
is_system_server = (params.oomadj == SYSTEM_ADJ &&
(pwdrec = getpwnam("system")) != NULL &&
params.uid == pwdrec->pw_uid);
writefilestring(path, val, !is_system_server);
}
procp = pid_lookup(params.pid);
if (!procp) {
procp = malloc(sizeof(struct proc));
if (!procp) {
// Oh, the irony. May need to rebuild our state.
return;
}
procp->pid = params.pid;
procp->uid = params.uid;
procp->oomadj = params.oomadj;
proc_insert(procp);
} else {
proc_unslot(procp);
procp->oomadj = params.oomadj;
proc_slot(procp);
}
}
如果设置了per_app_memcg,则会根据进程的adj值限制进程的内存使用,然后查询当前的进程列表中是否存在这个进程,不存在则创建一个新的struct proc对象来记录进程的信息,存在则修改proc的oomadj值,然后通过proc_unslot和proc_slot方法来调整adj链表的lru顺序。
参考资料
reklaw_blog/Android Q LMKD原理简介.md at master · reklaw-tech/reklaw_blog · GitHub
Linux PSI 指标_私房菜的博客-CSDN博客_/proc/pressure/memory
PSI - Pressure Stall Information — The Linux Kernel documentation