Android核心詳解之Low memory killer

來源:互聯網
上載者:User

Android在記憶體管理上與linux有些小的區別。其中一個就是引入了Low memory killer .
1,引入原因
   Android是一個多任務系統,也就是說可以同時運行多個程式,這個大家應該很熟悉。一般來說,啟動運行一個程式是有一定的時間開銷的,因此為了加快運行速度,當你退出一個程式時,Android並不會立即殺掉它,這樣下次再運行該程式時,可以很快的啟動。隨著系統中保留的程式越來越多,記憶體肯定會出現不足,low memory killer就是在系統記憶體低於某值時,清除相關的程式,保障系統保持擁有一定數量的空閑記憶體。
   Android中,進程的生命週期都是由系統控制的,即使使用者關掉了程式,進程依然是存在於記憶體之中。這樣設計的目的是為了下次能快速啟動。當然,隨著系統已耗用時間的增長,記憶體會越來越少。Android Kernel 會定時執行一次檢查,殺死一些進程,釋放掉記憶體。
    那麼,如何來判斷,那些進程是需要殺死的呢?答案就是我們的標題:Low memory killer機制。
    Android 的Low memory killer是基於linux的OOM(out of memory)  規則改進而來的。 OOM通過一些比較複雜的評分機制,對進程進行打分,然後將分數高的進程判定為bad進程,殺死並釋放記憶體。OOM只有當系統記憶體不足的時候才會啟動檢查,而Low memory killer 則是定時進行檢查。
     Low memory killer 主要是通過進程的oom_adj 來判定進程的重要程度。oom_adj的大小和進程的類型以及進程被調度的次序有關。
     Low memory killer 的具體實現可參看:kernel/drivers/misc/lowmemorykiller.c 
     其原理很簡單,在linux中,存在一個kswapd的核心線程,當linux回收存放分頁的時候,kswapd線程將會遍曆一張shrinker鏈表,並執行回調,定義如下:
struct shrinker{
int (*shrink)(int nr_to_scan, gfp_t gfp_mask);
int seeks;
struct list_head list;
long nr;
};
#define DEFAULT_SEEKS 2
extern void register_shrinker(struct shrinker *);
extern void unregiter_shrinker(struct shrinker *);

所以只要註冊 Shrinker,變可以在記憶體分頁回收時根據規則釋放記憶體,下面我們來看看其實現。 
首先定義shrinker結構體,lowmem_shrink為回呼函數的指標,當有記憶體分頁回收的時候,這個函數將會被調用。
static struct shrinker lowmem_shrinker = {
   .shrink = lowmem_shrink,
   .seeks = DEFAULT_SEEKS * 16
};

2. 基本原理和重要概念
   Low memory killer根據兩個原則,進程的重要性和釋放這個進程可擷取的空閑記憶體數量,來決定釋放的進程。
(1)進程的重要性,由task_struct->signal_struct->oom_adj決定。
Android將程式分成以下幾類,按照重要性依次降低的順序:
名稱 oom_adj 解釋 
FOREGROUD_APP 0 前景程式,可以理解為你正在使用的程式 
VISIBLE_APP 1 使用者可見的程式 
SECONDARY_SERVER 2 後台服務,比如說QQ會在後台運行服務 
HOME_APP 4 HOME,就是主介面 
HIDDEN_APP 7 被隱藏的程式 
CONTENT_PROVIDER 14 內容提供者, 
EMPTY_APP 15  空程式,既不提供服務,也不提供內容
其中每個程式都會有一個oom_adj值,這個值越小,程式越重要,被殺的可能性越低。
(2)進程的記憶體,通過get_mm_rss擷取,在相同的oom_adj下,記憶體大的,優先被殺。
(3)那記憶體低到什麼情況下,low memory killer開始幹活呢?Android提供了兩個數組,一個lowmem_adj,一個lowmem_minfree。前者存放著oom_adj的閥值,後者存放著minfree的警戒值,以page為單位(4K)。
oom_adj 記憶體警戒值( 以4K為單位)
0 1536 
1 2048 
2 4096 
7 5120 
14 5632 
15 6144
3.源碼解析
module_init(lowmem_init);
 module_exit(lowmem_exit);
    模組載入和退出的函數,主要的功能就是register_shrinker和unregister_shrinker結構體lowmem_shrinker。主要是將函數lowmem_shrink註冊到shrinker鏈表裡,在mm_scan調用。
下面詳細的介紹這個函數:
 for (i = 0; i < array_size; i++) {
        if (other_file < lowmem_minfree[i]) {
            min_adj = lowmem_adj[i];
            break;
        }
    }
other_file,系統的空閑記憶體數,根據上面的邏輯判斷出,low memory killer需要對adj高於多少(min_adj)的進程進行分析是否釋放。
  if (nr_to_scan <= 0 || min_adj == OOM_ADJUST_MAX + 1) {
        lowmem_print(5, "lowmem_shrink %d, %x, return %d\n",
                 nr_to_scan, gfp_mask, rem);
        return rem;
    }
  判斷,系統當前的狀態是否需要進行low memory killer。
for_each_process(p) {
        struct mm_struct *mm;
        struct signal_struct *sig;
        int oom_adj;
        task_lock(p);
        mm = p->mm;
        sig = p->signal;
        if (!mm || !sig) {
            task_unlock(p);
            continue;
        }
        oom_adj = sig->oom_adj;
        if (oom_adj < min_adj) {
            task_unlock(p);
            continue;
        }
        tasksize = get_mm_rss(mm);
        task_unlock(p);
        if (tasksize <= 0)
            continue;
        if (selected) {
            if (oom_adj < selected_oom_adj)
                continue;
            if (oom_adj == selected_oom_adj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;
        selected_oom_adj = oom_adj;
        lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
                 p->pid, p->comm, oom_adj, tasksize);
    }
對每個sig->oom_adj大於min_adj的進程,找到佔用記憶體最大的進程存放在selected中。
if (selected) {
        if (fatal_signal_pending(selected)) {
            pr_warning("process %d is suffering a slow death\n",
                   selected->pid);
            read_unlock(&tasklist_lock);
            return rem;
        }
        lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",
                 selected->pid, selected->comm,
                 selected_oom_adj, selected_tasksize);
        force_sig(SIGKILL, selected);
        rem -= selected_tasksize;
    }
 發送SIGKILL資訊,殺掉該進程。
關於Low Memory Killer的分析就到這裡,在瞭解了其機制和原理之後,我們發現它的實現非常簡單,與標準的Linux OOM機制類似,只是實現方式稍有不同。標準Linux的OOM Killer機制在mm/oom_kill.c中實現,且會被__alloc_pages_may_oom調用(在分配記憶體時,即mm/page_alloc.c中)。oom_kill.c最主要的一個函數是out_of_memory,它選擇一個bad進程Kill,Kill的方法同樣是通過發送SIGKILL訊號。在out_of_memory中通過調用select_bad_process來選擇一個進程Kill,選擇的依據在badness函數中實現,基於多個標準來給每個進程評分,評分最高的被選中並Kill。一般而言,佔用記憶體越多,oom_adj就越大,也就越有可能被選中。

4. 資源配置
閾值表可以通過/sys/module/lowmemorykiller/parameters/adj和/sys/module/lowmemorykiller/parameters/minfree進行配置,例如在init.rc中:
# Write value must be consistent with the above properties.
   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
   class_start default
進程oom_adj同樣可以進行設定,通過write /proc/<PID>/oom_adj  ,在init.rc中,init進程的pid為1,omm_adj被配置為-16,永遠不會被殺死。
   # Set init its forked children's oom_adj.
   write /proc/1/oom_adj -16
   Low memory killer的基本原理我們應該弄清了,正如我前面所說的,進程omm_adj的大小跟進程的類型以及進程被調度的次序有關。進程的類型,可以在ActivityManagerService中清楚的看到: 
    static final int EMPTY_APP_ADJ;
    static final int HIDDEN_APP_MAX_ADJ;
    static final int HIDDEN_APP_MIN_ADJ;
    static final int HOME_APP_ADJ;
    static final int BACKUP_APP_ADJ;
    static final int SECONDARY_SERVER_ADJ;
    static final int HEAVY_WEIGHT_APP_ADJ;
    static final int PERCEPTIBLE_APP_ADJ;
    static final int VISIBLE_APP_ADJ;
    static final int FOREGROUND_APP_ADJ;
    static final int CORE_SERVER_ADJ = -12;
    static final int SYSTEM_ADJ = -16; 
   ActivityManagerService定義各種進程的oom_adj,CORE_SERVER_ADJ代表一些核心的服務的omm_adj,數值為-12,由前面的分析可知道,這類進程永遠也不會被殺死。
其他未賦值的都在static塊中進行了初始化,是通過system/rootdir/init.rc進行配置的:

 在init.rc中:
# Define the oom_adj values for the classes of processes that can be
# killed by the kernel.  These are used in ActivityManagerService.
   setprop ro.FOREGROUND_APP_ADJ 0
   setprop ro.VISIBLE_APP_ADJ 1
   setprop ro.SECONDARY_SERVER_ADJ 2
   setprop ro.HIDDEN_APP_MIN_ADJ 7
   setprop ro.CONTENT_PROVIDER_ADJ 14
   setprop ro.EMPTY_APP_ADJ 15
 
# Define the memory thresholds at which the above process classes will
# be killed.  These numbers are in pages (4k).
   setprop ro.FOREGROUND_APP_MEM 1536
   setprop ro.VISIBLE_APP_MEM 2048
   setprop ro.SECONDARY_SERVER_MEM 4096
   setprop ro.HIDDEN_APP_MEM 5120
   setprop ro.CONTENT_PROVIDER_MEM 5632
   setprop ro.EMPTY_APP_MEM 6144
  由此我們知道EMPTY_APP 最容易被殺死,其實是CONTENT_PROVIDER ,FOREGROUND的進程很難被殺死。
  現在我們再來說影響oom_adj的第二個因素,進程的調度次序。這涉及到了ActivityManagerService的複雜調度,我們下次再來看吧。

附言:
Android將進程分為6個等級,它們按優先順序順序由高到低依次是:
    1. FOREGROUND_APP:
    This is the process running the current foreground app.  We'd really rather not kill it!
    使用者正在使用的程式. 這個設的太高,使用者看到得就會是一個正在使用的程式莫名其妙的消失了,然後自動回到案頭..(因為它被系統kill了..) 所以最好別動它.
    2. VISIBLE_APP:
    This is a process only hosting activities that are visible to the user, so we'd prefer they don't disappear.
    跟FOREGROUND_APP類似,使用者正在使用/看得到. 它們的區別就是VISIBLE_APP可能不是使用者focus的程式,但是使用者看得到,或者沒有覆蓋到整個螢幕,只有螢幕的一部分. 所以可以適當的比FOREGROUND_APP高一點.
    3. SECONDARY_SERVER:
    This is a process holding a secondary server -- killing it will not have much of an impact as far as the user is concerned.
    所有應用的service. 系統級的service比如PhoneService不屬於這類,它們是絕不會被Android結束掉的. 所以這個可以適當的設高一點點~ 注意, HOME(SenseUI)也包括在這裡 因此還是別設的太高. 要不每次返回案頭都得等它重新load,特別是widget多的.
    4. HIDDEN_APP:
    This is a process only hosting activities that are not visible, so it can be killed without any disruption.
    本來屬於1或者2的程式, 在使用者按了"back"或者"home"後,程式本身看不到了,但是其實還在啟動並執行程式,它們就屬於HIDDEN_APP了. 幹掉沒什麼影響.. 不過要瞭解並不是所有屬於這一類的就應該馬上結束掉,像push mail,locale,鬧鐘,等都屬於這一類. 因此還是別設的過高. 真正"應該"一點返回鍵就退出的程式(真正沒用的程式)在下面.
    5. CONTENT_PROVIDER:
    This is a process with a content provider that does not have any clients attached to it.  If it did have any clients, its adjustment would be the one for the highest-priority of those processes.
    5,6的區別具體不太瞭解..這個也是用處不大,但是還是比EMPTY_APP稍微有點用.. 所以高點沒關係~, 
    6. EMPTY_APP:
    This is a process without anything currently running in it.  Definitely the first to go! This value is initalized in the constructor, careful when refering to this static variable externally.
    完全沒用的一個,殺了它只有好處沒壞處,第一個幹它!
The configuration is set in the file: "/sys/module/lowmemorykiller/parameters/minfree" 
查看現在的設定可以:
   1. # cat /sys/module/lowmemorykiller/parameters/minfree
顯示出的應該是6個數字,以逗號隔開,例如:
    1536,2048,4096,5120,5632,6144
注意這些數位單位是page. 1 page = 4 kilobyte.
上面的六個數字對應的就是(MB): 6,8,16,20,22,24
這些數字也就是對應的記憶體閥值,一旦低於該值,Android便開始按順序關閉進程. 因此Android開始結束優先順序最低的EMPTY_APP當可用記憶體小於24MB(6144*4/1024).
有一點沒搞明白,它的可用記憶體不知道是從哪得到.. 明顯不是free顯示的可用記憶體,而且貌似compcache跟swap也不影響.
要想重新設定該值:
   1. # echo "1536,2048,4096,5120,15360,23040" > /sys/module/lowmemorykiller/parameters/minfree;
這樣當可用記憶體低於90MB的時候便開始結束EMPTY_APP. 而當可用記憶體低於60MB的時候才開始結束CONTENT_PROVIDER組. 其餘四個沒動.
注意:
通過以上方法改變的數值並非永久.在下次重啟後就又恢複到之前的設定. 若想讓設定在每次開機執行,將;
   echo "1536,3072,4096,21000,23000,25000" > /sys/module/lowmemorykiller/parameters/minfree
加入到任意一個開機啟動的設定檔. 一般在/system/init.d下的檔案都是開機執行的(有的ROM也可能不在這裡..) 只需用記事本開啟任意一個檔案,再把這行加入其中就好.

個人經驗補充一點:
一般前三個不用動,或者只是稍微改高一點就好,畢竟它們比較重要,不要太早結束. 值得關注的是後三個. 它們也可以適當的設大一點. 當然,任何東西都有一個度,像上面例子中EMPTY_APP搞到25000(97MB)並不至於大到離譜,hero我沒有過 我在Magic 32A甚至還試過120MB..不過120MB那是有點過頭了..遊戲玩著玩著就消失回到案頭了(可用記憶體低於120MB被系統結束任務了).. 因此還是不要太高.
我目前的設定(開了32MB的CC加32MB的backing swap @60 swappiness):
    1536,2048,4096,8192,11520,19200
也許大家還不明白這樣做的好處. 這樣的好處就是讓我們隨時有足夠的記憶體來執行我們要啟動並執行程式,而那些真正沒用的進程又不會多餘的佔用著寶貴的記憶體. 更重要的是這一切都是不需要您的參與或任何第三方軟體的協助,完全由Android在後台自動執行. 試想,又有誰會比Android更熟悉的掌握每個進程呢 比起那些記憶體管理程式傻傻的一鍋端的方法聰明多了吧~ 讓我們從現在開始把那些記憶體管理程式刪掉吧。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.