Android後臺殺死系列之三:後臺殺死原理LowMemoryKiller(4.3-6.0)

本篇是Android後臺殺死系列的第三篇,前面兩篇已經對後臺殺死注意事項殺死恢復機制作了分析,本篇主要講解的是Android後臺殺死原理。相對於後臺殺死恢復,LowMemoryKiller原理相對簡單,而且在網上仍是能找到很多資料的,不過,因爲Android不一樣版本在框架層的實現有一些不一樣,網上的分析也可能是針對一個Android版本,本文簡單作了如下區分對比。LowMemoryKiller(低內存殺手)是Andorid基於oomKiller原理所擴展的一個多層次oomKiller,OOMkiller(Out Of Memory Killer)是在Linux系統沒法分配新內存的時候,選擇性殺掉進程,到oom的時候,系統可能已經不太穩定,而LowMemoryKiller是一種根據內存閾值級別觸發的內存回收的機制,在系統可用內存較低時,就會選擇性殺死進程的策略,相對OOMKiller,更加靈活。在詳細分析其原理與運行機制以前,不妨本身想一下,假設讓你設計一個LowMemoryKiller,你會如何作,這樣一個系統須要什麼功能模塊呢?javascript

  • 進程優先級定義:只有有了優先級,才能決定先殺誰,後殺誰
  • 進程優先級的動態管理:一個進程的優先級不該該是固定不變的,須要根據其變更而動態變化,好比前臺進程切換到後臺優先級確定要下降
  • 進程殺死的時機,何時須要挑一個,或者挑多個進程殺死
  • 如何殺死

以上幾個問題即是一個MemoryKiller模塊須要的基本功能,Android底層採用的是Linux內核,其進程管理都是基於Linux內核,LowMemoryKiller也相應的放在內核模塊,這也意味着用戶空間對於後臺殺死不可見,就像AMS徹底不知道一個APP是否被後臺殺死,只有在AMS喚醒APP的時候,才知道APP是否被LowMemoryKiller殺死過。其實LowmemoryKiller的原理是很清晰的,先看一下總體流程圖,再逐步分析:html

App操做影響進程優先級

先記住兩點 :java

  1. LowMemoryKiller是被動殺死進程
  2. Android應用經過AMS,利用proc文件系統更新進程信息

Android應用進程優先級及oomAdj

Android會盡量長時間地保持應用存活,但爲了新建或運行更重要的進程,可能須要移除舊進程來回收內存,在選擇要Kill的進程的時候,系統會根據進程的運行狀態做出評估,權衡進程的「重要性「,其權衡的依據主要是四大組件。若是須要縮減內存,系統會首先消除重要性最低的進程,而後是重要性略遜的進程,依此類推,以回收系統資源。在Android中,應用進程劃分5級(摘自Google文檔):Android中APP的重要性層次一共5級:android

  • 前臺進程(Foreground process)
  • 可見進程(Visible process)
  • 服務進程(Service process)
  • 後臺進程(Background process)
  • 空進程(Empty process)

前臺進程git

用戶當前操做所必需的進程。若是一個進程知足如下任一條件,即視爲前臺進程:緩存

  • 包含正在交互的Activity(resumed
  • 包含綁定到正在交互的Activity的Service
  • 包含正在「前臺」運行的Service(服務已調用startForeground())
  • 包含正執行一個生命週期回調的Service(onCreate()、onStart() 或 onDestroy())
  • 包含一個正執行其onReceive()方法的BroadcastReceiver

一般,在任意給定時間前臺進程都爲數很少。只有在內存不足以支持它們同時繼續運行這一萬不得已的狀況下,系統纔會終止它們。 此時,設備每每已達到內存分頁狀態,所以須要終止一些前臺進程來確保用戶界面正常響應。網絡

可見進程架構

沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。 若是一個進程知足如下任一條件,即視爲可見進程:app

  • 包含不在前臺、但仍對用戶可見的 Activity(已調用其 onPause() 方法)。例如,若是前臺 Activity 啓動了一個對話框,容許在其後顯示上一Activity,則有可能會發生這種狀況。
  • 包含綁定到可見(或前臺)Activity 的 Service。

可見進程被視爲是極其重要的進程,除非爲了維持全部前臺進程同時運行而必須終止,不然系統不會終止這些進程。框架

服務進程

正在運行已使用 startService() 方法啓動的服務且不屬於上述兩個更高類別進程的進程。儘管服務進程與用戶所見內容沒有直接關聯,可是它們一般在執行一些用戶關心的操做(例如,在後臺播放音樂或從網絡下載數據)。所以,除非內存不足以維持全部前臺進程和可見進程同時運行,不然系統會讓服務進程保持運行狀態。

後臺進程

包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。這些進程對用戶體驗沒有直接影響,系統可能隨時終止它們,以回收內存供前臺進程、可見進程或服務進程使用。 一般會有不少後臺進程在運行,所以它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。若是某個 Activity 正確實現了生命週期方法,並保存了其當前狀態,則終止其進程不會對用戶體驗產生明顯影響,由於當用戶導航回該 Activity 時,Activity會恢復其全部可見狀態。 有關保存和恢復狀態、或者異常殺死恢復能夠參考前兩篇 文章。

空進程

不含任何活動應用組件的進程。保留這種進程的的惟一目的是用做緩存,以縮短下次在其中運行組件所需的啓動時間,這就是所謂熱啓動 。爲了使系統資源在進程緩存和底層內核緩存之間保持平衡,系統每每會終止這些進程。

根據進程中當前活動組件的重要程度,Android會將進程評定爲它可能達到的最高級別。例如,若是某進程託管着服務和可見 Activity,則會將此進程評定爲可見進程,而不是服務進程。此外,一個進程的級別可能會因其餘進程對它的依賴而有所提升,即服務於另外一進程的進程其級別永遠不會低於其所服務的進程。 例如,若是進程 A 中的內容提供程序爲進程 B 中的客戶端提供服務,或者若是進程 A 中的服務綁定到進程 B 中的組件,則進程 A 始終被視爲至少與進程B一樣重要。

經過Google文檔,對不一樣進程的重要程度有了一個直觀的認識,下面看一下量化到內存是什麼樣的呈現形式,這裏針對不一樣的重要程度,作了進一步的細分,定義了重要級別ADJ,並將優先級存儲到內核空間的進程結構體中去,供LowmemoryKiller參考:

ADJ優先級 優先級 對應場景
UNKNOWN_ADJ 16 通常指將要會緩存進程,沒法獲取肯定值
CACHED_APP_MAX_ADJ 15 不可見進程的adj最大值(不可見進程可能在任什麼時候候被殺死)
CACHED_APP_MIN_ADJ 9 不可見進程的adj最小值(不可見進程可能在任什麼時候候被殺死)
SERVICE_B_AD 8 B List中的Service(較老的、使用可能性更小)
PREVIOUS_APP_ADJ 7 上一個App的進程(好比APP_A跳轉APP_B,APP_A不可見的時候,A就是屬於PREVIOUS_APP_ADJ)
HOME_APP_ADJ 6 Home進程
SERVICE_ADJ 5 服務進程(Service process)
HEAVY_WEIGHT_APP_ADJ 4 後臺的重量級進程,system/rootdir/init.rc文件中設置
BACKUP_APP_ADJ 3 備份進程(這個不太瞭解)
PERCEPTIBLE_APP_ADJ 2 可感知進程,好比後臺音樂播放<
>VISIBLE_APP_ADJ 1 可見進程(可見,可是沒能獲取焦點,好比新進程僅有一個懸浮Activity,Visible process)
FOREGROUND_APP_ADJ 0 前臺進程(正在展現的APP,存在交互界面,Foreground process)
PERSISTENT_SERVICE_ADJ -11 關聯着系統或persistent進程
PERSISTENT_PROC_ADJ -12 系統persistent進程,好比電話
SYSTEM_ADJ -16 系統進程
NATIVE_ADJ -17 native進程(不被系統管理 )

以上介紹的目的只有一點:Android的應用進程是有優先級的,它的優先級跟當前是否存在展現界面,以及是否能被用戶感知有關,越是被用戶感知的的應用優先級越高(系統進程不考慮)。

Android應用的優先級是如何更新的

APP中不少操做均可能會影響進程列表的優先級,好比退到後臺、移到前臺等,都會潛在的影響進程的優先級,咱們知道Lowmemorykiller是經過遍歷內核的進程結構體隊列,選擇優先級低的殺死,那麼APP操做是如何寫入到內核空間的呢?Linxu有用戶間跟內核空間的區分,不管是APP仍是系統服務,都是運行在用戶空間,嚴格說用戶控件的操做是沒法直接影響內核空間的,更不用說更改進程的優先級。其實這裏是經過了Linux中的一個proc文件體統,proc文件系統能夠簡單的看可能是內核空間映射成用戶能夠操做的文件系統,固然不是全部進程都有權利操做,經過proc文件系統,用戶空間的進程就可以修改內核空間的數據,好比修改進程的優先級,在Android家族,5.0以前的系統是AMS進程直接修改的,5.0以後,是修改優先級的操做被封裝成了一個獨立的服務-lmkd,lmkd服務位於用戶空間,其做用層次同AMS、WMS相似,就是一個普通的系統服務。咱們先看一下5.0以前的代碼,這裏仍然用4.3的源碼看一下,模擬一個場景,APP只有一個Activity,咱們主動finish掉這個Activity,APP就回到了後臺,這裏要記住,雖然沒有可用的Activity,可是APP自己是沒喲死掉的,這就是所謂的熱啓動,先看下大致的流程:

App操做影響進程優先級

如今直接去AMS看源碼:

ActivityManagerService

public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) {
     ...
    synchronized(this) {

        final long origId = Binder.clearCallingIdentity();
        boolean res = mMainStack.requestFinishActivityLocked(token, resultCode,
                resultData, "app-request", true);
     ...
    }
}複製代碼

一開始的流程跟startActivity相似,首先是先暫停當前resume的Activity,其實也就是本身,

final boolean finishActivityLocked(ActivityRecord r, int index, int resultCode,
            Intent resultData, String reason, boolean immediate, boolean oomAdj) {
         ...
            if (mPausingActivity == null) {
                if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r);
                if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false");
                startPausingLocked(false, false);
            }
            ...
    }複製代碼

pause掉當前Activity以後,還須要喚醒上一個Activity,若是當前APP的Activity棧裏應經空了,就回退到上一個應用或者桌面程序,喚醒流程就不在講解了,由於在AMS恢復異常殺死APP的那篇已經說過,這裏要說的是喚醒以後對這個即將退回後臺的APP的操做,這裏注意與startActivity不一樣的地方,看下面代碼:

ActivityStack

private final void completePauseLocked() {
    ActivityRecord prev = mPausingActivity;

    if (prev != null) {
        if (prev.finishing) {
        1、 不一樣點
       <!--主動finish的時候,走的是這個分支,狀態變換的細節請本身查詢代碼-->
            prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);
        } 
        ...
        2、相同點         
     if (!mService.isSleeping()) {
        resumeTopActivityLocked(prev);
    }複製代碼

看一下上面的兩個關鍵點1跟2,1是同startActivity的completePauseLocked不一樣的地方,主動finish的prev.finishing是爲true的,所以會執行finishCurrentActivityLocked分支,將當前pause的Activity加到mStoppingActivities隊列中去,而且喚醒下一個須要走到到前臺的Activity,喚醒後,會繼續執行stop:

private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r,
            int index, int mode, boolean oomAdj) {
        if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) {
            if (!mStoppingActivities.contains(r)) {
                mStoppingActivities.add(r);
                 ...
            }
               ....
            return r;
        }
        ...
    }複製代碼

讓咱們再回到resumeTopActivityLocked繼續看,resume以後會回調completeResumeLocked函數,繼續執行stop,這個函數經過向Handler發送IDLE_TIMEOUT_MSG消息來回調activityIdleInternal函數,最終執行destroyActivityLocked銷燬ActivityRecord,

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
        ...
   if (next.app != null && next.app.thread != null) {                    ...
            try {
                。。。
                next.app.thread.scheduleResumeActivity(next.appToken,
                        mService.isNextTransitionForward());
                  ..。
            try {
                next.visible = true;
                completeResumeLocked(next);
            }  
            ....
         } 複製代碼

在銷燬Activity的時候,若是當前APP的Activity堆棧爲空了,就說明當前Activity沒有可見界面了,這個時候就須要動態更新這個APP的優先級,詳細代碼以下:

final boolean destroyActivityLocked(ActivityRecord r,
            boolean removeFromApp, boolean oomAdj, String reason) {
            ...
       if (hadApp) {
            if (removeFromApp) {
                // 這裏動ProcessRecord裏面刪除,可是沒從history刪除
                int idx = r.app.activities.indexOf(r);
                if (idx >= 0) {
                    r.app.activities.remove(idx);
                }
                ...
                if (r.app.activities.size() == 0) {
                    // No longer have activities, so update oom adj.
                    mService.updateOomAdjLocked();
                 ...
       }複製代碼

最終會調用AMS的updateOomAdjLocked函數去更新進程優先級,在4.3的源碼裏面,主要是經過Process類的setOomAdj函數來設置優先級:

ActivityManagerService

private final boolean updateOomAdjLocked(ProcessRecord app, int hiddenAdj,
        int clientHiddenAdj, int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) {
    ...
    計算優先級
    computeOomAdjLocked(app, hiddenAdj, clientHiddenAdj, emptyAdj, TOP_APP, false, doingAll);
     。。。
      <!--若是不相同,設置新的OomAdj-->

    if (app.curAdj != app.setAdj) {
        if (Process.setOomAdj(app.pid, app.curAdj)) {
        ...
}複製代碼

Process中setOomAdj是一個native方法,原型在android_util_Process.cpp中

android_util_Process.cpp

jboolean android_os_Process_setOomAdj(JNIEnv* env, jobject clazz,
                                      jint pid, jint adj)
{
#ifdef HAVE_OOM_ADJ
    char text[64];
    sprintf(text, "/proc/%d/oom_adj", pid);
    int fd = open(text, O_WRONLY);
    if (fd >= 0) {
        sprintf(text, "%d", adj);
        write(fd, text, strlen(text));
        close(fd);
    }
    return true;
#endif
    return false;
}複製代碼

能夠看到,在native代碼裏,就是經過proc文件系統修改內核信息,這裏就是動態更新進程的優先級oomAdj,以上是針對Android4.3系統的分析,以後會看一下5.0以後的系統是如何實現的。下面是4.3更新oomAdj的流程圖,注意紅色的執行點:

LowMemoryKiller更新進程oomAdj

Android5.0以後框架層的實現:LMKD服務

Android5.0將設置進程優先級的入口封裝成了一個獨立的服務lmkd服務,AMS再也不直接訪問proc文件系統,而是經過lmkd服務來進行設置,從init.rc文件中看到服務的配置。

service lmkd /system/bin/lmkd
    class core critical socket lmkd seqpacket 0660 system system複製代碼

從配置中能夠看出,該服務是經過socket與其餘進行進程進行通訊,其實就是AMS經過socket向lmkd服務發送請求,讓lmkd去更新進程的優先級,lmkd收到請求後,會經過/proc文件系統去更新內核中的進程優先級。首先看一下5.0中這一塊AMS有什麼改變,其實大部分流程跟以前4.3源碼相似,咱們只看一下不一樣地方

ActivityManagerService

private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
        ProcessRecord TOP_APP, boolean doingAll, long now) {
    ...
    computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
    ...
    applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
}

private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
        long nowElapsed) {
    boolean success = true;

    if (app.curRawAdj != app.setRawAdj) {
        app.setRawAdj = app.curRawAdj;
    }

    int changes = 0;
      不一樣點1
    if (app.curAdj != app.setAdj) {
        ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
        if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
                "Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": "
                + app.adjType);
        app.setAdj = app.curAdj;
        app.verifiedAdj = ProcessList.INVALID_ADJ;
    }複製代碼

從上面的不一樣點1能夠看出,5.0以後是經過ProcessList類去設置oomAdj,其實這裏就是經過socket與LMKD服務進行通訊,向lmkd服務傳遞給LMK_PROCPRIO命令去更新進程優先級:

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();
      }    

private static void writeLmkd(ByteBuffer buf) {
         for (int i = 0; i < 3; i++) {
        if (sLmkdSocket == null) {
          if (openLmkdSocket() == false) {
            ...
        try {
            sLmkdOutputStream.write(buf.array(), 0, buf.position());
            return;
            ...
    }複製代碼

其實就是openLmkdSocket打開本地socket端口,並將優先級信息發送過去,那麼lmkd服務端如何處理的呢,init.rc裏配置的服務是在開機時啓動的,來看看lmkd服務的入口:main函數

lmkd.c函數

int main(int argc __unused, char **argv __unused) {
    struct sched_param param = {
            .sched_priority = 1,
    };

    mlockall(MCL_FUTURE);
    sched_setscheduler(0, SCHED_FIFO, &param);
    if (!init())
        mainloop();

    ALOGI("exiting");
    return 0;
}複製代碼

很簡單,打開一個端口,並經過mainloop監聽socket,若是有請求到來,就解析命令並執行,剛纔傳入的LMK_PROCPRIO命令對應的操做就是cmd_procprio,用來更新oomAdj,其更新新機制仍是經過proc文件系統,不信?看下面代碼:

static void cmd_procprio(int pid, int uid, int oomadj) {
    struct proc *procp;
    。。。
    仍是利用/proc文件系統進行更新
    snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid);
    snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj));
    writefilestring(path, val);
   。。。
}複製代碼

簡單的流程圖以下,同4.3不一樣的地方

Android5.0以後的LMKD服務

以上就分析完了用戶空間的操做如何影響到進程的優先級,而且將新的優先級寫到內核中。最後看一下LomemoryKiller在何時、如何根據優先級殺死進程的:

LomemoryKiller內核部分:如何殺死

LomemoryKiller屬於一個內核驅動模塊,主要功能是:在系統內存不足的時候掃描進程隊列,找到低優先級(也許說性價比低更合適)的進程並殺死,以達到釋放內存的目的。對於驅動程序,入口是__init函數,先看一下這個驅動模塊的入口:

static int __init lowmem_init(void)
{
    register_shrinker(&lowmem_shrinker);
    return 0;
}複製代碼

能夠看到在init的時候,LomemoryKiller將本身的lowmem_shrinker入口註冊到系統的內存檢測模塊去,做用就是在內存不足的時候能夠被回調,register_shrinker函數是一屬於另外一個內存管理模塊的函數,若是必定要根下去的話,能夠看一下它的定義,其實就是加到一個回調函數隊列中去:

void register_shrinker(struct shrinker *shrinker)
{
    shrinker->nr = 0;
    down_write(&shrinker_rwsem);
    list_add_tail(&shrinker->list, &shrinker_list);
    up_write(&shrinker_rwsem);
}複製代碼

最後,看一下,當內存不足觸發回調的時候,LomemoryKiller是如何找到低優先級進程,並殺死的:入口函數就是init時候註冊的lowmem_shrink函數(4.3源碼,後面的都有微調但原理大概相似):

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
    struct task_struct *p;
    。。。
    關鍵點1 找到當前的內存對應的閾值
    for(i = 0; i < array_size; i++) {
        if (other_free < lowmem_minfree[i] &&
            other_file < lowmem_minfree[i]) {
            min_adj = lowmem_adj[i];
            break;
        }
    }
    。。。
    關鍵點2 找到優先級低於這個閾值的進程,並殺死

    read_lock(&tasklist_lock);
    for_each_process(p) {
        if (p->oomkilladj < min_adj || !p->mm)
            continue;
        tasksize = get_mm_rss(p->mm);
        if (tasksize <= 0)
            continue;
        if (selected) {
            if (p->oomkilladj < selected->oomkilladj)
                continue;
            if (p->oomkilladj == selected->oomkilladj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;

    }
    if(selected != NULL) {
        force_sig(SIGKILL, selected);
        rem -= selected_tasksize;
    }
    lowmem_print(4, "lowmem_shrink %d, %x, return %d\n", nr_to_scan, gfp_mask, rem);
    read_unlock(&tasklist_lock);
    return rem;
}複製代碼

先看關鍵點1:其實就是肯定當前低內存對應的閾值;關鍵點2 :找到比該閾值優先級低或者相等,而且內存佔用較多的進程(tasksize = get_mm_rss(p->mm)其實就是獲取內存佔用)),將其殺死。如何殺死的呢?很直接,經過Linux的中的信號量,發送SIGKILL信號直接將進程殺死。到這就分析完了LomemoryKiller內核部分如何工做的。其實很簡單,一句話:被動掃描,找到低優先級的進程,殺死。

總結

經過本篇文章,但願你們能有如下幾點認知:

  • Android APP進程是有優先級的的,與進程是否被用戶感知有直接關係
  • APP切換等活動均可能形成進程優先級的變化,都是利用AMS,並經過proc文件設置到內核的
  • LowmemoryKiller運行在內核,在內存須要縮減的時候,會選擇低優先級的進程殺死

至於更加細節的內存的縮減、優先級的計算也許未來會放到單獨的文章中說明,本文的目的是:能讓你們對LowmemoryKiller的概念以及運行機制有個簡單瞭解。

Android 後臺殺死系列之一:FragmentActivity 及 PhoneWindow 後臺殺死處理機制
Android後臺殺死系列之二:ActivityManagerService與App現場恢復機制 Android後臺殺死系列之三:後臺殺死原理LowMemoryKiller(4.3-6.0)
Android後臺殺死系列之四:Binder訃告原理
Android後臺殺死系列之五:Android進程保活-自「裁」或者耍流氓
僅供參考,歡迎指正

參考文檔

Android應用程序啓動過程源代碼分析
Android Framework架構淺析之【近期任務】
Android Low Memory Killer介紹
Android開發之InstanceState詳解
對Android近期任務列表(Recent Applications)的簡單分析
Android 操做系統的內存回收機制
Android LowMemoryKiller原理分析 精
Android進程生命週期與ADJ
Linux下/proc目錄簡介
Android系統中的進程管理:進程的建立 精
Google文檔--進程和線程

相關文章
相關標籤/搜索