从源码角度看LowMemoryKiller
JohnnieLand
7年前
<p>Android系统中,进程的运行状态常常是各个应用开发者关注点的重中之重,进程是否能够存活基本也就意味着应用能否从用户手中获益。之前我写过一篇文章专门讲过进程保活与进程优先级的计算方式,有兴趣的话大家可以再看看。</p> <p>进程一直存活固然很重要,一来应用可以随时随地的在用户手机上做业务操作,二来用户再次启动时速度也会更快些。然而,作为移动设备,应用进程如果只启动而不能被Android系统被动的杀除,那么越来越多的内存被尚在运行的无用进程霸占,系统只会越用越卡。对此,Android基于OOM机制引入了lowmemmorykiller,可以在系统内存紧缺时杀死不必要的低优先级进程,进程的优先级越低就越容易被杀死。</p> <p>对于lowmemorykiller, 我总结为三层:AMS, lmkd 与 lowmemorykiller。其中AMS负责更新各应用的进程优先级与阈值数组,lmkd负责接收AMS传输过来的数据然后写入到sys与proc节点,lowmemorykiller则在内存低于阈值时才会被触发并负责杀死低优先级的进程。</p> <p>这篇文章中,我会先列出一张总结流程图,便于大家掌握lowmemorykiller总体涉及也方便以后查看。随后我再会按照三层从上往下的分析lowmemmorykiller的代码。</p> <p><a href="https://simg.open-open.com/show/5681239ec682fcfa53f46e36d14d804c.jpg" rel="nofollow,noindex">查看大图</a></p> <p><img src="https://simg.open-open.com/show/5681239ec682fcfa53f46e36d14d804c.jpg"></p> <h2>上层:ActivityManagerService更新adj</h2> <h3>AMS.updateConfiguration</h3> <p>frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java</p> <pre> <code class="language-java">public void updateConfiguration(Configuration values){ synchronized (this) { ... if (mWindowManager != null) { mProcessList.applyDisplaySize(mWindowManager); } ... } } </code></pre> <p>updateConfiguration是AMS对外提供的binder接口,调用后可以更新窗口配置</p> <p>frameworks/base/services/core/java/com/android/server/am/ProcessList.java</p> <pre> <code class="language-java">void applyDisplaySize(WindowManagerService wm){ if (!mHaveDisplaySize) { Point p = new Point(); // 获取窗口显示的宽高 wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p); if (p.x != 0 && p.y != 0) { updateOomLevels(p.x, p.y, true); mHaveDisplaySize = true; } } } </code></pre> <pre> <code class="language-java">private void updateOomLevels(int displayWidth, int displayHeight, boolean write){ // mTotalMemMb是指当前设备内存大小,以MB为单位 // 计算内存比例 float scaleMem = ((float)(mTotalMemMb-350))/(700-350); // 计算屏幕大小 int minSize = 480*800; // 384000 int maxSize = 1280*800; // 1024000 230400 870400 .264 float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize); if (false) { Slog.i("XXXXXX", "scaleMem=" + scaleMem); Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth + " dh=" + displayHeight); } // 选择较大的比例,最小为0最大为1 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); if (false) { Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs); } 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 // 64位的机器high值会适当的提高,最终空进程、缓存进程被杀的内存阈值也会被提高 if (i == 4) high = (high*3)/2; else if (i == 5) high = (high*7)/4; } mOomMinFree[i] = (int)(low + ((high-low)*scale)); } ... if (write) { // 通过socket将计算完毕的信息传输给lwkd守护进程 ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1)); buf.putInt(LMK_TARGET); for (int i=0; i<mOomAdj.length; i++) { // 最终写入到minfree文件的数组单位为page的个数 buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE); // 写入到adj文件的数组为固定的adj数组 buf.putInt(mOomAdj[i]); } writeLmkd(buf); SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve)); } // GB: 2048,3072,4096,6144,7168,8192 // HC: 8192,10240,12288,14336,16384,20480 } </code></pre> <table> <thead> <tr> <th>名称</th> <th>值</th> </tr> </thead> <tbody> <tr> <td>FOREGROUND_APP_ADJ</td> <td>0</td> </tr> <tr> <td>VISIBLE_APP_ADJ</td> <td>1</td> </tr> <tr> <td>PERCEPTIBLE_APP_ADJ</td> <td>2</td> </tr> <tr> <td>BACKUP_APP_ADJ</td> <td>3</td> </tr> <tr> <td>CACHED_APP_MIN_ADJ</td> <td>9</td> </tr> <tr> <td>CACHED_APP_MAX_ADJ</td> <td>15</td> </tr> </tbody> </table> <p>这张表为mOomAdj数组,最终会被写入到sys节点的adj文件</p> <h3>AMS.applyOomAdjLocked</h3> <p>frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java</p> <pre> <code class="language-java">private final boolean applyOomAdjLocked(ProcessRecord app,boolean doingAll, long now, long nowElapsed) { ... if (app.curAdj != app.setAdj) { ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj); } ... </code></pre> <p>当计算后的adj和之前的不一样,就会通过setOomAdj更新lmk的adj数值</p> <p>frameworks/base/services/core/java/com/android/server/am/ProcessList.java</p> <pre> <code class="language-java">public static final void setOomAdj(int pid, int uid, int amt){ if (amt == UNKNOWN_ADJ) return; long start = SystemClock.elapsedRealtime(); ByteBuffer buf = ByteBuffer.allocate(4 * 4); buf.putInt(LMK_PROCPRIO); buf.putInt(pid); buf.putInt(uid); buf.putInt(amt); writeLmkd(buf); long now = SystemClock.elapsedRealtime(); if ((now-start) > 250) { Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid + " = " + amt); } } </code></pre> <p>setOomAdj会将AMS已经计算好的adj值通过socket发送到lmkd</p> <h3>AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked</h3> <p>frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java</p> <pre> <code class="language-java">private final void handleAppDiedLocked(ProcessRecord app, boolean restarting, boolean allowRestart) { int pid = app.pid; boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1); if (!kept && !restarting) { removeLruProcessLocked(app); if (pid > 0) { ProcessList.remove(pid); } } ... </code></pre> <p>死亡回调后,如果进程不再重启,那么会先移除lruProcess的记录,随后会调用该进程的ProcessList.remove方法</p> <pre> <code class="language-java">private final boolean cleanUpApplicationRecordLocked(ProcessRecord app, boolean restarting, boolean allowRestart, int index) { ... if (restart && !app.isolated) { // We have components that still need to be running in the // process, so re-launch it. if (index < 0) { ProcessList.remove(app.pid); } addProcessNameLocked(app); startProcessLocked(app, "restart", app.processName); return true; } return false; } </code></pre> <p>通过trim会调用应用的cleanUp机制,如果应用会重启那么会调用该进程的ProcessList.remove方法</p> <p>frameworks/base/services/core/java/com/android/server/am/ProcessList.java</p> <pre> <code class="language-java">public static final void remove(int pid){ ByteBuffer buf = ByteBuffer.allocate(4 * 2); buf.putInt(LMK_PROCREMOVE); buf.putInt(pid); writeLmkd(buf); } </code></pre> <h3>各协议所携带的参数</h3> <table> <thead> <tr> <th>lmk协议</th> <th>所需参数</th> </tr> </thead> <tbody> <tr> <td>LMK_TARGET</td> <td>mOomMinFree, mOomAdj两个数组</td> </tr> <tr> <td>LMK_PROCPRIO</td> <td>pid, uid, amt (adj)</td> </tr> <tr> <td>LMK_PROCREMOVE</td> <td>pid</td> </tr> </tbody> </table> <h3>ProcessList.writeLmkd</h3> <p>frameworks/base/services/core/java/com/android/server/am/ProcessList.java</p> <pre> <code class="language-java">private static void writeLmkd(ByteBuffer buf){ for (int i = 0; i < 3; i++) { if (sLmkdSocket == null) { // 首先尝试打开socket,如果没有打开就睡眠1s,最多会尝试打开3次,如果没有打开就不会再做尝试 if (openLmkdSocket() == false) { try { Thread.sleep(1000); } catch (InterruptedException ie) { } continue; } } try { // 写入数据到对端的socket sLmkdOutputStream.write(buf.array(), 0, buf.position()); return; } catch (IOException ex) { Slog.w(TAG, "Error writing to lowmemorykiller socket"); try { sLmkdSocket.close(); } catch (IOException ex2) { } sLmkdSocket = null; } } } </code></pre> <p>writeLmkd的输入参数是ByteBuffer,用来保存需要传输给lmkd的数据</p> <pre> <code class="language-java">private static boolean openLmkdSocket(){ try { // 尝试连接socket sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET); sLmkdSocket.connect( new LocalSocketAddress("lmkd", LocalSocketAddress.Namespace.RESERVED)); // 获取输出流,用于向对端写入数据 sLmkdOutputStream = sLmkdSocket.getOutputStream(); } catch (IOException ex) { Slog.w(TAG, "lowmemorykiller daemon socket open failed"); sLmkdSocket = null; return false; } return true; } </code></pre> <p>以上的代码便是AMS通过socket来将计算后的adj, minfree阈值写入到lwkd的实现方法</p> <h2>中层:lmkd守护线程,向节点写入数据</h2> <pre> <code class="language-java">int main(int argc __unused, char **argv __unused){ struct sched_param param = { .sched_priority = 1, }; mlockall(MCL_FUTURE); sched_setscheduler(0, SCHED_FIFO, ¶m); // 首先完成初始化操作 if (!init()) // 随后进入主循环,等待AMS发送socket请求 mainloop(); ALOGI("exiting"); return 0; } </code></pre> <pre> <code class="language-java">static int init(void){ // 初始化epoll事件 struct epoll_event epev; int i; int ret; ... // 创建一个epoll并获取fd epollfd = epoll_create(MAX_EPOLL_EVENTS); ctrl_lfd = android_get_control_socket("lmkd"); ret = listen(ctrl_lfd, 1); epev.events = EPOLLIN; // 当监听到socket连接事件后会调用ctrl_connect_handler方法 epev.data.ptr = (void *)ctrl_connect_handler; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1) { ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno); return -1; } maxevents++; use_inkernel_interface = !access(INKERNEL_MINFREE_PATH, W_OK); if (use_inkernel_interface) { ALOGI("Using in-kernel low memory killer interface"); } else { ret = init_mp(MEMPRESSURE_WATCH_LEVEL, (void *)∓_event); if (ret) ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer"); } for (i = 0; i <= ADJTOSLOT(OOM_ADJUST_MAX); i++) { procadjslot_list[i].next = &procadjslot_list[i]; procadjslot_list[i].prev = &procadjslot_list[i]; } return 0; } </code></pre> <pre> <code class="language-java">static void ctrl_connect_handler(uint32_t events __unused){ struct sockaddr addr; socklen_t alen; struct epoll_event epev; alen = sizeof(addr); // 作为服务端接受socket信息 ctrl_dfd = accept(ctrl_lfd, &addr, &alen); ALOGI("ActivityManager connected"); maxevents++; epev.events = EPOLLIN; // 当接收到socket消息后会调用ctrl_data_handler方法 epev.data.ptr = (void *)ctrl_data_handler; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_dfd, &epev) == -1) { ALOGE("epoll_ctl for data connection socket failed; errno=%d", errno); ctrl_data_close(); return; } } </code></pre> <pre> <code class="language-java">static void ctrl_data_handler(uint32_t events){ // socket消息类型为断开 if (events & EPOLLHUP) { ALOGI("ActivityManager disconnected"); if (!ctrl_dfd_reopened) ctrl_data_close(); // socket消息类型为接受 } else if (events & EPOLLIN) { ctrl_command_handler(); } } </code></pre> <pre> <code class="language-java">static void ctrl_command_handler(void){ int ibuf[CTRL_PACKET_MAX / sizeof(int)]; int len; int cmd = -1; int nargs; int targets; // 读取socket管道信息 len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX); // 获取buffer中的命令协议 cmd = ntohl(ibuf[0]); // 使用协议命令分流后,首先会解析出数据然后调用各自的方法来处理具体操作 switch(cmd) { case LMK_TARGET: targets = nargs / 2; if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj)) goto wronglen; cmd_target(targets, &ibuf[1]); break; case LMK_PROCPRIO: if (nargs != 3) goto wronglen; cmd_procprio(ntohl(ibuf[1]), ntohl(ibuf[2]), ntohl(ibuf[3])); break; case LMK_PROCREMOVE: if (nargs != 1) goto wronglen; cmd_procremove(ntohl(ibuf[1])); break; default: ALOGE("Received unknown command code %d", cmd); return; } return; wronglen: ALOGE("Wrong control socket read length cmd=%d len=%d", cmd, len); } </code></pre> <h3>LMK_TARGET -> cmd_target</h3> <pre> <code class="language-java">static void cmd_target(int ntargets, int *params){ int i; // 取出minfree, adj数组 for (i = 0; i < ntargets; i++) { lowmem_minfree[i] = ntohl(*params++); lowmem_adj[i] = ntohl(*params++); } lowmem_targets_size = ntargets; if (use_inkernel_interface) { char minfreestr[128]; char killpriostr[128]; // 将数组信息解析为字符串 for (i = 0; i < lowmem_targets_size; i++) { char val[40]; if (i) { strlcat(minfreestr, ",", sizeof(minfreestr)); strlcat(killpriostr, ",", sizeof(killpriostr)); } snprintf(val, sizeof(val), "%d", lowmem_minfree[i]); strlcat(minfreestr, val, sizeof(minfreestr)); snprintf(val, sizeof(val), "%d", lowmem_adj[i]); strlcat(killpriostr, val, sizeof(killpriostr)); } // 随后写入到固定的文件节点 writefilestring(INKERNEL_MINFREE_PATH, minfreestr); writefilestring(INKERNEL_ADJ_PATH, killpriostr); } } </code></pre> <p>LMK_TARGET的作用在于更新了minifree与adj的阈值</p> <h3>LMK_PROCPRIO -> cmd_procprio</h3> <pre> <code class="language-java">static void cmd_procprio(int pid, int uid, int oomadj){ struct proc *procp; char path[80]; char val[20]; snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid); // 将adj转换为oom_score_adj,规则是除了MAX之外,公式:adj * 1000 / -17 snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj)); writefilestring(path, val); if (use_inkernel_interface) return; // 查找进程 procp = pid_lookup(pid); // 如果不存在,则创建一个新的节点,为它分配内存然后写入数据 if (!procp) { procp = malloc(sizeof(struct proc)); if (!procp) { // Oh, the irony. May need to rebuild our state. return; } procp->pid = pid; procp->uid = uid; procp->oomadj = oomadj; proc_insert(procp); } else { // 如果存在则改变oomadj的数据 proc_unslot(procp); procp->oomadj = oomadj; proc_slot(procp); } } </code></pre> <p>LMK_PROCPRIO的作用在于更新了oom_score_adj的数据</p> <h3>LMK_PROCREMOVE -> cmd_procremove</h3> <pre> <code class="language-java">static void cmd_procremove(int pid){ if (use_inkernel_interface) return; pid_remove(pid); kill_lasttime = 0; } </code></pre> <p>LMK_PROCREMOVE的作用只是移除了进程proc节点</p> <h2>底层:lowmemorykiller,低内存时触发进程查杀</h2> <p>linux/blob/master/drivers/staging/android/lowmemorykiller.c</p> <pre> <code class="language-java">static int __initlowmem_init(void) { register_shrinker(&lowmem_shrinker); return 0; } static struct shrinker lowmem_shrinker = { .shrink = lowmem_shrink, .seeks = DEFAULT_SEEKS * 16 }; </code></pre> <p>初始化lowmemorykiller,注册cache shrinker,当空闲内存页面不足时,内核进程kswpd会调用注册的shrink回调函数来回收页面,在Android上,lowmemorykiller,是通过minifree与adj数组来确认需要杀死的低优先级进程,以此来释放内存</p> <p>例如,一组minifree与adj的阈值数据如下:</p> <p>minifree:</p> <p>15360,19200,23040,26880,34415,43737</p> <p>adj:</p> <p>0,58,117,176,529,1000</p> <p>当内存低于43737x4KB时,会杀死adj大于1000的进程,当内存低于34415x4KB时,会杀死adj大于529的进程</p> <pre> <code class="language-java">static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask) { struct task_struct *p; struct task_struct *selected = NULL; int rem = 0; int tasksize; int i; int min_adj = OOM_ADJUST_MAX + 1; int selected_tasksize = 0; int selected_oom_adj; int array_size = ARRAY_SIZE(lowmem_adj); // 获取当前可用内存页数 int other_free = global_page_state(NR_FREE_PAGES); int other_file = global_page_state(NR_FILE_PAGES); if (lowmem_adj_size < array_size) array_size = lowmem_adj_size; if (lowmem_minfree_size < array_size) array_size = lowmem_minfree_size; for (i = 0; i < array_size; i++) { if (other_free < lowmem_minfree[i] && other_file < lowmem_minfree[i]) { // 获取需要被清除的adj阈值 min_adj = lowmem_adj[i]; break; } } rem = global_page_state(NR_ACTIVE_ANON) + global_page_state(NR_ACTIVE_FILE) + global_page_state(NR_INACTIVE_ANON) + global_page_state(NR_INACTIVE_FILE); selected_oom_adj = min_adj; read_lock(&tasklist_lock); // 遍历每个进程 for_each_process(p) { struct mm_struct *mm; int oom_adj; task_lock(p); mm = p->mm; if (!mm) { task_unlock(p); continue; } oom_adj = mm->oom_adj; if (oom_adj < min_adj) { task_unlock(p); continue; } tasksize = get_mm_rss(mm); task_unlock(p); if (tasksize <= 0) continue; if (selected) { // 当adj小于阈值时,不杀进程 if (oom_adj < selected_oom_adj) continue; if (oom_adj == selected_oom_adj && tasksize <= selected_tasksize) continue; } selected = p; selected_tasksize = tasksize; selected_oom_adj = oom_adj; lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n", p->pid, p->comm, oom_adj, tasksize); } // 杀死所选的低优先级进程 if (selected) { lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n", selected->pid, selected->comm, selected_oom_adj, selected_tasksize); force_sig(SIGKILL, selected); rem -= selected_tasksize; } lowmem_print(4, "lowmem_shrink %d, %x, return %d\n", nr_to_scan, gfp_mask, rem); read_unlock(&tasklist_lock); return rem; } </code></pre> <p>这段lowmemorykiller内核的代码很简单,思路就是遍历所有进程并比较进程优先级与优先级阈值,并杀死优先级低于阈值的进程</p> <h2>总结</h2> <table> <thead> <tr> <th>ActivitiyManagerService</th> <th>ProcessList</th> <th>lmk_cmd</th> <th>ctrl_command_handler</th> <th>作用</th> </tr> </thead> <tbody> <tr> <td>updateConfiguration</td> <td>updateOomLevels</td> <td>LMK_TARGET</td> <td>cmd_target</td> <td>更新minifree与adj阈值到sys节点</td> </tr> <tr> <td>applyOomAdjLocked</td> <td>setOomAdj</td> <td>LMK_PROCPRIO</td> <td>cmd_procprio</td> <td>更新oom_score_adj到proc节点</td> </tr> <tr> <td>cleanUpApplicationRecordLocked<br> handleAppDiedLocked</td> <td>remove</td> <td>LMK_PROCREMOVE</td> <td>cmd_procremove</td> <td>无</td> </tr> </tbody> </table> <p>简而言之,AMS通过在framework层调用三类不同的方法来向lmkd守护进程传输进程与内存阈值数据,lmkd负责向节点写入进程优先级数据,lowmemorykiller.c则运行在内核中,当设备的内存低于某个阈值时就会触发相应的策略杀死较低优先级的进程。</p> <p> </p> <p>来自:http://navyblue.top/2018/01/21/从源码角度看LowMemoryKiller/</p> <p> </p>