Android用戶空間lowmemorykiller

為了提高性能,Android系統(tǒng)中進程的匿名頁、文件頁按照一定的策略進行緩存,在內存緊張的時候再進行回收。但內存回收并不總是理想的,在一定條件下,為了保證系統(tǒng)的正常運行,會采用更加激進、直接的方式——殺進程,也就是這里要介紹的low memory killer(lmk)。

lmk有內核空間和用戶空間兩種實現(xiàn),如果getprop ro.lmk.enable_userspace_lmk為false,同時/sys/module/lowmemorykiller/parameters/minfree節(jié)點存在且可被lmkd訪問就用內核lmk,否則用后者。本文介紹用戶空間lmk。

注冊監(jiān)聽

1.1 內存壓力監(jiān)聽

用戶空間lmk內存壓力事件上報方式有vmpressure和psi兩種方式,通過屬性ro.lmk.use_psi來控制使用哪個。

a) psi

file: system/core/lmkd/lmkd.c

2252 use_psi_monitors = property_get_bool("ro.lmk.use_psi", true) &&

2253? ? init_psi_monitors();?

2120static bool init_psi_monitors() {

? ? ? ? //注冊3個級別的內存壓力事件監(jiān)聽

2121? ? if (!init_mp_psi(VMPRESS_LEVEL_LOW)) .......

2124? ? if (!init_mp_psi(VMPRESS_LEVEL_MEDIUM)) ......

2128? ? if (!init_mp_psi(VMPRESS_LEVEL_CRITICAL)) ......

2134}

注冊過程如下:

file: system/core/lmkd/lmkd.c

1) 打開/proc/pressure/memory節(jié)點并寫入"stall_type threshold_ms PSI_WINDOW_SIZE_MS"。

PSI_WINDOW_SIZE_MS為1000ms,stall_type和threshold_ms分別為:

170static struct psi_threshold psi_thresholds[VMPRESS_LEVEL_COUNT] = {

171? ? { PSI_SOME, 70 },? ? /* 70ms out of 1sec for partial stall */

172? ? { PSI_SOME, 100 },? /* 100ms out of 1sec for partial stall */

173? ? { PSI_FULL, 70 },? ? /* 70ms out of 1sec for complete stall */

174};

其中partial stall指的是該時間段內有一個或多個task因為缺少資源而等待,

complete stall指的是該時間段內所有的task都因得不到資源而等待。

psi可以是針對system-wide的,也可以是per-cgroup的。

2) 注冊到epoll,回調函數(shù)為mp_event_common,通過data可以得到內存壓力級別。

2097? vmpressure_hinfo[level].handler = mp_event_common;

//level對應VMPRESS_LEVEL_LOW/MEDIUM/CRITICAL

2098? vmpressure_hinfo[level].data = level;

80? ? struct epoll_event epev;

82? ? epev.events = EPOLLPRI;

83? ? epev.data.ptr = &vmpressure_hinfo;

84? ? res = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &epev);

b) vmpressure

file: system/core/lmkd/lmkd.c

//同樣是注冊三個級別的內存壓力監(jiān)聽,而且可以看到psi的優(yōu)先級更高

2255? ? ? ? if (!use_psi_monitors &&

2256? ? ? ? ? ? (!init_mp_common(VMPRESS_LEVEL_LOW) ||

2257? ? ? ? ? ? !init_mp_common(VMPRESS_LEVEL_MEDIUM) ||

2258? ? ? ? ? ? !init_mp_common(VMPRESS_LEVEL_CRITICAL))) {

2260? ? ? ? ? ? return -1;

2261? ? ? ? }

注冊過程如下:

file: system/core/lmkd/lmkd.c

79#define MEMCG_SYSFS_PATH "/dev/memcg/"

2136static bool init_mp_common(enum vmpressure_level level) {

1) // 向/dev/memcg/cgroup.event_control節(jié)點寫入"evfd mpfd levelstr"即可注冊監(jiān)聽,

其中evfd告訴內核事件發(fā)生后通知誰,

mpfd表示監(jiān)聽的是什么事件(這里為memory.pressure_level),

levelstr表示監(jiān)聽內存壓力級別,可以是low,medium,critical

2147? ? mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level",O_RDONLY|O_CLOEXEC);

2153? ? evctlfd = open(MEMCG_SYSFS_PATH "cgroup.event_control",O_WRONLY|O_CLOEXEC);

2159? ? evfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);

2165? ? ret = snprintf(buf, sizeof(buf), "%d %d %s", evfd, mpfd, levelstr);

2171? ? ret = TEMP_FAILURE_RETRY(write(evctlfd, buf, strlen(buf) + 1));

2) //將evfd添加到epoll中,回調函數(shù)為mp_event_common,通過data部分可以知道內存壓力級別。

2178? ? epev.events = EPOLLIN;

2179? ? /* use data to store event level */

2180? ? vmpressure_hinfo[level_idx].data = level_idx;

2181? ? vmpressure_hinfo[level_idx].handler = mp_event_common;

2182? ? epev.data.ptr = (void *)&vmpressure_hinfo[level_idx];

2183? ? ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, evfd, &epev);

2201}

1.2 客戶端socket監(jiān)聽

2203static int init(void) {

2224? ? ctrl_sock.sock = android_get_control_socket("lmkd");

2230? ? ret = listen(ctrl_sock.sock, MAX_DATA_CONN);

2236? ? epev.events = EPOLLIN;

2237? ? ctrl_sock.handler_info.handler = ctrl_connect_handler;

2238? ? epev.data.ptr = (void *)&(ctrl_sock.handler_info);

2239? ? if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_sock.sock, &epev) == -1) {

2. 事件通知

2.1 PSI

使用psi_memstall_enter和psi_memstall_leave包裹相關內存操作,實現(xiàn)事件信息統(tǒng)計。這些操作有以下幾種:

kernel/msm-4.14/mm/vmscan.c

在try_to_free_mem_cgroup_pages函數(shù)中,調用do_try_to_free_pages進行內存回收的時候

kswapd在調用balance_pgdat進行內存回收的時候

kernel/msm-4.14/mm/compaction.c

kcompactd函數(shù)在進行內存壓縮的時候

kernel/msm-4.14/mm/page_alloc.c

調用__alloc_pages_direct_compact進行內存壓縮的時候

調用__perform_reclaim進行內存回收的時候

kernel/msm-4.14/mm/filemap.c

調用wait_on_page_locked,等待文件頁就緒的時候

//滿足注冊條件后開始上報

2.2 vmpressure

通過以下幾條路徑觸發(fā)事件的上報:

kernel/msm-4.14/mm/vmscan.c:

//分配物理頁時,水位不滿足要求,觸發(fā)內存回收時上報

__alloc_pages ->__alloc_pages_nodemask -> get_page_from_freelist

-> node_reclaim -> __node_reclaim -> shrink_node -> vmpressure

//分配物理頁時,進入slow path,進行直接內存回收的過程中上報

__alloc_pages -> __alloc_pages_nodemask -> __alloc_pages_slowpath

-> __alloc_pages_direct_reclaim -> __perform_reclaim -> try_to_free_pages

-> do_try_to_free_pages -> shrink_zones -> shrink_node -> vmpressure

//回收cgroup內存的過程中上報

try_to_free_mem_cgroup_pages -> do_try_to_free_pages -> shrink_zones

-> shrink_node -> vmpressure

//分配物理頁時,進入slow path,喚醒kswapd,kswapd在回收內存的過程中上報

kswapd -> balance_pgdat -> kswapd_shrink_node ->? shrink_node -> vmpressure

//vmpressure中進行事件上報

其中存在變量?vmpressure_win?=?SWAP_CLUSTER_MAX*16;

3. 事件處理

3.1 內存壓力事件處理

lmk使用minfree和oom_score_adj來決定殺進程的策略。比如如下一組配置, /sys/module/lowmemorykiller/parameters/minfree :15360,19200,23040,26880,34415,43737;/sys/module/lowmemorykiller/parameters/adj : 0,1,2,4,9,12 表示當系統(tǒng)可用內存低于26880個page的時候殺oom_score_adj>=4的進程。具體邏輯在回調函數(shù)mp_event_common中,包含兩個步驟,首先根據當前系統(tǒng)的可用內存狀態(tài)定位到minfree的某一檔,進而確定相應的min oom_score_adj,細節(jié)如下代碼所示:

// 其中mi.field是解析/proc/meminfo得到的

1930? ? ? ? other_free = mi.field.nr_free_pages - zi.field.totalreserve_pages;

1931? ? ? ? if (mi.field.nr_file_pages > (mi.field.shmem + mi.field.unevictable +

? ? ? ? ? ? ? ? mi.field.swap_cached)) {

1932? ? ? ? ? ? other_file = (mi.field.nr_file_pages - mi.field.shmem -

1933? ? ? ? ? ? ? ? ? ? ? ? ? mi.field.unevictable - mi.field.swap_cached);

1934? ? ? ? } else {

1935? ? ? ? ? ? other_file = 0;

1936? ? ? ? }

1937

1938? ? ? ? min_score_adj = OOM_SCORE_ADJ_MAX + 1;

1939? ? ? ? for (i = 0; i < lowmem_targets_size; i++) {

1940? ? ? ? ? ? minfree = lowmem_minfree[i];

1941? ? ? ? ? ? if (other_free < minfree && other_file < minfree) {

1942? ? ? ? ? ? ? ? min_score_adj = lowmem_adj[i];

然后就是從OOM_SCORE_ADJ_MAX到min oom_score_adj遍歷,從屬于當前oom_score_adj的進程中選擇一個進行kill,選擇策略有兩種,如果ro.lmk.kill_heaviest_task屬性設為true,則解析/proc/pid/statm選擇占用物理內存最大的進程,否則按照LRU的原則選擇。

3.2 客戶端事件處理

a) minfree和adj

//1. 系統(tǒng)啟動過程中,客戶端向lmkd發(fā)送minfree和adj信息

SystemServer.java : startOtherServices()

-> WindowManagerService.java : displayReady()

-> ActivityTaskManagerService.java : updateConfiguration()

-> ActivityManagerService.java: updateOomLevelsForDisplay()

-> ProcessList.java : applyDisplaySize() -> ProcessList.java :

updateOomLevels() :

601? ? private void updateOomLevels(int displayWidth, int displayHeight,

? ? ? boolean write) {

682? ? ? ? ? ? ByteBuffer buf = ByteBuffer.allocate(4 * (2 * mOomAdj.length + 1));

683? ? ? ? ? ? buf.putInt(LMK_TARGET);

684? ? ? ? ? ? for (int i = 0; i < mOomAdj.length; i++) {

685? ? ? ? ? ? ? ? buf.putInt((mOomMinFree[i] * 1024)/PAGE_SIZE);

686? ? ? ? ? ? ? ? buf.putInt(mOomAdj[i]);

687? ? ? ? ? ? }

689? ? ? ? ? ? writeLmkd(buf, null);

//2. lmkd將接收到的信息,對應寫入/sys/module/lowmemorykiller/parameters/minfree和

? ? /sys/module/lowmemorykiller/parameters/adj節(jié)點

b) app進程修改adj

//1. 不同類型的app進程有著不同的adj,比如FOREGROUND_APP_ADJ為0,SERVICE_ADJ為500等,

// 這樣就可以在內存不足的情況下殺掉不重要的進程來緩解內存壓力。

// 同一個app進程因其狀態(tài)的變化,比如前后臺切換,其adj也是不斷變化的,

// 因此需要調用updateOomAdjLocked來進行更新。

ActivityManagerService.java :

updateOomAdjLocked()

-> OomAdjuster.java : applyOomAdjLocked()

-> ProcessList.java : setOomAdj() :

1185? ? public static void setOomAdj(int pid, int uid, int amt) {

1194? ? ? ? ByteBuffer buf = ByteBuffer.allocate(4 * 4);

1195? ? ? ? buf.putInt(LMK_PROCPRIO);

1196? ? ? ? buf.putInt(pid);

1197? ? ? ? buf.putInt(uid);

1198? ? ? ? buf.putInt(amt);

1199? ? ? ? writeLmkd(buf, null);

//2. 將adj寫入/proc/pid/oom_score_adj節(jié)點

system/core/lmkd/lmkd.c

971? ? case LMK_PROCPRIO:

974? ? ? ? cmd_procprio(packet);

617static void cmd_procprio(LMKD_CTRL_PACKET packet) {

646? ? snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);

647? ? snprintf(val, sizeof(val), "%d", params.oomadj);

648? ? if (!writefilestring(path, val, false)) {

---------------------

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375