Android lowmemkiller配置(Android7.1實踐)

一、概述

1.1 Android

Andorid的Low Memory Killer是在標準的linux kernel的OOM基礎上修改而來的一種內存管理機制。當系統內存不足時,殺死不必要的進程釋放其內存。不必要的進程的選擇根據有2個:oom_adj和佔用的內存的大小。

oom_adj代表進程的優先級,數值越高,優先級越低,越容易被殺死;對應每個oom_adj都可以有一個空閒內存的閥值。Android Kernel每隔一段時間會檢測當前空閒內存是否低於某個閥值。假如是,則殺死oom_adj最大的不必要的進程,如果有多個,就根據oom_score_adj去殺死進程,直到內存恢復低於閥值的狀態。

LowMemoryKiller的值的設定,主要保存在2個文件之中,分別是:

/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree

例如:

# cat /sys/module/lowmemorykiller/parameters/adj
0,100,200,300,900,906
# cat /sys/module/lowmemorykiller/parameters/minfree
9216,11520,13824,16128,18432,23040

Android按照進程的不同類別,分爲了6個等級
在這裏插入圖片描述
分別對應的空閒內存閾值,在frameworks\base\services\core\java\com\android\server\am\ProcessList.java中的函數updateOomLevels計算得來。ADJ在ProcessList.java中定義,不用做修改。

在Android系統裏面,當app的狀態發生變化,如:創建,收到廣播,喚醒,放入後臺等, ActivityManagerService的updateOomAdjLocked() 會computeOomAdjLocked(),然後,通過applyOomAdjLocked()把app的oom_adj值寫入到Linux Kernel。

那麼AN是如何計算每個app最終的oom_adj值的呢?

oom_adj的範圍是[-16, 15],AN根據app進程的特性進行了分類,不同的類別,對應不同的數值。

  • 9 ~ 15: 是緩存到後臺的app。
  • 8: service B 列表, 長時間未使用的service進程。
  • 7: 前一個app。
  • 6: Home app。
  • 5: 包含service的app 進程。
  • 4: 高權重的應用--隱藏的屬性,AN P以上版本才能配置:android:cantSaveState=“true”
  • 3: backup app--正在被備份的app。
  • 2: 用戶可感知的後臺進程,如:後臺背景音樂播放器。
  • 1: 前臺app啓動的一些可見的組件,如: 彈出的Email activity。
  • 0: 前臺app.
  • -11: persistent service -- android:persistent:=true
  • -12: 常駐內存的系統app--如:系統自帶的撥號app。
  • -16: 系統進程--如:system_server進程。
  • -17: 不受oom_adj的管理,該進程不會被殺死,也native process的默認值。
1.2 lowmemorykiller Driver部分

lowmemorykiller driver 位於 drivers/staging/android/lowmemorykiller.c

LMK通過註冊shrinker來實現,shrinker是Linux kernel標準的回收page的機制,由內核線程kswapd負責監控。參見mm/vmscan.c中的kswapd。 當需要分配內存,發現可用內存不足時,則內核會阻塞請求分配內存的進程,進入slow path的內存申請邏輯進行回收(包括ZRAM的內存壓縮),參見mm/page_alloc.c中的__alloc_pages_slowpath。 LMK核心思想:

LowMemoryKiller註冊了shrinker--Linux Kernel的一個內存管理工具,當kernel需要回收內存時,
會回調LowMemoryKiller的lowmem_shrink(),它先檢查kernel 剩下多少內存,根據剩下的內存數量
來匹配數組 lowmem_minfree[], 找到數組索引值,然後,再使用該索引值,從 lowmem_adj[]
這個數組裏面就得到目標oom_adj值,最終,在大於等於該目標oom_adj的進程中,殺死擁有最大oom_adj
值的進程--send_sig(SIGKILL, selected, 0) 。算法其實很簡單,就是兩個一維數組的映射。

lowmemorykiller.c 中的lowmem_shrink註冊到vm

static struct shrinker lowmem_shrinker = {
	.shrink = lowmem_shrink,
	.seeks = DEFAULT_SEEKS * 16
};

static int __init lowmem_init(void)
{
	register_shrinker(&lowmem_shrinker);
	return 0;
}
/*
 * Add a shrinker callback to be called from the vm
 */
void register_shrinker(struct shrinker *shrinker)
{
	atomic_long_set(&shrinker->nr_in_batch, 0);
	down_write(&shrinker_rwsem);
	list_add_tail(&shrinker->list, &shrinker_list);
	up_write(&shrinker_rwsem);
}

然後,當Linux內存管理模塊線程kswapd被調度時,就會通過 kswapd_shrink_node > ……>scan_objects 來觸發lowmem_shrink內存掃描及執行lowmem killer。lowmem_shrink根據當前系統free內存和每個進程的oom_score_adj來決定當前哪個進程將會被killed。

內存回收時機

內存中有三個水位min <low < high,當內存達到low水位時,kswapd開始回收內存,直到內存達到high水位時停止kswapd;

如果kswapd回收速度小於內存消耗速度,內存水位下降到min水位,則direct reclaim開始回收內存,並會阻塞應用程序。 內存換出到swap分區的過程:

kswapd()-->balance_pgdat()-->shrink_zone()-->shrink_inactive_list()

Watermark的設置

每個zone有單獨的水位,可以在/proc/sys/vm/min_free_kbytes中設置min水位,這個參數本身決定了系統中每個zone的watermark[min]的值大小。 然後內核根據min的大小並參考每個zone的內存大小分別算出每個zone的low水位和high水位值 通過命令查看zone的watermark:

XXXXX:/ # cat /proc/zoneinfo
  Node 0, zone      DMA
  per-node stats
  ………….
     pages free     285389
        min      1392
        low      2054 
        high     2716
   node_scanned  0
  …………..

相關代碼見/mm/page_alloc.c: __setup_per_zone_wmarks

二、配置修改

關於lowmemkiller可配置項有
android\frameworks\base\core\res\res\values\config.xml

<!-- config_lowMemoryKillerMinFreeKbytesAbsolute和config_lowMemoryKillerMinFreeKbytesAdjust修改minfree的默認值 -->
<!-- config_lowMemoryKillerMinFreeKbytesAbsolute按照和MAX_ADJ的minfree比例,等比例計算個等級的空閒內存閾值。-->
<integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">-1</integer>
<!-- config_lowMemoryKillerMinFreeKbytesAdjust按照和MAX_ADJ的minfree比例,等比例計算各等級的空閒內存閾值的加減量,然後默認值加或減加減量 -->
<integer name="config_lowMemoryKillerMinFreeKbytesAdjust">0</integer>

<!-- 修改/proc/sys/vm/extra_free_kbytes的值,這個參數告訴VM在後臺回收程序
開始工作的門限值和直接回收(內存分配進程做的)的門限值之間保持額外的free memory。
需要低延遲的內存分配和在內存分配具有突發性的情況很有用,例如一個實時應用,接受
和發送最大的信息可能達到200MB的網絡數據(導致內核內存分配),這就需要200MB的額
外的可用內存來避免直接回收內存相關的延遲。-->
<!-- config_extraFreeKbytesAbsolute替換默認值 -->
<integer name="config_extraFreeKbytesAbsolute">-1</integer>
<!-- config_extraFreeKbytesAdjust,在默認值的基礎上加或減配置值 -->
<integer name="config_extraFreeKbytesAdjust">0</integer>

計算代碼如下:

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);
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;
        }
    }
}
... 
int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust);
        int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute);
if (reserve_abs >= 0) {
    reserve = reserve_abs;
}

if (reserve_adj != 0) {
    reserve += reserve_adj;
    if (reserve < 0) {
        reserve = 0;
    }
}

在updateOomLevels中,oom_adj和minfree通過socket傳給lmkd服務,服務設置給驅動。

if (write) {
    ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
    buf.putInt(LMK_TARGET);
    for (int i=0; i<mOomAdj.length; i++) {
    	// 注意:設置到kernel的minfree是內存頁數,每個內存頁大小爲PAGE_SIZE(4KB)
        buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
        buf.putInt(mOomAdj[i]);
    }
    writeLmkd(buf); // 寫給lmkd服務
    SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve)); // 觸發init.rc中on事件
}

reserve_adj 在init.rc中通過如下方式設置

on property:sys.sysctl.extra_free_kbytes=*
    write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}

注意: 設置到kernel的minfree是內存頁數,每個內存頁大小爲PAGE_SIZE(4KB)

三、實踐

開發的設備內存比較小,總共1G,可用動態分配只有770,952KB。
monkey煲機,發現很容易死機,log如下

[ 1875.198095] match process is mediaserver ,not kill it!
[ 1875.204129] match process is android.icetech ,not kill it!
[ 1875.210333] BUG: scheduling while atomic: flushcache.sh/9931/0x40000028
[ 1875.217600] Modules linked in: itech_rfid itech_led itech_wiegand bcmdhd mali(O) nand(O)
[ 1875.226864] CPU: 1 PID: 9931 Comm: flushcache.sh Tainted: G           O 3.10.65 #18
[ 1875.235638] [<c0017fc4>] (unwind_backtrace+0x0/0xec) from [<c0014238>] (show_stack+0x20/0x24)
[ 1875.245261] [<c0014238>] (show_stack+0x20/0x24) from [<c06aa4c4>] (dump_stack+0x20/0x28)
[ 1875.254376] [<c06aa4c4>] (dump_stack+0x20/0x28) from [<c06a7818>] (__schedule_bug+0x54/0x6c)
[ 1875.263890] [<c06a7818>] (__schedule_bug+0x54/0x6c) from [<c06ae4a4>] (__schedule+0x80/0x754)
[ 1875.273513] [<c06ae4a4>] (__schedule+0x80/0x754) from [<c0054208>] (__cond_resched+0x20/0x2c)
[ 1875.283111] [<c0054208>] (__cond_resched+0x20/0x2c) from [<c06aec1c>] (_cond_resched+0x40/0x50)
[ 1875.292900] [<c06aec1c>] (_cond_resched+0x40/0x50) from [<c00e5cec>] (shrink_slab+0x29c/0x398)
[ 1875.302609] [<c00e5cec>] (shrink_slab+0x29c/0x398) from [<c0162e34>] (drop_caches_sysctl_handler+0x84/0xa0)
[ 1875.313536] [<c0162e34>] (drop_caches_sysctl_handler+0x84/0xa0) from [<c016e9e0>] (proc_sys_call_handler+0x94/0xb0)
[ 1875.325265] [<c016e9e0>] (proc_sys_call_handler+0x94/0xb0) from [<c016ea20>] (proc_sys_write+0x24/0x2c)
[ 1875.335874] [<c016ea20>] (proc_sys_write+0x24/0x2c) from [<c0117200>] (vfs_write+0xe8/0x184)
[ 1875.345388] [<c0117200>] (vfs_write+0xe8/0x184) from [<c01175a0>] (SyS_write+0x50/0x78)
[ 1875.354403] [<c01175a0>] (SyS_write+0x50/0x78) from [<c000f9e0>] (ret_fast_syscall+0x0/0x30)
[ 1935.389865] INFO: rcu_preempt self-detected stall on CPU { 0}  (t=6000 jiffies g=68818 c=68817 q=1590)
[ 1935.390012] CPU: 0 PID: 9931 Comm: flushcache.sh Tainted: G        W  O 3.10.65 #18
[ 1935.390012] [<c0017fc4>] (unwind_backtrace+0x0/0xec) from [<c0014238>] (show_stack+0x20/0x24)
[ 1935.390012] [<c0014238>] (show_stack+0x20/0x24) from [<c06aa4c4>] (dump_stack+0x20/0x28)
[ 1935.390012] [<c06aa4c4>] (dump_stack+0x20/0x28) from [<c0015be4>] (smp_send_all_cpu_backtrace+0x64/0xd4)
[ 1935.390012] [<c0015be4>] (smp_send_all_cpu_backtrace+0x64/0xd4) from [<c0010da8>] (arch_trigger_all_cpu_backtrace+0x18/0x1c)
[ 1935.390012] [<c0010da8>] (arch_trigger_all_cpu_backtrace+0x18/0x1c) from [<c00a2e1c>] (rcu_check_callbacks+0x280/0x71c)
[ 1935.390012] [<c00a2e1c>] (rcu_check_callbacks+0x280/0x71c) from [<c00346a4>] (update_process_times+0x4c/0x78)
[ 1935.390012] [<c00346a4>] (update_process_times+0x4c/0x78) from [<c0074684>] (tick_sched_handle+0x58/0x64)
[ 1935.390012] [<c0074684>] (tick_sched_handle+0x58/0x64) from [<c0074930>] (tick_sched_timer+0x54/0x84)
[ 1935.390012] [<c0074930>] (tick_sched_timer+0x54/0x84) from [<c004a678>] (__run_hrtimer+0x1b8/0x2d0)
[ 1935.390012] [<c004a678>] (__run_hrtimer+0x1b8/0x2d0) from [<c004b314>] (hrtimer_interrupt+0x148/0x2b4)
[ 1935.390012] [<c004b314>] (hrtimer_interrupt+0x148/0x2b4) from [<c049099c>] (arch_timer_handler_phys+0x38/0x40)

從log看是觸發了內除回收,結合出發前剩餘內存信息,此時可用內存46364KB(此處log未體現),按照lowmemkiller的機制,此時的可用內存應該小於最小可用內存的閾值,影響了前臺進程運行,因此觸發內存壓縮,回收內存。但是設備並沒有配置內存壓縮分區,內存不能再分配,因此崩潰。

查看不同oom_adj對應的minfree

xxxx:/ # cat /sys/module/lowmemorykiller/parameters/minfree
18432,23040,27648,32256,36864,46080

對應的字節數是(內存頁數*4,即KB)

73728,92160,110592,1299024,147456,184320

剩餘內存的確小於了前臺進程對應的可用內存閾值73728,觸發了內存壓縮。設備配置內存本來比較小,沒有必要把閾值配置的很高,由於使用的是默認值,沒有做修改,也沒有打開low_mem,從代碼看low_mem打開會犧牲一些性能(實測如此),因此沒有打開,那麼現在需要修改不同oom_adj對應的可用內存。
通過配置config_lowMemoryKillerMinFreeKbytesAdjust,把各閾值減少爲

xxxx:/ # cat /sys/module/lowmemorykiller/parameters/minfree
9216,11520,13824,16128,18432,23040

對應KB值爲

36864,46080,55296,64512,73728,92160

通過log,5s前的可用內存是87176KB,現在是46364KB,內存跳躍較大(此處應該有異常),查看
/proc/sys/vm/extra_free_kbytes

xxxx:/ # cat /proc/sys/vm/extra_free_kbytes
7200

這個值不是內存頁數,單位是KB,因此只有7M,通過多次可用內存分析,內存需求較大是在20M左右,爲系統穩定不卡頓運行,該值設置爲25M,修改config_extraFreeKbytesAbsolute爲25600即可。

通過修改後,做相同的壓測,系統穩定性提高很多,已煲機21小時,還在煲機中。=_=

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章