編輯:Android資訊
Android的設計理念之一,便是應用程序退出,但進程還會繼續存在系統以便再次啟動時提高響應時間. 這樣的設計會帶來一個問題, 每個進程都有自己獨立的內存地址空間,隨著應用打開數量的增多,系統已使用的內存越來越大,就很有可能導致系統內存不足, 那麼需要一個能管理所有進程,根據一定策略來釋放進程的策略,這便有了lmk
,全稱為LowMemoryKiller(低內存殺手),lmkd來決定什麼時間殺掉什麼進程.
Android基於Linux的系統,其實Linux有類似的內存管理策略——OOM killer,全稱(Out Of Memory Killer), OOM的策略更多的是用於分配內存不足時觸發,將得分最高的進程殺掉。而lmk
則會每隔一段時間檢查一次,當系統剩余可用內存較低時,便會觸發殺進程的策略,根據不同的剩余內存檔位來來選擇殺不同優先級的進程,而不是等到OOM時再來殺進程,真正OOM時系統可能已經處於異常狀態,系統更希望的是未雨綢缪,在內存很低時來殺掉一些優先級較低的進程來保障後續操作的順利進行。
位於ProcessList.java
中定義了3種命令類型,這些文件的定義必須跟lmkd.c
定義完全一致,格式分別如下:
LMK_TARGET <minfree> <minkillprio> ... (up to 6 pairs) LMK_PROCPRIO <pid> <prio> LMK_PROCREMOVE <pid>
在前面文章Android進程調度之adj算法中有講到AMS.applyOomAdjLocked
,接下來以這個過程為主線開始分析。
private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now, long nowElapsed) { ... if (app.curAdj != app.setAdj) { //【見小節2.2】 ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj); app.setAdj = app.curAdj; } ... }
public static final void setOomAdj(int pid, int uid, int amt) { //當adj=16,則直接返回 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); //將16Byte字節寫入socket【見小節2.3】 writeLmkd(buf); long now = SystemClock.elapsedRealtime(); if ((now-start) > 250) { Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid + " = " + amt); } }
buf大小為16個字節,依次寫入LMK_PROCPRIO(命令類型), pid(進程pid), uid(進程uid), amt(目標adj),將這些字節通過socket發送給lmkd.
private static void writeLmkd(ByteBuffer buf) { //當socket打開失敗會嘗試3次 for (int i = 0; i < 3; i++) { if (sLmkdSocket == null) { //打開socket 【見小節2.4】 if (openLmkdSocket() == false) { try { Thread.sleep(1000); } catch (InterruptedException ie) { } continue; } } try { //將buf信息寫入lmkd socket sLmkdOutputStream.write(buf.array(), 0, buf.position()); return; } catch (IOException ex) { try { sLmkdSocket.close(); } catch (IOException ex2) { } sLmkdSocket = null; } } }
這個重新執行操作最多3次,如果3次後還失敗,則writeLmkd操作會直接結束。嘗試3次,則不管結果如何都將退出該操作,可見writeLmkd寫入操作還有可能失敗的。
private static boolean openLmkdSocket() { try { sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET); //與遠程lmkd守護進程建立socket連接 sLmkdSocket.connect( new LocalSocketAddress("lmkd", LocalSocketAddress.Namespace.RESERVED)); sLmkdOutputStream = sLmkdSocket.getOutputStream(); } catch (IOException ex) { Slog.w(TAG, "lowmemorykiller daemon socket open failed"); sLmkdSocket = null; return false; } return true; }
sLmkdSocket采用的是SOCK_SEQPACKET,這是類型的socket能提供順序確定的,可靠的,雙向基於連接的socket endpoint,與類型SOCK_STREAM很相似,唯一不同的是SEQPACKET保留消息的邊界,而SOCK_STREAM是基於字節流,並不會記錄邊界。
舉例:本地通過write()系統調用向遠程先後發送兩組數據:一組4字節,一組8字節;對於SOCK_SEQPACKET類型通過read()能獲知這是兩組數據以及大小,而對於SOCK_STREAM類型,通過read()一次性讀取到12個字節,並不知道數據包的邊界情況。
常見的數據類型還有SOCK_DGRAM,提供數據報形式,用於udp這樣不可靠的通信過程。
再回到openLmkdSocket()方法,該方法是打開一個名為lmkd
的socket,類型為LocalSocket.SOCKET_SEQPACKET,這只是一個封裝,真實類型就是SOCK_SEQPACKET。先跟遠程lmkd守護進程建立連接,再向其通過write()將數據寫入該socket,再接下來進入lmkd過程。
lmkd是由init進程,通過解析init.rc文件來啟動的lmkd守護進程,lmkd會創建名為lmkd
的socket,節點位於/dev/socket/lmkd
,該socket用於跟上層framework交互。
service lmkd /system/bin/lmkd class core critical socket lmkd seqpacket 0660 system system writepid /dev/cpuset/system-background/tasks
lmkd啟動後,接下裡的操作都在platform/system/core/lmkd/lmkd.c
文件,首先進入main()方法
int main(int argc __unused, char **argv __unused) { struct sched_param param = { .sched_priority = 1, }; mlockall(MCL_FUTURE); sched_setscheduler(0, SCHED_FIFO, ¶m); //初始化【見小節3.2】 if (!init()) mainloop(); //成功後進入loop [見小節3.3] ALOGI("exiting"); return 0; }
static int init(void) { struct epoll_event epev; int i; int ret; page_k = sysconf(_SC_PAGESIZE); if (page_k == -1) page_k = PAGE_SIZE; page_k /= 1024; //創建epoll監聽文件句柄 epollfd = epoll_create(MAX_EPOLL_EVENTS); //獲取lmkd控制描述符 ctrl_lfd = android_get_control_socket("lmkd"); //監聽lmkd socket ret = listen(ctrl_lfd, 1); epev.events = EPOLLIN; epev.data.ptr = (void *)ctrl_connect_handler; //將文件句柄ctrl_lfd,加入epoll句柄 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1) { return -1; } maxevents++; //該路徑是否具有可寫的權限 use_inkernel_interface = !access(INKERNEL_MINFREE_PATH, W_OK); if (use_inkernel_interface) { ALOGI("Using in-kernel low memory killer interface"); } else { ret = init_mp(MEMPRESSURE_WATCH_LEVEL, (void *)&mp_event); if (ret) ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer"); } for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) { procadjslot_list[i].next = &procadjslot_list[i]; procadjslot_list[i].prev = &procadjslot_list[i]; } return 0; }
這裡,通過檢驗/sys/module/lowmemorykiller/parameters/minfree節點是否具有可寫權限來判斷是否使用kernel接口來管理lmk事件。默認該節點是具有系統可寫的權限,也就意味著use_inkernel_interface
=1.
static void mainloop(void) { while (1) { struct epoll_event events[maxevents]; int nevents; int i; ctrl_dfd_reopened = 0; //等待epollfd上的事件 nevents = epoll_wait(epollfd, events, maxevents, -1); if (nevents == -1) { if (errno == EINTR) continue; continue; } for (i = 0; i < nevents; ++i) { if (events[i].events & EPOLLERR) ALOGD("EPOLLERR on event #%d", i); // 當事件到來,則調用ctrl_connect_handler方法 【見小節3.4】 if (events[i].data.ptr) (*(void (*)(uint32_t))events[i].data.ptr)(events[i].events); } } }
主循環調用epoll_wait(),等待epollfd上的事件,當接收到中斷或者不存在事件,則執行continue操作。當事件到來,則 調用的ctrl_connect_handler方法,該方法是由init()過程中設定的方法。
static void ctrl_connect_handler(uint32_t events __unused) { struct epoll_event epev; if (ctrl_dfd >= 0) { ctrl_data_close(); ctrl_dfd_reopened = 1; } ctrl_dfd = accept(ctrl_lfd, NULL, NULL); if (ctrl_dfd < 0) { ALOGE("lmkd control socket accept failed; errno=%d", errno); return; } ALOGI("ActivityManager connected"); maxevents++; epev.events = EPOLLIN; epev.data.ptr = (void *)ctrl_data_handler; //將ctrl_lfd添加到epollfd if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_dfd, &epev) == -1) { ALOGE("epoll_ctl for data connection socket failed; errno=%d", errno); ctrl_data_close(); return; } }
當事件觸發,則調用ctrl_data_handler
static void ctrl_data_handler(uint32_t events) { if (events & EPOLLHUP) { //ActivityManager 連接已斷開 if (!ctrl_dfd_reopened) ctrl_data_close(); } else if (events & EPOLLIN) { //[見小節3.6] ctrl_command_handler(); } }
static void ctrl_command_handler(void) { int ibuf[CTRL_PACKET_MAX / sizeof(int)]; int len; int cmd = -1; int nargs; int targets; len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX); if (len <= 0) return; nargs = len / sizeof(int) - 1; if (nargs < 0) goto wronglen; //將網絡字節順序轉換為主機字節順序 cmd = ntohl(ibuf[0]); switch(cmd) { case LMK_TARGET: targets = nargs / 2; if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj)) goto wronglen; cmd_target(targets, &ibuf[1]); break; case LMK_PROCPRIO: if (nargs != 3) goto wronglen; //設置進程adj【見小節3.7】 cmd_procprio(ntohl(ibuf[1]), ntohl(ibuf[2]), ntohl(ibuf[3])); break; case LMK_PROCREMOVE: if (nargs != 1) goto wronglen; cmd_procremove(ntohl(ibuf[1])); break; default: ALOGE("Received unknown command code %d", cmd); return; } return; wronglen: ALOGE("Wrong control socket read length cmd=%d len=%d", cmd, len); }
CTRL_PACKET_MAX
大小等於 (sizeof(int) * (MAX_TARGETS * 2 + 1));而MAX_TARGETS=6,對於sizeof(int)=4的系統,則CTRL_PACKET_MAX
=52。 獲取framework傳遞過來的buf數據後,根據3種不同的命令,進入不同的分支。 接下來,繼續以前面傳遞過來的LMK_PROCPRIO
命令來往下講解,進入cmd_procprio
過程。
static void cmd_procprio(int pid, int uid, int oomadj) { struct proc *procp; char path[80]; char val[20]; ... snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid); snprintf(val, sizeof(val), "%d", oomadj); //向節點/proc/<pid>/oom_score_adj寫入oomadj writefilestring(path, val); //當使用kernel方式則直接返回 if (use_inkernel_interface) return; procp = pid_lookup(pid); if (!procp) { procp = malloc(sizeof(struct proc)); if (!procp) { // Oh, the irony. May need to rebuild our state. return; } procp->pid = pid; procp->uid = uid; procp->oomadj = oomadj; proc_insert(procp); } else { proc_unslot(procp); procp->oomadj = oomadj; proc_slot(procp); } }
向節點“/proc//oom_score_adj`寫入oomadj。由於use_inkernel_interface=1,那麼再接下裡需要看看kernel的情況
use_inkernel_interface該值後續應該會逐漸采用用戶空間策略。不過目前仍為 use_inkernel_interface=1則有:
/proc/<pid>/oom_score_adj
寫入oomadj,則直接返回;/sys/module/lowmemorykiller/parameters
目錄下的minfree
和adj
節點寫入相應信息;lowmemorykiller driver位於 drivers/staging/Android/lowmemorykiller.c
static struct shrinker lowmem_shrinker = { .scan_objects = lowmem_scan, .count_objects = lowmem_count, .seeks = DEFAULT_SEEKS * 16 }; static int __init lowmem_init(void) { register_shrinker(&lowmem_shrinker); return 0; } static void __exit lowmem_exit(void) { unregister_shrinker(&lowmem_shrinker); } module_init(lowmem_init); module_exit(lowmem_exit);
通過register_shrinker和unregister_shrinker分別用於初始化和退出。
LMK驅動通過注冊shrinker來實現的,shrinker是linux kernel標准的回收內存page的機制,由內核線程kswapd負責監控。
當內存不足時kswapd線程會遍歷一張shrinker鏈表,並回調已注冊的shrinker函數來回收內存page,kswapd還會周期性喚醒來執行內存操作。每個zone維護active_list和inactive_list鏈表,內核根據頁面活動狀態將page在這兩個鏈表之間移動,最終通過shrink_slab和shrink_zone來回收內存頁,有興趣想進一步了解linux內存回收機制,可自行研究,這裡再回到LowMemoryKiller的過程分析。
static unsigned long lowmem_count(struct shrinker *s, struct shrink_control *sc) { return global_page_state(NR_ACTIVE_ANON) + global_page_state(NR_ACTIVE_FILE) + global_page_state(NR_INACTIVE_ANON) + global_page_state(NR_INACTIVE_FILE); }
ANON代表匿名映射,沒有後備存儲器;FILE代表文件映射; 內存計算公式= 活動匿名內存 + 活動文件內存 + 不活動匿名內存 + 不活動文件內存
當觸發lmkd,則先殺oom_adj最大的進程, 當oom_adj相等時,則選擇oom_score_adj最大的進程.
static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc) { struct task_struct *tsk; struct task_struct *selected = NULL; unsigned long rem = 0; int tasksize; int i; short min_score_adj = OOM_SCORE_ADJ_MAX + 1; int minfree = 0; int selected_tasksize = 0; short selected_oom_score_adj; int array_size = ARRAY_SIZE(lowmem_adj); //獲取當前剩余內存大小 int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages; int other_file = global_page_state(NR_FILE_PAGES) - global_page_state(NR_SHMEM) - total_swapcache_pages(); //獲取數組大小 if (lowmem_adj_size < array_size) array_size = lowmem_adj_size; if (lowmem_minfree_size < array_size) array_size = lowmem_minfree_size; //遍歷lowmem_minfree數組找出相應的最小adj值 for (i = 0; i < array_size; i++) { minfree = lowmem_minfree[i]; if (other_free < minfree && other_file < minfree) { min_score_adj = lowmem_adj[i]; break; } } if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) { return 0; } selected_oom_score_adj = min_score_adj; rcu_read_lock(); for_each_process(tsk) { struct task_struct *p; short oom_score_adj; if (tsk->flags & PF_KTHREAD) continue; p = find_lock_task_mm(tsk); if (!p) continue; if (test_tsk_thread_flag(p, TIF_MEMDIE) && time_before_eq(jiffies, lowmem_deathpending_timeout)) { task_unlock(p); rcu_read_unlock(); return 0; } oom_score_adj = p->signal->oom_score_adj; //小於目標adj的進程,則忽略 if (oom_score_adj < min_score_adj) { task_unlock(p); continue; } //獲取的是進程的Resident Set Size,也就是進程獨占內存 + 共享庫大小。 tasksize = get_mm_rss(p->mm); task_unlock(p); if (tasksize <= 0) continue; //算法關鍵,選擇oom_score_adj最大的進程中,並且rss內存最大的進程. if (selected) { if (oom_score_adj < selected_oom_score_adj) continue; if (oom_score_adj == selected_oom_score_adj && tasksize <= selected_tasksize) continue; } selected = p; selected_tasksize = tasksize; selected_oom_score_adj = oom_score_adj; lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n", p->comm, p->pid, oom_score_adj, tasksize); } if (selected) { long cache_size = other_file * (long)(PAGE_SIZE / 1024); long cache_limit = minfree * (long)(PAGE_SIZE / 1024); long free = other_free * (long)(PAGE_SIZE / 1024); lowmem_deathpending_timeout = jiffies + HZ; set_tsk_thread_flag(selected, TIF_MEMDIE); //向選中的目標進程發送signal 9來殺掉目標進程 send_sig(SIGKILL, selected, 0); rem += selected_tasksize; } rcu_read_unlock(); return rem; }
send_sig(SIGKILL, selected, 0)
`向選中的目標進程發送signal 9來殺掉目標進程。另外,lowmem_minfree[]和lowmem_adj[]數組大小個數為6,通過如下兩條命令:
module_param_named(debug_level, lowmem_debug_level, uint, S_IRUGO | S_IWUSR); module_param_array_named(adj, lowmem_adj, short, &lowmem_adj_size, S_IRUGO | S_IWUSR);
當如下節點數據發送變化時,會通過修改lowmem_minfree[]和lowmem_adj[]數組:
/sys/module/lowmemorykiller/parameters/minfree /sys/module/lowmemorykiller/parameters/adj
本文主要從frameworks的ProcessList.java調整adj,通過socket通信將事件發送給native的守護進程lmkd;lmkd再根據具體的命令來執行相應操作,其主要功能 更新進程的oom_score_adj值以及lowmemorykiller驅動的parameters(包括minfree和adj);
最後講到了lowmemorykiller驅動,通過注冊shrinker,借助linux標准的內存回收機制,根據當前系統可用內存以及parameters配置參數(adj,minfree)來選取合適的selected_oom_score_adj,再從所有進程中選擇adj大於該目標值的並且占用rss內存最大的進程,將其殺掉,從而釋放出內存。
oom_adj
:代表進程的優先級, 數值越大,優先級越低,越容易被殺. 取值范圍[-16, 15]oom_score_adj
: 取值范圍[-1000, 1000]想查看某個進程的上述3值,只需要知道pid,查看以下幾個節點:
/proc/<pid>/oom_adj /proc/<pid>/oom_score_adj /proc/<pid>/oom_score
對於oom_adj與oom_score_adj有一定的映射關系:
/sys/module/lowmemorykiller/parameters/minfree (代表page個數) /sys/module/lowmemorykiller/parameters/adj (代表oom_score_adj)
例如:將1,6
寫入節點/sys/module/lowmemorykiller/parameters/adj,將1024,8192
寫入節點/sys/module/lowmemorykiller/parameters/minfree。策略:當系統可用內存低於8192
個pages時,則會殺掉oom_score_adj>=6
的進程;當系統可用內存低於1024
個pages時,則會殺掉oom_score_adj>=1
的進程。
如果Android機頂盒能夠支持IOS設備的Airplay協議,實現為Airplay服務器,能夠接收和處理來自IOS設備的多媒體數據(視頻、照片和音樂),將能提高
Google近期在Udacity上發布了Android性能優化的在線課程,分別從渲染,運算與內存,電量幾個方面介紹了如何去優化性能,這些課程是Google之前在Y
單選按鈕RadioButton在Android平台上也應用的非常多,比如一些選擇項的時候,會用到單選按鈕,實現單選按鈕由兩部分組成,也就是RadioButton和
普渡大學、英特爾公司和 Mobile Enerlytics 的研究人員分析了2000 部 Galaxy S3 和 S4 手機的使用,發現手機上安裝的應用在屏幕關閉