Android Low Memory Killer

LMK簡介

Linux的內存的使用原則就是不要浪費內存,因此在程序退出時在一段時間內還停留在內存中,這也是咱們下一次打開程序時發現快一些的緣由。可是這樣帶來的壞處就是若是駐留在內存中的程序多了,容易致使OOM(out of memory)的可能。Linux中使用內存監控機制來避免OOM發生。java

OOM Killer

Linux本來存在一個內存監控機制OOM Killer,一旦發現內存使用進入一個臨界值就會自動按照必定的策略來清理。它的核心思想是,android

  • 按照優先級,從低到高來殺死進程,回收內存資源。
  • 一方面要考慮殺死進程給系統帶來的損壞要儘可能小,另外一方面要釋放盡可能多的內存。

具體的作法是OOM Killer會根據一些參考因素,例如進程消耗內存,運行時間,OOM權重等指標計算出一個oom_score分數,這個分數越低,進程被殺死的機率越小,被殺死的時間越晚。數組

LMK

在Android中存在另外一個內存監控機制Low memory killer(LMK)。它實現一個不一樣級別的killer,根據進程的oom_adj 來殺死進程,釋放內存。oom_adj的大小和進程的類型以及進程被調度的次序有關,這個值越小,程序越重要,被殺的可能性越低。其源碼位於,kernel/drivers/staging/android/LowMemoryKiller.c。該文件中定義了兩個數組,用來調整killer行爲。app

static short lowmem_adj[6] = {
        0,
        1,
        6,
        12,
};
static int lowmem_adj_size = 4;
/*static*/ int lowmem_minfree[6] = {
        3 * 512,        /* 6MB */
        2 * 1024,       /* 8MB */
        4 * 1024,       /* 16MB */
        16 * 1024,      /* 64MB */
};

上面定義的兩個數組時一一對應的,其中lowmem_adj表示的是被處理某一個級別的adj的值,lowmem_minfree則表示該級別對應的內存閾值。好比說adj=0的級別,它對應的內存閾值是6M,也就是在可用內存小於6M時,會清除adj大於等於0的全部進程。因此能夠看出adj越小,它被殺死的可能越小。less

LMK參數

Kernel代碼中設定了lowmem_adj和lowmem_minfree的默認值,能夠經過設置下面的文件來修改這兩組值。須要注意的是最多隻能設置6個級別,而且minfree的單位是page。ide

  • /sys/module/lowmemorykiller/parameters/adj
  • /sys/module/lowmemorykiller/parameters/minfree (以頁爲單位,通常是4KB大小)

例如能夠在init.rc中修改其默認值,函數

write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15 
   write /proc/sys/vm/overcommit_memory 1
   write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144

可是在Android運行時,AMS還會經過updateOomLevels()對LMK的參數進行調整。ui

LMK驅動

LMK使用了kernel中的shrinker機制,在驅動加載時,向系統註冊了一個shrinker。當系統空閒內存頁面不足時就會調用該函數。LMK的shrinker實現以下,this

kernel\drivers\staging\android\lowmemorykiller.c
static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
{
    struct task_struct *tsk;
    struct task_struct *selected = NULL;
    int rem = 0;
    int tasksize;
    int i;
    short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
    int minfree = 0;
    int selected_tasksize = 0;
    short selected_oom_score_adj;
    int array_size = ARRAY_SIZE(lowmem_adj);
    int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
    int other_file = global_page_state(NR_FILE_PAGES) -
                        global_page_state(NR_SHMEM);

    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++) {  
        //依次遍歷策略閥值數組,從小到大,根據當前memory free狀況,取觸發adj值
        minfree = lowmem_minfree[i];
        if (other_free < minfree && other_file < minfree) {
            min_score_adj = lowmem_adj[i];
            break;
        }
    }
//這裏獲得的min_score_adj  就是此時內存狀態下 將會kill掉的最小score_adj 
......
for_each_process(tsk) {
......
tasksize = get_mm_rss(p->mm);
......
        if (selected) {
            if (oom_score_adj < selected_oom_score_adj)
                continue;
            if (oom_score_adj == selected_oom_score_adj &&
                tasksize <= selected_tasksize)
                continue;
        }//能夠看到 遍歷一圈process 只爲找到一個 oom_score_adj tasksize 最大的process
        selected = p;
        selected_tasksize = tasksize;
        selected_oom_score_adj = oom_score_adj;
    }
    if (selected) {
        lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \
                "   to free %ldkB on behalf of '%s' (%d) because\n" \
                "   cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \
                "   Free memory is %ldkB above reserved\n",
                 selected->comm, selected->pid,
                 selected_oom_score_adj,
                 selected_tasksize * (long)(PAGE_SIZE / 1024),
                 current->comm, current->pid,
                 other_file * (long)(PAGE_SIZE / 1024),
                 minfree * (long)(PAGE_SIZE / 1024),
                 min_score_adj,
                 other_free * (long)(PAGE_SIZE / 1024));

        trace_lowmem_kill(selected,  other_file, minfree, min_score_adj, other_free);

        lowmem_deathpending_timeout = jiffies + HZ;
        send_sig(SIGKILL, selected, 0);  //發送kill signal 去kill selected的process
        set_tsk_thread_flag(selected, TIF_MEMDIE);
        rem -= selected_tasksize;
    }
}

lowmem_shrink()中,首先根據當前的內存狀態找到一個合適的ADJ值,再根據該ADJ找到tasksize最大的進程將其殺死。在判斷內存狀態時須要注意一下,與minfree作比較的是free和cache,也就是說只有當free和cache都小於minfree時才知足ADJ的條件。rest

LMK設定

LMK的策略是經過驅動來執行的,但其策略的參數是在應用層設定。參數的默認值能夠經過上文講到的配置文件來修改,在Android中參數是由AMS設置的。AMS中定義了ADJ和minfree相關的數組資源。

frameworks/base/services/core/java/com/android/server/am/ProcessList.java
// These are the various interesting memory levels that we will give to
// the OOM killer.  Note that the OOM killer only supports 6 slots, so we
// can't give it a different value for every possible kind of process.
private final int[] mOomAdj = new int[] {
        FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
        BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
};
// These are the low-end OOM level limits.  This is appropriate for an
// HVGA or smaller phone with less than 512MB.  Values are in KB.
private final int[] mOomMinFreeLow = new int[] {
        12288, 18432, 24576,
        36864, 43008, 49152
};
// These are the high-end OOM level limits.  This is appropriate for a
// 1280x800 or larger screen with around 1GB RAM.  Values are in KB.
private final int[] mOomMinFreeHigh = new int[] {
        73728, 92160, 110592,
        129024, 147456, 184320
};
// The actual OOM killer memory levels we are using.
private final int[] mOomMinFree = new int[mOomAdj.length];

策略參數的更新是由updateOomLevels()完成的。最終計算出來的minfree會與mOomMinFreeLow,mOomMinFreeHigh,minfree_adj,minfree_abs等多個參數相關。

frameworks/base/services/core/java/com/android/server/am/ProcessList.java
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
    ……
    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);
    ……
    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
            if (i == 4) high = (high*3)/2;
            else if (i == 5) high = (high*7)/4;
        }
        mOomMinFree[i] = (int)(low + ((high-low)*scale));
    }

    if (minfree_abs >= 0) {
        for (int i=0; i<mOomAdj.length; i++) {
            mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]
                    / mOomMinFree[mOomAdj.length - 1]);
        }
    }

    if (minfree_adj != 0) {
        for (int i=0; i<mOomAdj.length; i++) {
            mOomMinFree[i] += (int)((float)minfree_adj * mOomMinFree[i]
                    / mOomMinFree[mOomAdj.length - 1]);
            if (mOomMinFree[i] < 0) {
                mOomMinFree[i] = 0;
            }
        }
    }
    ……
    }

AMS回收機制

Android用用在各類activity生命週期切換時,會觸發AMS中的回收機制。在AMS的回收過程當中,還會去維護一個ADJ變量,做爲LMK行爲的參考依據。AMS回收機制的入口爲trimApplications(),它在不少地方都有調用。

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void trimApplications() {
        synchronized (this) {
            int i;

            // First remove any unused application processes whose package
            // has been removed.
            for (i=mRemovedProcesses.size()-1; i>=0; i--) {
                final ProcessRecord app = mRemovedProcesses.get(i);
                if (app.activities.size() == 0
                        && app.curReceiver == null && app.services.size() == 0) {
                    Slog.i(
                        TAG, "Exiting empty application process "
                        + app.processName + " ("
                        + (app.thread != null ? app.thread.asBinder() : null)
                        + ")\n");
                    if (app.pid > 0 && app.pid != MY_PID) {
                        app.kill("empty", false);
                    } else {
                        try {
                            app.thread.scheduleExit();
                        } catch (Exception e) {
                            // Ignore exceptions.
                        }
                    }
                    cleanUpApplicationRecordLocked(app, false, true, -1);
                    mRemovedProcesses.remove(i);

                    if (app.persistent) {
                        addAppLocked(app.info, false, null /* ABI override */);
                    }
                }
            }

            // Now update the oom adj for all processes.

            updateOomAdjLocked();

        }
    }

mRemovedProcesses 列表中主要包含了 crash 的進程、5 秒內沒有響應並被用戶選在強制關閉的進程、以及應用開發這調用 killBackgroundProcess 想要殺死的進程。調用 Process.killProcess 將全部此類進程所有殺死。updateOomAdjLocked()計算更新全部process的oomadj。

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void updateOomAdjLocked() {
......
        // First update the OOM adjustment for each of the
        // application processes based on their current state.
        int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
        int nextCachedAdj = curCachedAdj+1;
        int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
        int nextEmptyAdj = curEmptyAdj+2;
        for (int i=N-1; i>=0; i--) {
            ProcessRecord app = mLruProcesses.get(i);
            if (!app.killedByAm && app.thread != null) {
                app.procStateChanged = false;
                computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
    ......
     applyOomAdjLocked(app, TOP_APP, true, now);
......
}

computeOomAdjLocked()計算當前process的ADJ。ADJ值爲-17~15,越小優先級越高。AMS中根據進程的類型定義了ADJ的值,他們的意義分別以下:

ADJ Description
CACHED_APP_MAX_ADJ = 15 當前只運行了不可見的Activity組件的進程
CACHED_APP_MIN_ADJ = 9
SERVICE_B_ADJ = 8 B list of Service。和A list相比,他們對用戶的黏合度要小些
PREVIOUS_APP_ADJ = 7 用戶前一次交互的進程。按照用戶的使用習慣,人們常常會在幾個經常使用的進程間切換,因此這類進程獲得再次運行的機率比較大
HOME_APP_ADJ = 6 Launcher進程,他對用戶的重要性不言而喻
SERVICE_ADJ = 5 當前運行了application service的進程
EAVY_WEIGHT_APP_ADJ = 4 重量級應用程序進程
BACKUP_APP_ADJ = 3 用於承載backup相關操做的進程
PERCEPTIBLE_APP_ADJ = 2 這類進程用戶感受到但看不見,如後臺運行的音樂播放器
VISIBLE_APP_ADJ = 1 前臺可見的Activity進程,若是輕易殺死這類進程將嚴重影響用戶體驗
FOREGROUND_APP_ADJ = 0 當前正在前臺運行的進程,也就是用戶正在交互的那個程序
PERSISTENT_SERVICE_ADJ = -11 與系統進程或Persistent進程綁定的進程,說明該進程很
PERSISTENT_PROC_ADJ = -12 Persistent性質的進程,如telephony
SYSTEM_ADJ = -16 系統進程

這些是系統提供的adj,咱們還能夠改變本身進程的adj值,有如下兩種方式:

  • 寫/proc/pid/oom_adj 值,在init.rc文件中就常常看到下面的語句。它設置進程的adj 值爲-16,屬於系統進程永遠不會被殺死。

    on early-init
    write /proc/1/oom_adj -16
  • 設置persistent屬性。在AndroidManifest.xml文件中設置這個屬性爲true,便可將其adj的值設置爲-12,處於這個級別的進程基本上也不會被殺死,好比電話。

繼續updateOomAdjLocked()流程。經過computeOomAdjLocked()獲得ADJ值後,applyOomAdjLocked()將其通過必定的修整,設置到對應的process。大致流程以下,

  • 必須是非 persistent 進程,即非系統進程;
  • 必須是空進程,即進程中沒有任何 activity 存在。若是殺死存在 Activity 的進程,有可能關閉用戶正在使用的程序,或者使應用程序恢復的時延變大,從而影響用戶體驗;
  • 必須無 broadcast receiver。運行 broadcast receiver 通常都在等待一個事件的發生,用戶並不但願此類程序被系統強制關閉;
  • 進程中 service 的數量必須爲 0。存在 service 的進程頗有可能在爲一個或者多個程序提供某種服務,如 GPS 定位服務。殺死此類進程將使其餘進程沒法正常服務。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    private final boolean applyOomAdjLocked(ProcessRecord app,
            ProcessRecord TOP_APP, boolean doingAll, long now) {
        ......
        if (app.curAdj != app.setAdj) {
            ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(
                TAG, "Set " + app.pid + " " + app.processName +
                " adj " + app.curAdj + ": " + app.adjType);
            app.setAdj = app.curAdj;
        }
        ......
    }

setOomAdj()經過lmkd將ADJ值寫入到proc文件系統對應的節點上,其路徑爲"/proc/<pid>/oom_score_adj"。

frameworks/base/services/core/java/com/android/server/am/ProcessList.java
    /**
     * Set the out-of-memory badness adjustment for a process.
     *
     * @param pid The process identifier to set.
     * @param uid The uid of the app
     * @param amt Adjustment value -- lmkd allows -16 to +15.
     *
     * {@hide}
     */
    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);
        }
    }
相關文章
相關標籤/搜索