前面進程系列已經更新了五篇,本文(基於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中的工做流程,下面一篇分解。