Android用户空间lowmemorykiller
为了提高性能,Android系统中进程的匿名页、文件页按照一定的策略进行缓存,在内存紧张的时候再进行回收。但内存回收并不总是理想的,在一定条件下,为了保证系统的正常运行,会采用更加激进、直接的方式——杀进程,也就是这里要介绍的low memory killer(lmk)。
lmk有内核空间和用户空间两种实现,如果getprop ro.lmk.enable_userspace_lmk为false,同时/sys/module/lowmemorykiller/parameters/minfree节点存在且可被lmkd访问就用内核lmk,否则用后者。本文介绍用户空间lmk。
注册监听
1.1 内存压力监听
用户空间lmk内存压力事件上报方式有vmpressure和psi两种方式,通过属性ro.lmk.use_psi来控制使用哪个。
a) psi
file: system/core/lmkd/lmkd.c
2252 use_psi_monitors = property_get_bool("ro.lmk.use_psi", true) &&
2253 init_psi_monitors();
2120static bool init_psi_monitors() {
//注册3个级别的内存压力事件监听
2121 if (!init_mp_psi(VMPRESS_LEVEL_LOW)) .......
2124 if (!init_mp_psi(VMPRESS_LEVEL_MEDIUM)) ......
2128 if (!init_mp_psi(VMPRESS_LEVEL_CRITICAL)) ......
2134}
注册过程如下:
file: system/core/lmkd/lmkd.c
1) 打开/proc/pressure/memory节点并写入"stall_type threshold_ms PSI_WINDOW_SIZE_MS"。
PSI_WINDOW_SIZE_MS为1000ms,stall_type和threshold_ms分别为:
170static struct psi_threshold psi_thresholds[VMPRESS_LEVEL_COUNT] = {
171 { PSI_SOME, 70 }, /* 70ms out of 1sec for partial stall */
172 { PSI_SOME, 100 }, /* 100ms out of 1sec for partial stall */
173 { PSI_FULL, 70 }, /* 70ms out of 1sec for complete stall */
174};
其中partial stall指的是该时间段内有一个或多个task因为缺少资源而等待,
complete stall指的是该时间段内所有的task都因得不到资源而等待。
psi可以是针对system-wide的,也可以是per-cgroup的。
2) 注册到epoll,回调函数为mp_event_common,通过data可以得到内存压力级别。
2097 vmpressure_hinfo[level].handler = mp_event_common;
//level对应VMPRESS_LEVEL_LOW/MEDIUM/CRITICAL
2098 vmpressure_hinfo[level].data = level;
80 struct epoll_event epev;
82 epev.events = EPOLLPRI;
83 epev.data.ptr = &vmpressure_hinfo;
84 res = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &epev);
b) vmpressure
file: system/core/lmkd/lmkd.c
//同样是注册三个级别的内存压力监听,而且可以看到psi的优先级更高
2255 if (!use_psi_monitors &&
2256 (!init_mp_common(VMPRESS_LEVEL_LOW) ||
2257 !init_mp_common(VMPRESS_LEVEL_MEDIUM) ||
2258 !init_mp_common(VMPRESS_LEVEL_CRITICAL))) {
2260 return -1;
2261 }
注册过程如下:
file: system/core/lmkd/lmkd.c
79#define MEMCG_SYSFS_PATH "/dev/memcg/"
2136static bool init_mp_common(enum vmpressure_level level) {
1) // 向/dev/memcg/cgroup.event_control节点写入"evfd mpfd levelstr"即可注册监听,
其中evfd告诉内核事件发生后通知谁,
mpfd表示监听的是什么事件(这里为memory.pressure_level),
levelstr表示监听内存压力级别,可以是low,medium,critical
2147 mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level",O_RDONLY|O_CLOEXEC);
2153 evctlfd = open(MEMCG_SYSFS_PATH "cgroup.event_control",O_WRONLY|O_CLOEXEC);
2159 evfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
2165 ret = snprintf(buf, sizeof(buf), "%d %d %s", evfd, mpfd, levelstr);
2171 ret = TEMP_FAILURE_RETRY(write(evctlfd, buf, strlen(buf) + 1));
2) //将evfd添加到epoll中,回调函数为mp_event_common,通过data部分可以知道内存压力级别。
2178 epev.events = EPOLLIN;
2179 /* use data to store event level */
2180 vmpressure_hinfo[level_idx].data = level_idx;
2181 vmpressure_hinfo[level_idx].handler = mp_event_common;
2182 epev.data.ptr = (void *)&vmpressure_hinfo[level_idx];
2183 ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, evfd, &epev);
2201}
1.2 客户端socket监听
2203static int init(void) {
2224 ctrl_sock.sock = android_get_control_socket("lmkd");
2230 ret = listen(ctrl_sock.sock, MAX_DATA_CONN);
2236 epev.events = EPOLLIN;
2237 ctrl_sock.handler_info.handler = ctrl_connect_handler;
2238 epev.data.ptr = (void *)&(ctrl_sock.handler_info);
2239 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_sock.sock, &epev) == -1) {
2. 事件通知
2.1 PSI
使用psi_memstall_enter和psi_memstall_leave包裹相关内存操作,实现事件信息统计。这些操作有以下几种:
kernel/msm-4.14/mm/vmscan.c
在try_to_free_mem_cgroup_pages函数中,调用do_try_to_free_pages进行内存回收的时候
kswapd在调用balance_pgdat进行内存回收的时候
kernel/msm-4.14/mm/compaction.c
kcompactd函数在进行内存压缩的时候
kernel/msm-4.14/mm/page_alloc.c
调用__alloc_pages_direct_compact进行内存压缩的时候
调用__perform_reclaim进行内存回收的时候
kernel/msm-4.14/mm/filemap.c
调用wait_on_page_locked,等待文件页就绪的时候
//满足注册条件后开始上报
2.2 vmpressure
通过以下几条路径触发事件的上报:
kernel/msm-4.14/mm/vmscan.c:
//分配物理页时,水位不满足要求,触发内存回收时上报
__alloc_pages ->__alloc_pages_nodemask -> get_page_from_freelist
-> node_reclaim -> __node_reclaim -> shrink_node -> vmpressure
//分配物理页时,进入slow path,进行直接内存回收的过程中上报
__alloc_pages -> __alloc_pages_nodemask -> __alloc_pages_slowpath
-> __alloc_pages_direct_reclaim -> __perform_reclaim -> try_to_free_pages
-> do_try_to_free_pages -> shrink_zones -> shrink_node -> vmpressure
//回收cgroup内存的过程中上报
try_to_free_mem_cgroup_pages -> do_try_to_free_pages -> shrink_zones
-> shrink_node -> vmpressure
//分配物理页时,进入slow path,唤醒kswapd,kswapd在回收内存的过程中上报
kswapd -> balance_pgdat -> kswapd_shrink_node -> shrink_node -> vmpressure
//vmpressure中进行事件上报
其中存在变量 vmpressure_win = SWAP_CLUSTER_MAX*16;
3. 事件处理
3.1 内存压力事件处理
lmk使用minfree和oom_score_adj来决定杀进程的策略。比如如下一组配置, /sys/module/lowmemorykiller/parameters/minfree :15360,19200,23040,26880,34415,43737;/sys/module/lowmemorykiller/parameters/adj : 0,1,2,4,9,12 表示当系统可用内存低于26880个page的时候杀oom_score_adj>=4的进程。具体逻辑在回调函数mp_event_common中,包含两个步骤,首先根据当前系统的可用内存状态定位到minfree的某一档,进而确定相应的min oom_score_adj,细节如下代码所示:
// 其中mi.field是解析/proc/meminfo得到的
1930 other_free = mi.field.nr_free_pages - zi.field.totalreserve_pages;
1931 if (mi.field.nr_file_pages > (mi.field.shmem + mi.field.unevictable +
mi.field.swap_cached)) {
1932 other_file = (mi.field.nr_file_pages - mi.field.shmem -
1933 mi.field.unevictable - mi.field.swap_cached);
1934 } else {
1935 other_file = 0;
1936 }
1937
1938 min_score_adj = OOM_SCORE_ADJ_MAX + 1;
1939 for (i = 0; i < lowmem_targets_size; i++) {
1940 minfree = lowmem_minfree[i];
1941 if (other_free < minfree && other_file < minfree) {
1942 min_score_adj = lowmem_adj[i];
然后就是从OOM_SCORE_ADJ_MAX到min oom_score_adj遍历,从属于当前oom_score_adj的进程中选择一个进行kill,选择策略有两种,如果ro.lmk.kill_heaviest_task属性设为true,则解析/proc/pid/statm选择占用物理内存最大的进程,否则按照LRU的原则选择。
3.2 客户端事件处理
a) minfree和adj
//1. 系统启动过程中,客户端向lmkd发送minfree和adj信息
SystemServer.java : startOtherServices()
-> WindowManagerService.java : displayReady()
-> ActivityTaskManagerService.java : updateConfiguration()
-> ActivityManagerService.java: updateOomLevelsForDisplay()
-> ProcessList.java : applyDisplaySize() -> ProcessList.java :
updateOomLevels() :
601 private void updateOomLevels(int displayWidth, int displayHeight,
boolean write) {
682 ByteBuffer buf = ByteBuffer.allocate(4 * (2 * mOomAdj.length + 1));
683 buf.putInt(LMK_TARGET);
684 for (int i = 0; i < mOomAdj.length; i++) {
685 buf.putInt((mOomMinFree[i] * 1024)/PAGE_SIZE);
686 buf.putInt(mOomAdj[i]);
687 }
689 writeLmkd(buf, null);
//2. lmkd将接收到的信息,对应写入/sys/module/lowmemorykiller/parameters/minfree和
/sys/module/lowmemorykiller/parameters/adj节点
b) app进程修改adj
//1. 不同类型的app进程有着不同的adj,比如FOREGROUND_APP_ADJ为0,SERVICE_ADJ为500等,
// 这样就可以在内存不足的情况下杀掉不重要的进程来缓解内存压力。
// 同一个app进程因其状态的变化,比如前后台切换,其adj也是不断变化的,
// 因此需要调用updateOomAdjLocked来进行更新。
ActivityManagerService.java :
updateOomAdjLocked()
-> OomAdjuster.java : applyOomAdjLocked()
-> ProcessList.java : setOomAdj() :
1185 public static void setOomAdj(int pid, int uid, int amt) {
1194 ByteBuffer buf = ByteBuffer.allocate(4 * 4);
1195 buf.putInt(LMK_PROCPRIO);
1196 buf.putInt(pid);
1197 buf.putInt(uid);
1198 buf.putInt(amt);
1199 writeLmkd(buf, null);
//2. 将adj写入/proc/pid/oom_score_adj节点
system/core/lmkd/lmkd.c
971 case LMK_PROCPRIO:
974 cmd_procprio(packet);
617static void cmd_procprio(LMKD_CTRL_PACKET packet) {
646 snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);
647 snprintf(val, sizeof(val), "%d", params.oomadj);
648 if (!writefilestring(path, val, false)) {
---------------------