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,你會如何做,這樣一個系統需要什么功能模塊呢?
-
進程優先級定義:只有有了優先級,才能決定先殺誰,后殺誰
-
進程優先級的動態管理:一個進程的優先級不應該是固定不變的,需要根據其變動而動態變化,比如前臺進程切換到后臺優先級肯定要降低
-
進程殺死的時機,什么時候需要挑一個,或者挑多個進程殺死
-
如何殺死
以上幾個問題便是一個MemoryKiller模塊需要的基本功能,Android底層采用的是Linux內核,其進程管理都是基于Linux內核,LowMemoryKiller也相應的放在內核模塊,這也意味著用戶空間對于后臺殺死不可見,就像AMS完全不知道一個APP是否被后臺殺死,只有在AMS喚醒APP的時候,才知道APP是否被LowMemoryKiller殺死過。其實LowmemoryKiller的原理是很清晰的,先看一下整體流程圖,再逐步分析:
先記住兩點 :
LowMemoryKiller是被動殺死進程
Android應用通過AMS,利用proc文件系統更新進程信息
Android應用進程優先級及oomAdj
Android會盡可能長時間地保持應用存活,但為了新建或運行更重要的進程,可能需要移除舊進程來回收內存,在選擇要Kill的進程的時候,系統會根據進程的運行狀態作出評估,權衡進程的“重要性“,其權衡的依據主要是四大組件。如果需要縮減內存,系統會首先消除重要性最低的進程,然后是重要性略遜的進程,依此類推,以回收系統資源。在Android中,應用進程劃分5級(摘自Google文檔):Android中APP的重要性層次一共5級:
-
前臺進程(Foreground process)
-
可見進程(Visible process)
-
服務進程(Service process)
-
后臺進程(Background process)
-
空進程(Empty process)
前臺進程
用戶當前操作所必需的進程。如果一個進程滿足以下任一條件,即視為前臺進程:
-
包含正在交互的Activity(resumed
-
包含綁定到正在交互的Activity的Service
-
包含正在“前臺”運行的Service(服務已調用startForeground())
-
包含正執行一個生命周期回調的Service(onCreate()、onStart() 或 onDestroy())
-
包含一個正執行其onReceive()方法的BroadcastReceiver
通常,在任意給定時間前臺進程都為數不多。只有在內存不足以支持它們同時繼續運行這一萬不得已的情況下,系統才會終止它們。 此時,設備往往已達到內存分頁狀態,因此需要終止一些前臺進程來確保用戶界面正常響應。
可見進程
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。 如果一個進程滿足以下任一條件,即視為可見進程:
-
包含不在前臺、但仍對用戶可見的 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參考:
| 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進程,比如telephony |
| 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本身是沒喲死掉的,這就是所謂的熱啟動,先看下大體的流程:
現在直接去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_ADJchar 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; #endifreturn false; }可以看到,在native代碼里,就是通過proc文件系統修改內核信息,這里就是動態更新進程的優先級oomAdj,以上是針對Android4.3系統的分析,之后會看一下5.0之后的系統是如何實現的。下面是4.3更新oomAdj的流程圖,注意紅色的執行點:
Android5.0之后框架層的實現:LMKD服務
Android5.0將設置進程優先級的入口封裝成了一個獨立的服務lmkd服務,AMS不再直接訪問proc文件系統,而是通過lmkd服務來進行設置,從init.rc文件中看到服務的配置。
service lmkd /system/bin/lmkdclass corecriticalsocket 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;不同點1if (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, ¶m);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不同的地方
以上就分析完了用戶空間的操作如何影響到進程的優先級,并且將新的優先級寫到內核中。最后看一下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 :找到比該閾值優先級低,切task多的進程,將其殺死。如何殺死的呢?很直接,通過Linux的中的信號量,發送SIGKILL信號直接將進程殺死。到這就分析完了LomemoryKiller內核部分如何工作的。其實很簡單,一句話:被動掃描,找到低優先級的進程,殺死。
總結
通過本篇文章,希望大家能有以下幾點認知:
-
Android APP進程是有優先級的的,與進程是否被用戶感知有直接關系
-
APP切換等活動都可能造成進程優先級的變化,都是利用AMS,并通過proc文件設置到內核的
-
LowmemoryKiller運行在內核,在內存需要縮減的時候,會選擇低優先級的進程殺死
至于更加細節的內存的縮減、優先級的計算也許將來會放到單獨的文章中說明,本文的目的是:能讓大家對LowmemoryKiller的概念以及運行機制有個簡單了解。
參考文檔
Android應用程序啟動過程源代碼分析?
Android Framework架構淺析之【近期任務】?
Android Low Memory Killer介紹?
Android開發之InstanceState詳解?
對Android近期任務列表(Recent Applications)的簡單分析?
Android 操作系統的內存回收機制?
Android LowMemoryKiller原理分析 精?
Android進程生命周期與ADJ?
Linux下/proc目錄簡介?
Android系統中的進程管理:進程的創建 精?
Google文檔--進程和線程
原文地址:
總結
以上是生活随笔為你收集整理的Android后台杀死系列之三:LowMemoryKiller原理(4.3-6.0)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android后台杀死系列之二:Acti
- 下一篇: ActivityManagerServi