lmkd(Low Memory Killer Daemon)是低内存终止守护进程,用来监控运行中android系统内存的状态,通过终止最不必要的进程来应对内存压力较高的问题,使系统以可接受数据lmkd(Low Memory Killer Daemon)是低内存终止守护进程,用来监控运行中android系统内存的状态,通过终止最不必要的进程来应对内存压力较高的问题,使系统以可接受的水de行。
之前Android 使用内核中的 lowmemorykiller 驱动程序来监控系统内存压力,该驱动程序是一种依赖于硬编码值的严格机制。从内核 4.12 开始,lowmemorykiller 驱动程序已从上游内核中移除,用户空间 lmkd会执行内存监控以及进程终止任务。
用户空间 lmkd 可实现与内核中的驱动程序相同的功能,但它使用现有的内核机制检测和估测内存压力。这些机制包括使用内核生成的 vmpressure 事件或压力失速信息 (PSI) 监视器来获取关于内存压力级别的通知。
Android 10 及更高版本支持新的 lmkd 模式,它使用内核压力失速信息 (PSI) 监视器来检测内存压力。上游内核中的 PSI 补丁程序集(反向移植到 4.9 和 4.14 内核)测量由于内存不足而导致任务延迟的时间。由于这些延迟会直接影响用户体验,因此它们代表了确定内存压力严重性的便捷指标。上游内核还包括 PSI 监视器,该监视器允许特权用户空间进程(例如 lmkd)指定这些延迟的阈值,并在突破阈值时从内核订阅事件。
PSI监视器与vmpressure信号
由于 vmpressure 信号(由内核生成,用于内存压力检测并由 lmkd 使用)通常包含大量误报,因此 lmkd 必须执行过滤以确定内存是否真的有压力。这会导致不必要的 lmkd 唤醒并使用额外的计算资源。使用 PSI 监视器可以实现更精确的内存压力检测,并最大限度地减少过滤开销。
如何使用PSI监视器
要使用 PSI 监视器(而不是 vmpressure 事件),需要配置 ro.lmk.use_psi属性。默认值为 true,使得 PSI 监视器成为 lmdk 内存压力检测的默认机制。由于 PSI 监视器需要内核支持,因此内核必须包含 PSI 向后移植补丁程序,并在启用 PSI 支持 (CONFIG_PSI=y) 的情况下进行编译。
源码位置:
system/memory/lmkd
通过lmkd.rc启动
服务启动后,入口在system/memory/lmkd /lmkd.cpp文件的main函数中,主要做了如下几件事:
int main(int argc, char **argv) {
//1、读取prop配置参数
update_props();
ctx = create_android_logger(KILLINFO_LOG_TAG);
//2、初始化init epoll monitors 事件监听
if (!init()) {
if (!use_inkernel_interface) {
//3、锁住内存页
if (mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) {
ALOGW("mlockall failed %s", strerror(errno));
}
struct sched_param param = {
.sched_priority = 1,
};
//4、设置进程调度器
if (sched_setscheduler(0, SCHED_FIFO, ¶m)) {
ALOGW("set SCHED_FIFO failed %s", strerror(errno));
}
}
//5、循环处理事件
mainloop();
}
android_log_destroy(&ctx);
ALOGI("exiting");
return 0;
}
1.update_props中进行了配置数据的初始化,主要是prop的读取
2.注册监听是整个lmkd的关键,我们来看源码(这里我们已常见的PSI为例)
static int init(void) {
//底层监听使用的是linux的epoll机制
epollfd = epoll_create(MAX_EPOLL_EVENTS);
if (use_inkernel_interface) {
ALOGI("Using in-kernel low memory killer interface");
//高版本的Android系统都是使用的Userspace中的lmkd逻辑,这里走不到
}
} else {
//调用init_monitors进行监听的初始化
if (!init_monitors()) {
return -1;
}
}
}
static bool init_monitors() {
/* 调用init_psi_monitors尝试使用psi监视器 */
use_psi_monitors = property_get_bool("ro.lmk.use_psi", true) &&
init_psi_monitors();
/* 没有就分别注册不同等级的vmpressure监视器 */
if (!use_psi_monitors &&
(!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 false;
}
//这里通过log抓取可以判断实际使用的是psi还是vmpressure
if (use_psi_monitors) {
ALOGI("Using psi monitors for memory pressure detection");
} else {
ALOGI("Using vmpressure for memory pressure detection");
}
return true;
}
static bool init_psi_monitors() {
//根据prop判断是否使用新策略
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;
}
//调用init_mp_psi注册监听,分为LOW,MEDIUM,CRITICAL,三个等级对应不同的阈值
if (!init_mp_psi(VMPRESS_LEVEL_LOW, use_new_strategy)) {
return false;
}
if (!init_mp_psi(VMPRESS_LEVEL_MEDIUM, use_new_strategy)) {
destroy_mp_psi(VMPRESS_LEVEL_LOW);
return false;
}
if (!init_mp_psi(VMPRESS_LEVEL_CRITICAL, use_new_strategy)) {
destroy_mp_psi(VMPRESS_LEVEL_MEDIUM);
destroy_mp_psi(VMPRESS_LEVEL_LOW);
return false;
}
return true;
}
static bool init_mp_psi(enum vmpressure_level level, bool use_new_strategy) {
//生成fd,调用open("/proc/pressure/memory")
fd = init_psi_monitor(psi_thresholds[level].stall_type,
psi_thresholds[level].threshold_ms * US_PER_MS,
PSI_WINDOW_SIZE_MS * US_PER_MS);
//设置当前等级对应的回调方法
vmpressure_hinfo[level].handler = use_new_strategy ? mp_event_psi : mp_event_common;
vmpressure_hinfo[level].data = level;
//调用register_psi_monitor注册epoll
if (register_psi_monitor(epollfd, fd, &vmpressure_hinfo[level]) < 0) {
destroy_psi_monitor(fd);
return false;
}
}
int register_psi_monitor(int epollfd, int fd, void* data) {
//进行epoll事件注册,将回调的方法和参数vmpressure_hinfo存在epoll_event.data.ptr中
epev.events = EPOLLPRI;
epev.data.ptr = data;
res = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &epev);
}
走完这套流程我们就向kernel进行了psi事件的订阅,当psi触发预定的阈值是就会触发相应等级的方法的回调,mp_event_psi,这个逻辑是在mainloop中实现的
static void mainloop(void) {
struct event_handler_info* handler_info;
struct polling_params poll_params;
struct timespec curr_tm;
struct epoll_event *evt;
long delay = -1;
while (1) {
//通过epoll_wait进行阻塞,当psi阈值触发时这里会返回
nevents = epoll_wait(epollfd, events, maxevents, -1);
//获取对应psi事件等级的回调对象
for (i = 0, evt = &events[0]; i < nevents; ++i, evt++) {
if (evt->data.ptr) {
//从epoll_event.data.ptr取出存储的回调方法和参数vmpressure_hinfo,调用call_handler
handler_info = (struct event_handler_info*)evt->data.ptr;
call_handler(handler_info, &poll_params, evt->events);
}
}
}
}
static void call_handler(struct event_handler_info* handler_info,
struct polling_params *poll_params, uint32_t events) {
struct timespec curr_tm;
//调用handler方法,对于psi来说这里的方法就是mp_event_psi
handler_info->handler(handler_info->data, events, poll_params);
}
在这个mp_event_psi方法中进行的就是杀进程的逻辑了,首先根据不同配置计算出min_score_adj,然后调用find_and_kill_process进行查找和kill
static void mp_event_psi(int data, uint32_t events, struct polling_params *poll_params) {
int pages_freed = find_and_kill_process(min_score_adj, &ki, &mi, &wi, &curr_tm);
}
/*
* 在给定的 oom_score_adj 级别或以上找到一个要杀死的进程。
* 返回被杀死进程的大小。
*/
static int find_and_kill_process(int min_score_adj, struct kill_info *ki, union meminfo *mi,
struct wakeup_info *wi, struct timespec *tm) {
int i;
int killed_size = 0;
bool lmk_state_change_start = false;
bool choose_heaviest_task = kill_heaviest_task;
for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) {
struct proc *procp;
if (!choose_heaviest_task && i <= PERCEPTIBLE_APP_ADJ) {
/*
* 如果我们必须选择一个可感知的过程,请选择最重的一个
* 希望尽量减少被杀进程的个数。
*/
choose_heaviest_task = true;
}
while (true) {
procp = choose_heaviest_task ?
proc_get_heaviest(i) : proc_adj_lru(i);
if (!procp)
break;
killed_size = kill_one_process(procp, min_score_adj, ki, mi, wi, tm);
if (killed_size) {
break;
}
}
return killed_size;
}
static int kill_one_process(struct proc* procp, int min_oom_score, struct kill_info *ki,
union meminfo *mi, struct wakeup_info *wi, struct timespec *tm) {
if (pidfd < 0) {
start_wait_for_proc_kill(pid);
r = kill(pid, SIGKILL);
} else {
start_wait_for_proc_kill(pidfd);
r = pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
}
//输出相关log
if (ki) {
kill_st.kill_reason = ki->kill_reason;
kill_st.thrashing = ki->thrashing;
kill_st.max_thrashing = ki->max_thrashing;
killinfo_log(procp, min_oom_score, rss_kb, swap_kb, ki->kill_reason, mi, wi, tm);
ALOGI("Kill '%s' (%d), uid %d, oom_score_adj %d to free %" PRId64 "kB rss, %" PRId64
"kB swap; reason: %s", taskname, pid, uid, procp->oomadj, rss_kb, swap_kb,
ki->kill_desc);
} else {
kill_st.kill_reason = NONE;
kill_st.thrashing = 0;
kill_st.max_thrashing = 0;
killinfo_log(procp, min_oom_score, rss_kb, swap_kb, NONE, mi, wi, tm);
ALOGI("Kill '%s' (%d), uid %d, oom_score_adj %d to free %" PRId64 "kB rss, %" PRId64
"kb swap", taskname, pid, uid, procp->oomadj, rss_kb, swap_kb);
}
}
Android 11 之后一般定制lmkd机制是使用一下两个配置项。这些功能在高性能设备和低内存设备上都可使用
ro.lmk.psi_partial_stall_ms
部分 PSI 失速阈值(以毫秒为单位),用于触发内存不足通知。如果设备收到内存压力通知的时间太晚,可以降低此值以在较早的时间触发通知。如果在不必要的情况下触发了内存压力通知,请提高此值以降低设备对噪声的敏感度。默认值:70(高性能机器) 200(低内存机器)
ro.lmk.psi_complete_stall_ms
完全 PSI 失速阈值(以毫秒为单位),用于触发关键内存通知。如果设备收到关键内存压力通知的时间太晚,可以降低该值以在较早的时间触发通知。如果在不必要的情况下触发了关键内存压力通知,可以提高该值以降低设备对噪声的敏感度。默认值:700
https://source.android.google.cn/docs/core/perf/lmkd?hl=en
附:
什么是PSI
当CPU、内存或IO设备被竞争时,工作负载将经历延迟峰值、吞吐量损失,并运行OOM终止的风险。如果没有对这种争用的精确度量,用户就被迫要么安全行事,要么不充分利用其硬件资源,要么孤注一掷,经常遭受由于过度承诺而导致的中断。
PSI(Pressure Stall Information)特性识别并量化这种资源紧张所造成的中断,以及它对复杂工作负载甚至整个系统的时间影响。对资源稀缺造成的生产力损失有一个准确的度量,可以帮助处于根据硬件调整负载或是根据工作负载需求提供硬件的用户。
当PSI 实时聚合上述这些信息,系统可以通过一些技术进行动态管理,例如减负荷、将作业迁移到其他系统或数据中心、战略性暂停或终止低优先级或可重启的批处理作业。
上文设置的psi的触发值可以理解为当内存环境较好时,有大片内存剩余,Memory能很快的分配一块内存给需要的进程,等待时间较少,但是当内存状况比较差,只剩余少量内存,Memory查找剩余内存会比较慢,并且需要回收一些可清理的内存以便腾出足够空间,这样处理的事变多增加耗时,体现在这个值就会变大。