前面进程系列已经更新了五篇,本文(基于Android O源码),梳理LMK杀进程机制上篇,主要总结AMS和LowmemoryKiller通讯的方式以及LowmemoryKiller的原理。
Android进程系列第一篇---进程基础
Android进程系列第二篇---Zygote进程的建立流程
Android进程系列第三篇---SystemServer进程的建立流程
Android进程系列第四篇---SystemServer进程的启动流程
Android进程系列第五篇---应用进程的建立流程java
一、为何引入LowmemoryKiller?
进程的启动分冷启动和热启动,当用户退出某一个进程的时候,并不会真正的将进程退出,而是将这个进程放到后台,以便下次启动的时候能够立刻启动起来,这个过程名为热启动,这也是Android的设计理念之一。这个机制会带来一个问题,每一个进程都有本身独立的内存地址空间,随着应用打开数量的增多,系统已使用的内存愈来愈大,就颇有可能致使系统内存不足。为了解决这个问题,系统引入LowmemoryKiller(简称lmk)管理全部进程,根据必定策略来kill某个进程并释放占用的内存,保证系统的正常运行。android
二、 LMK基本原理?
全部应用进程都是从zygote孵化出来的,记录在AMS中mLruProcesses列表中,由AMS进行统一管理,AMS中会根据进程的状态更新进程对应的oom_adj值,这个值会经过文件传递到kernel中去,kernel有个低内存回收机制,在内存达到必定阀值时会触发清理oom_adj值高的进程腾出更多的内存空间,这就是Lowmemorykiller工做原理。算法
三、LMK基本实现方案
因此根据不一样手机的配置,就有对应的杀进程标准,这个标准用minfree和adj两个文件来定义:shell
/sys/module/lowmemorykiller/parameters/minfree:里面是以","分割的一组数,每一个数字表明一个内存级别。
/sys/module/lowmemorykiller/parameters/adj:对应上面的一组数,每一个数组表明一个进程优先级级别数组
用小米note3举例:app
wangjing@wangjing-OptiPlex-7050:~$ adb root restarting adbd as root wangjing@wangjing-OptiPlex-7050:~$ adb shell jason:/ # cat /sys/module/lowmemorykiller/parameters/minfree 18432,23040,27648,32256,55296,80640 jason:/ # jason:/ # cat /sys/module/lowmemorykiller/parameters/adj 0,100,200,300,900,906 jason:/ #
minfree中数值的单位是内存中的页面数量,通常状况下一个页面是4KB,当内存低于80640的时候,系统会杀死adjj>=906级别的进程,当内存低于55296的时候,系统会杀死adj>=900级别的进程。不一样配置的机器这两个文件会有区别,我把minfree文件中的值理解成五个水位线,而adj这个文件中的值与minfree文件中的数值一一对应,意味着到达什么样的水位线,杀死对应数值的进程。socket
对于应用进程来讲,也须要有自身的adj,由AMS负责更新。定义在oom_adj和oom_score_adj文件中:
/proc/pid/oom_adj:表明当前进程的优先级,这个优先级是kernel中的优先级。
/proc/pid/oom_score_adj:这个是AMS上层的优先级,与ProcessList中的优先级对应函数
好比查看一下头条进程的adj值,以下:fetch
jason:/ # ps -ef |grep news u0_a159 7113 1119 8 15:21:12 ? 00:00:11 com.ss.android.article.news u0_a159 7188 1119 0 15:21:12 ? 00:00:00 com.ss.android.article.news:ad u0_a159 7299 1119 1 15:21:16 ? 00:00:02 com.ss.android.article.news:push u0_a159 7384 1119 1 15:21:17 ? 00:00:00 com.ss.android.article.news:pushservice root 7838 6429 3 15:23:35 pts/0 00:00:00 grep news jason:/ # cat proc/7113/oom_adj 0 jason:/ # cat proc/7113/oom_score_adj 0 jason:/ # cat proc/7113/oom_adj 12 jason:/ # cat proc/7113/oom_score_adj 700 jason:/ #
当头条位于前台进程的时候oom_adj值为0,oom_score_adj值也是0,当退出成为后台进程的时候,oom_adj值为12,oom_score_adj值是700。ui
其实oom_adj与oom_score_adj这两个值是有换算关系的。
kernel/drivers/staging/android/lowmemorykiller.c 271static short lowmem_oom_adj_to_oom_score_adj(short oom_adj) 272{ 273 if (oom_adj == OOM_ADJUST_MAX) 274 return OOM_SCORE_ADJ_MAX; 275 else 276 return (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE; 277}
其中OOM_ADJUST_MAX=-15,OOM_SCORE_ADJ_MAX=1000,OOM_DISABLE=-17,那么换算就是:oom_score_adj=12*1000/17=700。高版本的内核都不在使用oom_adj,而是用oom_score_adj,oom_score_adj是一个向后兼容。
综上总结一下LMK的基本原理,以下
用户在启动一个进程以后,一般伴随着启动一个Activity游览页面或者一个Service播放音乐等等,这个时候此进程的adj被AMS提升,LMK就不会杀死这个进程,当这个进程要作的事情作完了,退出后台了,此进程的adj很快又被AMS下降。当须要杀死一个进程释放内存时,通常先根据当前手机剩余内存的状态,在minfree节点中找到当前等级,再根据这个等级去adj节点中找到这个等级应该杀掉的进程的优先级, 以后遍历全部进程并比较进程优先级adj与优先级阈值,并杀死优先级低于阈值的进程,达到释放内存的目的。本文不讨论adj的计算,只讨论lmk原理。
总的来讲,Framework层经过调整adj的值和阈值数组,输送给kernel中的lmk,为lmk提供杀进程的原材料,由于用户空间和内核空间相互隔离,就采用了文件节点进行通信,用socket将adj的值与阈值数组传给lmkd(5.0以后不在由AMS直接与lmk通讯,引入lmkd守护进程),lmkd将这些值写到内核节点中。lmk经过读取这些节点,实现进程的kill,因此整个lmk机制大概可分红三层。
3.一、Framework层
AMS中与adj调整的有三个核心的方法,以下
AMS.updateConfiguration:更新窗口配置,这个过程当中,分别向/sys/module/lowmemorykiller/parameters目录下的minfree和adj节点写入相应数值;
AMS.applyOomAdjLocked:应用adj,当须要杀掉目标进程则返回false;不然返回true,这个过程当中,调用setOomAdj(),向/proc/pid/oom_score_adj写入oom_adj 后直接返回;
AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked:进程死亡后,调用remove(),直接返回;
3.1.一、 AMS.updateConfiguration
public boolean updateConfiguration(Configuration values) { synchronized(this) { if (values == null && mWindowManager != null) { // sentinel: fetch the current configuration from the window manager values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY); } if (mWindowManager != null) { // Update OOM levels based on display size. mProcessList.applyDisplaySize(mWindowManager); } ..... } }
mProcessList是ProcessList对象,调用applyDisplaySize方法,基于屏幕尺寸,更新LMK的水位线
/frameworks/base/services/core/java/com/android/server/am/ProcessList.java 198 void applyDisplaySize(WindowManagerService wm) { 199 if (!mHaveDisplaySize) { 200 Point p = new Point(); 201 // TODO(multi-display): Compute based on sum of all connected displays' resolutions. 202 wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p); 203 if (p.x != 0 && p.y != 0) { //传入屏幕的尺寸 204 updateOomLevels(p.x, p.y, true); 205 mHaveDisplaySize = true; 206 } 207 } 208 }
传入屏幕的尺寸更新水位线,逻辑很简单
210 private void updateOomLevels(int displayWidth, int displayHeight, boolean write) { 211 // Scale buckets from avail memory: at 300MB we use the lowest values to 212 // 700MB or more for the top values. 213 float scaleMem = ((float)(mTotalMemMb-350))/(700-350); 214 215 //根据屏幕大小计算出scale 216 int minSize = 480*800; // 384000 217 int maxSize = 1280*800; // 1024000 230400 870400 .264 218 float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize); //google代码就是这么写的,表示很差评价了 219 if (false) { 220 Slog.i("XXXXXX", "scaleMem=" + scaleMem); 221 Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth 222 + " dh=" + displayHeight); 223 } 224 225 float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp; 226 if (scale < 0) scale = 0; 227 else if (scale > 1) scale = 1; 228 int minfree_adj = Resources.getSystem().getInteger( 229 com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust); 230 int minfree_abs = Resources.getSystem().getInteger( 231 com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute); 232 if (false) { 233 Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs); 234 } 235 236 final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0; 237 //经过下面的运算,将mOomMinFreeLow和mOomMinFreeHigh通过运算 // 最后得出的 值存入mOomMinFree中,而如何计算这个值,是根据当前屏幕的分辨率和内存大小来 238 for (int i=0; i<mOomAdj.length; i++) { 239 int low = mOomMinFreeLow[i]; 240 int high = mOomMinFreeHigh[i]; 241 if (is64bit) { 242 // 64-bit机器会high增大 243 if (i == 4) high = (high*3)/2; 244 else if (i == 5) high = (high*7)/4; 245 } 246 mOomMinFree[i] = (int)(low + ((high-low)*scale)); 247 } ....... 287 288 if (write) { 289 ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1)); 290 buf.putInt(LMK_TARGET); 291 for (int i=0; i<mOomAdj.length; i++) { 292 buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);//五个水位线 293 buf.putInt(mOomAdj[i]);//与上面水位线对应的五个adj数值 294 } 295 //将AMS已经计算好的值经过socket发送到lmkd 296 writeLmkd(buf); 297 SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve)); 298 } 299 // GB: 2048,3072,4096,6144,7168,8192 300 // HC: 8192,10240,12288,14336,16384,20480 301 }
这里携带的命令协议是LMK_TARGET,它对应到kernel里面执行的函数是cmd_target,要求kernel干的事情就是更新两面两个文件
/sys/module/lowmemorykiller/parameters/minfree /sys/module/lowmemorykiller/parameters/adj
这两个文件的做用我已经在开头说过了,我把minfree文件中的值理解成五个水位线,而adj这个文件中的值与minfree文件中的数值一一对应,意味着到达什么样的水位线,杀死对应数值的进程。而AMS里面就是经过调用applyDisplaySize方法,基于屏幕尺寸以及机器的CPU位数,更新LMK的水位线的。
3.1.二、 AMS.applyOomAdjLocked
在看applyOomAdjLocked方法,这个方法的做用是应用adj,这个过程当中,调用setOomAdj(),向/proc/pid/oom_score_adj写入oom_adj 后直接返回;系统中更新adj的操做很频繁,四大组件的生命周期都会影响着adj的值。而更新adj通常由applyOomAdjLocked完成。在看代码以前,在回温一下AMS中adj的定义,Android M与以后的adj定义有所区别。
能够看到M以后的adj数值变大的,为何呢
由于这样adj能够更加细化了,即便相同进程,不一样任务栈的adj也能够不同。从Android P开始,进一步细化ADJ级别,增长了VISIBLE_APP_LAYER_MAX(99),是指VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)之间有99个槽,则可见级别ADJ的取值范围为[100,199]。 算法会根据其所在task的mLayerRank来调整其ADJ,100加上mLayerRank就等于目标ADJ,layer越大,则ADJ越小。
再次科普一下,咱们能够用下面的两个办法随时查看adj的值 。
1、cat proc/<pid>/oom_score_adj 2、adb shell dumpsys activity o/p
好了,如今来看AMS调用applyOomAdjLocked更新adj。
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 22000 private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now, long nowElapsed) { ......... 22009 22010 if (app.curAdj != app.setAdj) { //以前的adj不等于计算的adj,须要更新 22011 ProcessList.setOomAdj(app.pid, app.uid, app.curAdj); 22012 if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) { 22013 String msg = "Set " + app.pid + " " + app.processName + " adj " 22014 + app.curAdj + ": " + app.adjType; 22015 reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); 22016 } 22017 app.setAdj = app.curAdj; 22018 app.verifiedAdj = ProcessList.INVALID_ADJ; 22019 } ......... 22020 }
/frameworks/base/services/core/java/com/android/server/am/ProcessList.java 630 public static final void setOomAdj(int pid, int uid, int amt) { 631 if (amt == UNKNOWN_ADJ) 632 return; 633 634 long start = SystemClock.elapsedRealtime(); 635 ByteBuffer buf = ByteBuffer.allocate(4 * 4); 636 buf.putInt(LMK_PROCPRIO); 637 buf.putInt(pid); 638 buf.putInt(uid); 639 buf.putInt(amt); //将AMS已经计算好的adj值经过socket发送到lmkd 640 writeLmkd(buf); 641 long now = SystemClock.elapsedRealtime(); 642 if ((now-start) > 250) { 643 Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid 644 + " = " + amt); 645 } 646 }
这里携带的命令协议是LMK_PROCPRIO,对应kernel里面cmd_procprio函数,要求kernel干的事情是---把AMS发送过来的adj值更新到下面的文件中去。这样内存紧张的时候,LMK就会遍历内核中进程列表,杀死相应adj的进程了。
3.1.三、 AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked
进程死掉后,会调用该进程的ProcessList.remove方法,也会经过Socket通知lmkd更新adj。
/frameworks/base/services/core/java/com/android/server/am/ProcessList.java 651 public static final void remove(int pid) { 652 ByteBuffer buf = ByteBuffer.allocate(4 * 2); 653 buf.putInt(LMK_PROCREMOVE); 654 buf.putInt(pid); 655 writeLmkd(buf); 656 }
这里携带的命令协议是LMK_PROCREMOVE,对应kernel里面的cmd_procremove函数,要求kernel干的事情是,当进程死亡了,删除/proc/<pid>下面的文件。
上面三大方法最后都是经过writeLmkd与lmkd通讯,如今看看writeLmkd中怎么和lmkd通讯的,首先须要打开与lmkd通讯的socket,lmkd建立名称为lmkd的socket,节点位于/dev/socket/lmkd
658 private static boolean openLmkdSocket() { 659 try { 660 sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET); 661 sLmkdSocket.connect( 662 new LocalSocketAddress("lmkd", 663 LocalSocketAddress.Namespace.RESERVED)); 664 sLmkdOutputStream = sLmkdSocket.getOutputStream(); 665 } catch (IOException ex) { 666 Slog.w(TAG, "lowmemorykiller daemon socket open failed"); 667 sLmkdSocket = null; 668 return false; 669 } 670 671 return true; 672 }
当sLmkdSocket建立以后,就用它来发送数据到对端(lmkd)
674 private static void writeLmkd(ByteBuffer buf) { 675 //尝试三次 676 for (int i = 0; i < 3; i++) { 677 if (sLmkdSocket == null) { 678 if (openLmkdSocket() == false) { 679 try { 680 Thread.sleep(1000); 681 } catch (InterruptedException ie) { 682 } 683 continue; 684 } 685 } 686 687 try { 688 sLmkdOutputStream.write(buf.array(), 0, buf.position()); 689 return; 690 } catch (IOException ex) { 691 Slog.w(TAG, "Error writing to lowmemorykiller socket"); 692 693 try { 694 sLmkdSocket.close(); 695 } catch (IOException ex2) { 696 } 697 698 sLmkdSocket = null; 699 } 700 } 701 } 702}
这篇文章主要是总结lmk的初步的工做原理,如何为系统的资源保驾护航。核心原理就是Framework层经过调整adj的值和阈值数组,输送给kernel中的lmk,为lmk提供杀进程的原材料。经过前面的分析AMS中给lmkd发送数据原材料有三个入口,携带的命令协议也有三种,以下。
功能 | AMS对应方法 | 命令 | 内核对应函数 | |
---|---|---|---|---|
LMK_PROCPRIO | PL.setOomAdj() | 设置指定进程的优先级,也就是oom_score_adj | cmd_procprio | |
LMK_TARGET | PL.updateOomLevels() | 更新/sys/module/lowmemorykiller/parameters/中的minfree以及adj | cmd_target | |
LMK_PROCREMOVE | PL.remove() | 移除进程 | cmd_procremove |
关于kenel中的工做流程,下面一篇分解。