Android進程系列第六篇---LowmemoryKiller機制分析(上)

內容預覽

2、概述

前面進程系列已經更新了五篇,本文(基於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原理。

3、LowmemoryKiller機制剖析

總的來講,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定義有所區別。

 
Android M
 
Android M以後

能夠看到M以後的adj數值變大的,爲何呢

 
image.png
 

由於這樣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}

4、總結

這篇文章主要是總結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中的工做流程,下面一篇分解。

相關文章
相關標籤/搜索