Android Low memory killer

2018/07/31 AOSP android-8.1.0_r31

Android盡可能多的緩存進(jìn)程,當(dāng)用戶下次再去使用該進(jìn)程時,就會直接使用緩存的進(jìn)程,從而避免了開啟進(jìn)程這樣的消耗,提高響應(yīng)速度。

但是隨著Android緩存的進(jìn)程越來越多,系統(tǒng)內(nèi)存就會越來越少。所以Android又會去殺掉一些緩存的進(jìn)程來釋放一些內(nèi)存, 而這就是low memory killer.

可以看出,Android緩存的進(jìn)程數(shù)量,與觸發(fā)Low memory killer是相關(guān)的,
當(dāng)緩存/empty的進(jìn)程數(shù)量,就會觸發(fā)android framework層面的低殺
或使用的內(nèi)存超過一個閾值時,就會觸發(fā)lmkd或kernel里的low memory killer.

本文主要分析怎樣計算一個進(jìn)程的oom adj, 然后android framework對緩存的進(jìn)程進(jìn)行低殺,接著會去lmkd以及kernel里繼續(xù)分析low memory killer.

一 計算 oom adj

Android Framework計算一個進(jìn)程的 oom adj 是在computeOomAdjLocked函數(shù)中進(jìn)行的,由于該函數(shù)很龐大,所以就不貼代碼了。大體把流程圖畫出來了,

computeOomAdjLocked
service_promotion
provider_promotion
system_adj

上圖是系統(tǒng)定義的adj level, 其中

  • system和persistent進(jìn)程
    這類進(jìn)程的 adj < 0, computeOomAdjLocked并不會調(diào)整這類進(jìn)程的adj, 也就意味著,這些進(jìn)程很重要,不到萬不得已的情況下是不會kill這些進(jìn)程的

  • 前端進(jìn)程
    這類進(jìn)程包括TOP APP, 也包括那些正在接收廣播,正在進(jìn)行service callback的進(jìn)程,以及在run instrumentation的進(jìn)程.

  • Visible 進(jìn)程
    visible進(jìn)程并不一定是前端進(jìn)程, 它是指app中有些activity是可見的,比如被TOP app覆蓋后,依然可見的那些進(jìn)程

  • 用戶可感知的進(jìn)程
    這些進(jìn)程包括那些 activity 狀態(tài)為 PAUSING/PAUSED/STOPPING的進(jìn)程, 以及有 overlay、前端service的進(jìn)程.
    以及被設(shè)置成forcingToImportant的進(jìn)程,這類進(jìn)程主要正在顯示Toast的進(jìn)程

Toast.png
  • previous進(jìn)程
    這里特別注意,previous進(jìn)程把HOME進(jìn)程排除了,也就是如果從launcher啟動一個APP, 那么照理說previous進(jìn)程應(yīng)該是HOME進(jìn)程才對,但是AMS在更新previous進(jìn)程時,將HOME進(jìn)程排除在外了,所以HOME進(jìn)程永遠(yuǎn)不會是previous進(jìn)程
void updatePreviousProcessLocked(ActivityRecord r) {
        // ...
        // Now set this one as the previous process, only if that really
        // makes sense to. 排除了home進(jìn)程
        if (r.app != null && fgApp != null && r.app != fgApp
                && r.lastVisibleTime > mService.mPreviousProcessVisibleTime
                && r.app != mService.mHomeProcess) {
            mService.mPreviousProcess = r.app;
            mService.mPreviousProcessVisibleTime = r.lastVisibleTime;
        }
    }

其它的進(jìn)程就參見上面的表格吧!

另外,一個進(jìn)程里的 services 或 provider是有可能會提升該進(jìn)程的oom adj
比如一個oomadj更小的進(jìn)程(也就是更重要的進(jìn)程)正綁定了oomadj更大的進(jìn)程,且在綁定的時候允許對host service的adj進(jìn)行promotion, 此時,hosting service的進(jìn)程就有可能會被promote到與client進(jìn)程相同的oomadj, 也就是說,如果host service進(jìn)程的oomadj太大了,那它可能會被kill掉,而此時更重要的client進(jìn)程還綁定在該service上,所以這時就有可能出現(xiàn)混亂。對于provider同理。

引用AOSP對于foreground App的定義, https://developer.android.com/about/versions/oreo/background#services

An app is considered to be in the foreground if any of the following is true:
*   It has a visible activity, whether the activity is started or paused.
*   It has a foreground service.
*   Another foreground app is connected to the app, either by binding to one of its services or by making 
    use of one of its content providers. For example, the app is in the foreground if another app binds to its:
    *   [IME](https://developer.android.com/guide/topics/text/creating-input-method.html)
    *   Wallpaper service
    *   Notification listener
    *   Voice or text service

二 native與kernel oomadj

applyOOMadj
lmkd

當(dāng)算出了一個進(jìn)程的 oomadj 后,就會往lkmd里去更新該oomadj, 前提是最新的與上一次的oomadj不一樣的時候。
最終是將oomadj寫入到 /proc/%d/oom_score_adj 中.

Android中進(jìn)行l(wèi)ow memory killer根據(jù)一些配置有可能發(fā)生在lmkd中,也有可能發(fā)生在kernel里
具體是

#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree"
has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
use_inkernel_interface = has_inkernel_module && !is_go_device;

如果不能訪問 "/sys/module/lowmemorykiller/parameters/minfree", 且不是 go device.在這種情況下,使用kernel的 low memory killer, 否則使用lmkd的 find_and_kill_process

2.1 lmkd的find_and_kill_process

如果使用的是lmkd的查殺功能,那么它進(jìn)行如下的步驟進(jìn)行操作

  • 監(jiān)聽pressure_level
    將/dev/memcg/memory.pressure_level的fd以及它所監(jiān)聽的level水平,如critical或medium寫入到/dev/memcg/cgroup.event_control, 當(dāng)有事件發(fā)生了,便會觸發(fā)mp_event或mp_event_critical, 最終都會調(diào)用mp_event_common

  • 計算內(nèi)存使用情況
    /dev/memcg/memory.usage_in_bytes
    /dev/memcg/memory.memsw.usage_in_bytes
    獲得系統(tǒng)內(nèi)存以及swap內(nèi)存的使用情況,決定是否觸發(fā)find_and_kill_process去查找進(jìn)程并kill

  • find_and_kill_process
    find_and_kill_process就比較簡單了,從oomadj最高往下依次查找,如果是critical,則查找到ro.lmk.critical 默認(rèn)是0; 如果是medium,則查找到ro.lmk.medium, 默認(rèn)是800;
    找到一個就kill掉,然后就不往下查找了。測試find_and_kill_process只會殺一個進(jìn)程,如果在kill掉一個進(jìn)程后,內(nèi)存還是不滿足,則kernel會觸發(fā)下一次事件... 這樣不斷的輪詢

2.2 kernel的low memory killer

drivers/staging/android/lowmemorykiller.c
這里面通過lowmem_scan遍歷所有的進(jìn)程task_struct,然后挑選出oomadj最大值, 如果兩個oomadj值相同,則比較兩個進(jìn)程的內(nèi)存使用,選擇占用內(nèi)存大的進(jìn)程進(jìn)行kill

    //找oomadj最大的進(jìn)程
    for_each_process(tsk) {
        struct task_struct *p; 
        short oom_score_adj;

        if (tsk->flags & PF_KTHREAD)
            continue;

        p = find_lock_task_mm(tsk);
        if (!p)
            continue;

        if (task_lmk_waiting(p) &&
            time_before_eq(jiffies, lowmem_deathpending_timeout)) {
            task_unlock(p);
            rcu_read_unlock();
            return 0;
        }   
        oom_score_adj = p->signal->oom_score_adj;
        if (oom_score_adj < min_score_adj) {
            task_unlock(p);
            continue;
        }   
        tasksize = get_mm_rss(p->mm);
        task_unlock(p);
        if (tasksize <= 0)
            continue;
        if (selected) {
            if (oom_score_adj < selected_oom_score_adj)
                continue;
            if (oom_score_adj == selected_oom_score_adj &&
                tasksize <= selected_tasksize)
                continue;
        }   
        selected = p;
        selected_tasksize = tasksize;
        selected_oom_score_adj = oom_score_adj;
        lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n",
                 p->comm, p->pid, oom_score_adj, tasksize);
    }
    //如果已經(jīng)找到,則向進(jìn)程發(fā)送 SIGKILL 信號
    if (selected) {
        long cache_size = other_file * (long)(PAGE_SIZE / 1024);
        long cache_limit = minfree * (long)(PAGE_SIZE / 1024);
        long free = other_free * (long)(PAGE_SIZE / 1024);

        task_lock(selected);
        send_sig(SIGKILL, selected, 0);
        if (selected->mm)
            task_set_lmk_waiting(selected);
        task_unlock(selected);
        trace_lowmemory_kill(selected, cache_size, cache_limit, free);
        lowmem_deathpending_timeout = jiffies + HZ;
        rem += selected_tasksize;
    }

在PIXEL手機中測試其實是沒有mp_event/mp_event_critical,也就是kill process這個動作由kernel去完成,而不是lmkd去完成。

三 Android Framework kill掉cached empty進(jìn)程

lmkd或kernel都可能會去kill掉進(jìn)程,除此之外android framework也會試著去kill cached/empty進(jìn)程

    final void updateOomAdjLocked() {
        //計算出 empty和cached的進(jìn)程的大小限制
        final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
        final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit;   
        ...
        //注意,這里是遍歷LRU緩存的進(jìn)程,從最新使用的進(jìn)程開始
        for (int i=N-1; i>=0; i--) {
            ProcessRecord app = mLruProcesses.get(i);
            if (!app.killedByAm && app.thread != null) {
                app.procStateChanged = false;
                
                //計算該進(jìn)程的 oomadj 和 curProcState
                computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
                ...
                
                //將 oomadj 更新到 lmkd中
                applyOomAdjLocked(app, true, now, nowElapsed);

                
                // Count the number of process types.
                switch (app.curProcState) {
                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                        //如果進(jìn)入該分支,說明是cached的進(jìn)程,更新numCached
                        mNumCachedHiddenProcs++;
                        numCached++;
                        //如果cached的進(jìn)程超過限制了,調(diào)用 app.kill 該進(jìn)程
                        if (numCached > cachedProcessLimit) {
                            app.kill("cached #" + numCached, true);
                        }
                        break;
                    case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
                        //進(jìn)入該分支是表明是 EMPTY 進(jìn)程, 如果超過限制,也直接kill掉
                      
                        if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
                                && app.lastActivityTime < oldTime) {
                            //這里超過了CUR_TRIM_EMPTY_PROCESSES這個限制
                            //且距離上一次使用的時間超過30分鐘,就kill掉,否則進(jìn)入else,
                            app.kill("empty for "
                                    + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
                                    / 1000) + "s", true);
                        } else {
                            numEmpty++;
                            //增加numEmpty, 如果緩存的大小大于 emptyProcessLimit也直接kill掉
                            if (numEmpty > emptyProcessLimit) {
                                app.kill("empty #" + numEmpty, true);
                            }
                        }
                        break;
                    default:
                        mNumNonCachedProcs++;
                        break;
                }

                //如果app已經(jīng)被置為 isolated了,且沒有services
                if (app.isolated && app.services.size() <= 0) {
                    app.kill("isolated not needed", true);
                } else {
                    ...
                }

                //計算出可以進(jìn)入 memory trim的進(jìn)程數(shù)量,前提是
                // curProcState 要高于 HOME 的那些進(jìn)程
                if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
                        && !app.killedByAm) {
                    numTrimming++;
                }
            }
        }
    ...

updateOomAdjLocked函數(shù)會對LRU里的進(jìn)程依次計算各個進(jìn)程的oomadj, 以及進(jìn)程的curProcState, 在設(shè)置對應(yīng)進(jìn)程的oomadj后,會根據(jù)進(jìn)程的curProcState的狀態(tài),來決定是否kill一些進(jìn)程,如上面代碼所示,從LRU里最近最常使用的進(jìn)程開始遍歷,如果curProcState是CACHED_ACTIVITY, 且緩存的進(jìn)程數(shù)量超過了 cachedProcessLimit, 就會觸發(fā) Process.kill 將該進(jìn)程kill掉。 同理對于 CACHED_EMPTY進(jìn)程一樣,只不過他們的limit不一樣而已。

        //計算出 empty和cached的進(jìn)程的大小限制
        final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
        final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit;  
default_cached_empty_limit

上圖是AOSP默認(rèn)的limit.

四 如何調(diào)試lmkd

在 Developer Options -> Apps -> Background process limit

Options Values
Standard limit -1
No background processes 0
At most 1 process 1
At most 2 process 2
At most 3 process 3
At most 4 process 4

選擇一項,最終會觸發(fā) setProcessLimit

    public void setProcessLimit(int max) {
        synchronized (this) {
            mConstants.setOverrideMaxCachedProcesses(max);
        }
        trimApplications();
    }
    public void setOverrideMaxCachedProcesses(int value) {
        mOverrideMaxCachedProcesses = value;
        updateMaxCachedProcesses();
    }
    private void updateMaxCachedProcesses() {
        CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
                ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses;
        CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
        final int rawMaxEmptyProcesses = computeEmptyProcessLimit(MAX_CACHED_PROCESSES);
        CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses/2;
        CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
    }

如果選擇 No background processes, 則第三小節(jié)中 updateOomAdjLockedcachedProcessLimitemptyProcessLimit 將會為0,
則表明如果一個進(jìn)程被計算出來是CACHED或者EMPTY的進(jìn)程, 就會被直接kill掉。

五 參考

https://www.cnblogs.com/tiger-wang-ms/p/6445213.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容