Android low_mem_kill
本文基于AndroidQ
属性 | 使用 | 默认 |
---|---|---|
ro.lmk.use_minfree_levels | 使用可用内存和文件缓存阈值来做出进程终止决策 即与内核中的 LMK 驱动程序的功能一致 | false |
ro.lmk.kill_heaviest_task | 终止符合条件的最繁重任务()最佳决策) 与终止符合条件的任何任务(快速决策) | true |
ro.lmk.debug | 启用 lmkd 调试日志。 | false |
ro.lmk.use_psi | 使用 PSI 监视器(而不是 vmpressure 事件) /proc/pressure/ | true |
ro.lmk.medium | 在中等 vmpressure 水平下可被终止的进程的最低 oom_adj 得分。 | 800 已缓存或非必要服务) |
system/core/lmkd/README.md:38: killed at low vmpressure level. Default = 1001
system/core/lmkd/README.md:42: killed at medium vmpressure level. Default = 800
system/core/lmkd/README.md:46: killed at critical vmpressure level. Default = 0
初始化
内核lowmemkill优先级最高,其次是psi初始化init_psi_monitors()
,psi也没有就初始化vmpressure调用 init_mp_common()
/* gid containing AID_SYSTEM required */
#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree"
#define INKERNEL_ADJ_PATH "/sys/module/lowmemorykiller/parameters/adj"
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 {
/* Try to use psi monitor first if kernel has it */
use_psi_monitors = property_get_bool("ro.lmk.use_psi", true) &&
init_psi_monitors();
/* Fall back to 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 -1;
}
if (use_psi_monitors) {
ALOGI("Using psi monitors for memory pressure detection");
} else {
ALOGI("Using vmpressure for memory pressure detection");
}
}
我们是pis,这里只看psi部分,
init_psi_monitor()
函数主要/proc/pressure/memory中写入数据,并返回fd
mp_event_common()
函数是事件处理函数
register_psi_monitor()
函数封装了epoll_ctl,做了EPOLL_CTL_ADD操作
#define PSI_MON_FILE_MEMORY "/proc/pressure/memory"
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;
}
在mainloop()
中回调mp_event_common()
函数,做出相应处理
static void mainloop(void) {
....
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->events & EPOLLERR)
ALOGD("EPOLLERR on event #%d", i);
if (evt->events & EPOLLHUP) {
/* This case was handled in the first pass */
continue;
}
if (evt->data.ptr) {
handler_info = (struct event_handler_info*)evt->data.ptr;
handler_info->handler(handler_info->data, evt->events); // 回调mp_event_common
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);
}
}
}
}
mp_event_common
讲mp_event_common
函数前,先认识两个结构体meminfo
和 zoneinfo
,有利于后面阅读
meminfo_parse
解析/proc/meminfo
nr_free_pages
对应MemFree
nr_file_pages
对应Buffers
+cached
+swap_cached
static int meminfo_parse(union meminfo *mi) {
static struct reread_data file_data = {
.filename = MEMINFO_PATH, // /proc/meminfo
.fd = -1,
};
char buf[PAGE_SIZE];
char *save_ptr;
char *line;
memset(mi, 0, sizeof(union meminfo));
if (reread_file(&file_data, buf, sizeof(buf)) < 0) {
return -1;
}
for (line = strtok_r(buf, "\n", &save_ptr); line;
line = strtok_r(NULL, "\n", &save_ptr)) {
if (!meminfo_parse_line(line, mi)) {
ALOGE("%s parse error", file_data.filename);
return -1;
}
}
/*
/proc/meminfo
Buffers: 27964 kB
Cached: 5084224 kB
SwapCached: 0 kB
*/
mi->field.nr_file_pages = mi->field.cached + mi->field.swap_cached +
mi->field.buffers;
return 0;
}
zoneinfo_parse
解析/proc/zoneinfo
, 可以看出totalreserve_pages
就是high
+ protection
累加
static int zoneinfo_parse(union zoneinfo *zi) {
static struct reread_data file_data = {
.filename = ZONEINFO_PATH, // /proc/zoneinfo
.fd = -1,
};
char buf[PAGE_SIZE];
char *save_ptr;
char *line;
memset(zi, 0, sizeof(union zoneinfo));
if (reread_file(&file_data, buf, sizeof(buf)) < 0) {
return -1;
}
for (line = strtok_r(buf, "\n", &save_ptr); line;
line = strtok_r(NULL, "\n", &save_ptr)) {
if (!zoneinfo_parse_line(line, zi)) {
ALOGE("%s parse error", file_data.filename);
return -1;
}
}
/* zoneinfo_parse_line 还有一段代码,这里就不贴了
if (!strcmp(cp, "protection:")) {
zi->field.totalreserve_pages +=
zoneinfo_parse_protection(ap);
}
所以 totalreserve_pages = high + protection
*/
zi->field.totalreserve_pages += zi->field.high;
return 0;
}
一种是开启use_minfree_levels
,如果满足条件直接return,不进入do_kill了
lowmem_minfree 可以通过属性sys.lmk.minfree_levels
查看, 通过other_free 和 lowmem_minfree 比较,判断是否回收进程
static void mp_event_common(int data, uint32_t events __unused) {
.... 省略
//meminfo_parse 解析 /proc/meminfo,完成mi初始化
//zoneinfo_parse 解析 /proc/zoneinfo,完成zi初始化
if (meminfo_parse(&mi) < 0 || zoneinfo_parse(&zi) < 0) {
ALOGE("Failed to get free memory!");
return;
}
if (use_minfree_levels) {
int i;
//计算剩余内存 meminfo.memfree - (zoneinfo.high + zoneinfo.protected)
other_free = mi.field.nr_free_pages - zi.field.totalreserve_pages;
// 计算meminfo内存页
// (buffers + swap_cached + cached) > (unevictable + shmem + swap_cached)
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++) {
// 详见1.2
// lowmem_minfree 可以通过属性`sys.lmk.minfree_levels`查看
minfree = lowmem_minfree[i];
if (other_free < minfree && other_file < minfree) {
// 触发do_kill
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;
}
// 见1.3
goto do_kill;
}
if (level == VMPRESS_LEVEL_LOW) {
record_low_pressure_levels(&mi); //记录压力位
}
if (level_oomadj[level] > OOM_SCORE_ADJ_MAX) {
/* Do not monitor this pressure level */
return;
}
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;
}
...
}
lowmem_minfree的赋值
static void cmd_target(int ntargets, LMKD_CTRL_PACKET packet)
{
... //省略部分
char minfree_str[PROPERTY_VALUE_MAX];
char *pstr = minfree_str;
char *pend = minfree_str + sizeof(minfree_str);
// packet 是am 通过socket传递
for (i = 0; i < ntargets; i++) {
lmkd_pack_get_target(packet, i, &target);
lowmem_minfree[i] = target.minfree;
lowmem_adj[i] = target.oom_adj_score;
// pstr指向minfree_str的地址
pstr += snprintf(pstr, pend - pstr, "%d:%d,", target.minfree,
target.oom_adj_score);
if (pstr >= pend) {
/* if no more space in the buffer then terminate the loop */
pstr = pend;
break;
}
}
lowmem_targets_size = ntargets;
/* Override the last extra comma */
pstr[-1] = '\0';
property_set("sys.lmk.minfree_levels", minfree_str);
}
开启use_minfree_levels
的情况下是要么跳到do_kill,要么直接return,不会走record_low_pressure_levels
函数,所以这函数就是给第二种情况使用不开启use_minfree_levels
来更新压力位
当前meminfo free减去之前的记录的max_nr_free_pages,波动不超过的10%才更新记录low_pressure_mem.max_nr_free_pages
void record_low_pressure_levels(union meminfo *mi) {
if (low_pressure_mem.min_nr_free_pages == -1 ||
low_pressure_mem.min_nr_free_pages > mi->field.nr_free_pages) {
if (debug_process_killing) {
ALOGI("Low pressure min memory update from %" PRId64 " to %" PRId64,
low_pressure_mem.min_nr_free_pages, mi->field.nr_free_pages);
}
low_pressure_mem.min_nr_free_pages = mi->field.nr_free_pages;
}
/*
* Free memory at low vmpressure events occasionally gets spikes,
* possibly a stale low vmpressure event with memory already
* freed up (no memory pressure should have been reported).
* Ignore large jumps in max_nr_free_pages that would mess up our stats.
*/
// 波动不超过10% 才更新记录max_nr_free_pages
if (low_pressure_mem.max_nr_free_pages == -1 ||
(low_pressure_mem.max_nr_free_pages < mi->field.nr_free_pages &&
mi->field.nr_free_pages - low_pressure_mem.max_nr_free_pages <
low_pressure_mem.max_nr_free_pages * 0.1)) {
if (debug_process_killing) {
ALOGI("Low pressure max memory update from %" PRId64 " to %" PRId64,
low_pressure_mem.max_nr_free_pages, mi->field.nr_free_pages);
}
low_pressure_mem.max_nr_free_pages = mi->field.nr_free_pages;
}
}
当前meminfo free小于压力位low_pressure_mem.max_nr_free_pages
就开始干活了kill进程了
do_kill:
if (low_ram_device) {
/* For Go devices kill only one task */
if (find_and_kill_process(level_oomadj[level]) == 0) {
if (debug_process_killing) {
ALOGI("Nothing to kill");
}
} else {
meminfo_log(&mi);
}
} else {
int pages_freed;
static struct timespec last_report_tm;
static unsigned long report_skip_count = 0;
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];
}
pages_freed = find_and_kill_process(min_score_adj);
总结
如果使用use_minfree_levels
则是计算meminfo.memfree - zoneinfo.high
并且 meminfo.buffers + mem.cached
是否小于minfree_levels
属性值
130|console:/ # getprop |grep lmk
[init.svc.lmkd]: [running]
[ro.boottime.lmkd]: [104017140428644]
[ro.lmk.debug]: [true]
[ro.lmk.kill_heaviest_task]: [true]
[ro.lmk.kill_timeout_ms]: [15]
[ro.lmk.use_minfree_levels]: [true]
[sys.lmk.minfree_levels]: [18432:0,23040:100,27648:200,32256:250,55296:900,80640:950]
以80640:950
为例: (一页等于4K)
内存低于80640 * 4 / 1024 = 315MB
则killoom_adj
大于950
的进程
开启use_minfree_levels
情况下,通过meminfo和zoneinfo计算出other_free,然后比较minfree_levels
,判断是否回收进程
不开启use_minfree_levels
情况下,使用meminfo free比较low_pressure_mem.max_nr_free_pages
,判断是否回收进程,通过record_low_pressure_levels()
更新low_pressure_mem
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库