為了提高性能,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)) {
---------------------