前面進(jìn)程系列已經(jīng)更新了七篇,本文(基于kernel 3.18)悠垛,基于前兩篇博客油航,繼續(xù)梳理LMK殺進(jìn)程機(jī)制下篇,主要總結(jié)LowmemoryKiller的中kernel的原理部分梳玫。
Android進(jìn)程系列第一篇---進(jìn)程基礎(chǔ)
Android進(jìn)程系列第二篇---Zygote進(jìn)程的創(chuàng)建流程
Android進(jìn)程系列第三篇---SystemServer進(jìn)程的創(chuàng)建流程
Android進(jìn)程系列第四篇---SystemServer進(jìn)程的啟動(dòng)流程
Android進(jìn)程系列第五篇---應(yīng)用進(jìn)程的創(chuàng)建流程
Android進(jìn)程系列第六篇---LowmemoryKiller機(jī)制分析(上)
Android進(jìn)程系列第七篇---LowmemoryKiller機(jī)制分析(中)
上文說(shuō)到如果lmkd.c中的use_inkernel_interface等于1,那么就執(zhí)行kernel空間的邏輯右犹,lmkd中數(shù)據(jù)結(jié)構(gòu)也不用更新提澎,也不用lmkd中殺進(jìn)程的邏輯,全部都交給LowmemoryKiller完成念链。在正式進(jìn)入之前盼忌,思考幾個(gè)問(wèn)題。
- LowmemoryKiller殺進(jìn)程的策略具體是怎么樣的掂墓??jī)?nèi)存低到什么情況下谦纱,LowmemoryKiller開(kāi)始干活呢?
- 有沒(méi)有永遠(yuǎn)也殺不死的進(jìn)程君编?
- minfree水位線和對(duì)應(yīng)的adj跨嘉,應(yīng)用開(kāi)發(fā)者能不能擅自修改,讓自己不易被殺死吃嘿?
- lmkd擔(dān)當(dāng)著AMS到LowmemoryKiller的橋梁祠乃,那lmkd進(jìn)程會(huì)不會(huì)被自己或者LowmemoryKiller殺了呢窘游?
- 應(yīng)用開(kāi)發(fā)者如果使得進(jìn)程活的更好?
下面先整體過(guò)一遍L(zhǎng)owmemoryKiller的機(jī)制跳纳,在回頭整理這些問(wèn)題忍饰。
一、lowmemorykiller低內(nèi)存時(shí)觸發(fā)進(jìn)程查殺
1.1寺庄、基本原理
在linux中艾蓝,有一個(gè)名為kswapd的內(nèi)核線程,當(dāng)linux回收存放分頁(yè)的時(shí)候斗塘,kswapd線程將會(huì)遍歷一張shrinker鏈表赢织,并執(zhí)行回調(diào),或者某個(gè)app啟動(dòng)馍盟,發(fā)現(xiàn)可用內(nèi)存不足時(shí)于置,則內(nèi)核會(huì)阻塞請(qǐng)求分配內(nèi)存的進(jìn)程分配內(nèi)存的過(guò)程,并在該進(jìn)程中去執(zhí)行l(wèi)owmemorykiller來(lái)釋放內(nèi)存贞岭。雖然之前沒(méi)有接觸過(guò)八毯,大體的理解就是向系統(tǒng)注冊(cè)了這個(gè)shrinker回調(diào)函數(shù)之后,當(dāng)系統(tǒng)空閑內(nèi)存頁(yè)面不足時(shí)會(huì)調(diào)用這個(gè)回調(diào)函數(shù)瞄桨。 struct shrinker的定義在linux/kernel/include/linux/shrinker.h中:
http://androidxref.com/kernel_3.18/xref/include/linux/shrinker.h
48struct shrinker {
49 unsigned long (*count_objects)(struct shrinker *,
50 struct shrink_control *sc);
51 unsigned long (*scan_objects)(struct shrinker *,
52 struct shrink_control *sc);
53
54 int seeks; /* seeks to recreate an obj */
55 long batch; /* reclaim batch size, 0 = default */
56 unsigned long flags;
57
58 /* These are for internal use */
59 struct list_head list;
60 /* objs pending delete, per node */
61 atomic_long_t *nr_deferred;
62};
63#define DEFAULT_SEEKS 2 /* A good number if you don't know better. */
64
65/* Flags */
66#define SHRINKER_NUMA_AWARE (1 << 0)
67
68extern int register_shrinker(struct shrinker *);
69extern void unregister_shrinker(struct shrinker *);
70#endif
71
shrinker的注冊(cè)與反注冊(cè)
http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c
189static struct shrinker lowmem_shrinker = {
190 .scan_objects = lowmem_scan,
191 .count_objects = lowmem_count,
192 .seeks = DEFAULT_SEEKS * 16
193};
http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c
195static int __init lowmem_init(void)
196{
197 register_shrinker(&lowmem_shrinker);
198 return 0;
199}
200
201static void __exit lowmem_exit(void)
202{
203 unregister_shrinker(&lowmem_shrinker);
204}
注冊(cè)完成之后话速,就回調(diào)lowmem_scan,這個(gè)基本上是lmk核心的代碼
http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c
80static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
81{
//tsk進(jìn)程結(jié)構(gòu)體對(duì)象
82 struct task_struct *tsk;
//我們需要選擇一個(gè)進(jìn)程殺掉芯侥,這個(gè)selected用來(lái)保存不幸中獎(jiǎng)的那個(gè)進(jìn)程
83 struct task_struct *selected = NULL;
84 unsigned long rem = 0;
85 int tasksize;
86 int i;
// OOM_SCORE_ADJ_MAX = 1000
87 short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
88 int minfree = 0;
//中獎(jiǎng)的進(jìn)程的內(nèi)存占用大小
89 int selected_tasksize = 0;
//中獎(jiǎng)的進(jìn)程的oom_score_adj值
90 short selected_oom_score_adj;
91 int array_size = ARRAY_SIZE(lowmem_adj);
//global_page_state可以獲取當(dāng)前系統(tǒng)可用的(剩余)內(nèi)存大小
92 int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
93 int other_file = global_page_state(NR_FILE_PAGES) -
94 global_page_state(NR_SHMEM) -
95 total_swapcache_pages();
96
97 if (lowmem_adj_size < array_size)
98 array_size = lowmem_adj_size;
99 if (lowmem_minfree_size < array_size)
100 array_size = lowmem_minfree_size;
// 遍歷lowmem_minfree數(shù)組找出相應(yīng)的最小adj值泊交,目的就是根據(jù)剩余內(nèi)存的大小,確定當(dāng)前剩余內(nèi)存的級(jí)別的adj
101 for (i = 0; i < array_size; i++) {
102 minfree = lowmem_minfree[i];
103 if (other_free < minfree && other_file < minfree) {
104 min_score_adj = lowmem_adj[i];
105 break;
106 }
107 }
108
109 lowmem_print(3, "lowmem_scan %lu, %x, ofree %d %d, ma %hd\n",
110 sc->nr_to_scan, sc->gfp_mask, other_free,
111 other_file, min_score_adj);
112 //系統(tǒng)的空閑內(nèi)存數(shù)柱查,根據(jù)上面的邏輯判斷出廓俭,low memory killer需要對(duì)adj高于多少(min_adj)的進(jìn)程進(jìn)行分析是否釋放。
//發(fā)現(xiàn)min_score_adj值為OOM_SCORE_ADJ_MAX + 1了唉工,說(shuō)明當(dāng)前系統(tǒng)很好研乒,不需要?dú)⑦M(jìn)程來(lái)釋放內(nèi)存了
113 if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
114 lowmem_print(5, "lowmem_scan %lu, %x, return 0\n",
115 sc->nr_to_scan, sc->gfp_mask);
116 return 0;
117 }
118
119 selected_oom_score_adj = min_score_adj;
120 //內(nèi)核一種同步機(jī)制 -- RCU同步機(jī)制
121 rcu_read_lock();
//遍歷所有進(jìn)程
122 for_each_process(tsk) {
123 struct task_struct *p;
124 short oom_score_adj;
125 //內(nèi)核線程kthread
126 if (tsk->flags & PF_KTHREAD)
127 continue;
128
129 p = find_lock_task_mm(tsk);
130 if (!p)
131 continue;
132
133 if (test_tsk_thread_flag(p, TIF_MEMDIE) &&
134 time_before_eq(jiffies, lowmem_deathpending_timeout)) {
135 task_unlock(p);
136 rcu_read_unlock();
137 return 0;
138 }
139 oom_score_adj = p->signal->oom_score_adj;
// 如果當(dāng)前找到的進(jìn)程的oom_score_adj比當(dāng)前需要?dú)⒌淖钚?yōu)先級(jí)還低,不殺
140 if (oom_score_adj < min_score_adj) {
141 task_unlock(p);
142 continue;
143 }
/獲取進(jìn)程的占用內(nèi)存大小(rss值)酵紫,也就是進(jìn)程獨(dú)占內(nèi)存 + 共享庫(kù)大小
144 tasksize = get_mm_rss(p->mm);
145 task_unlock(p);
146 if (tasksize <= 0)
147 continue;
//第一次循環(huán)告嘲,selected一定是null的
148 if (selected) {
//如果這個(gè)進(jìn)程的oom_score_adj小于我們已經(jīng)選中的那個(gè)進(jìn)程的oom_score_adj,
//或者這個(gè)進(jìn)程的oom_score_adj等于我們已經(jīng)選中的那個(gè)進(jìn)程的oom_score_adj奖地,
// 但其所占用的內(nèi)存大小tasksize小于我們已經(jīng)選中的那個(gè)進(jìn)程所占用內(nèi)存大小,則繼續(xù)尋找下一個(gè)進(jìn)程
149 if (oom_score_adj < selected_oom_score_adj)
150 continue;
151 if (oom_score_adj == selected_oom_score_adj &&
152 tasksize <= selected_tasksize)
153 continue;
154 }
//已經(jīng)找到了需要尋找的進(jìn)程赋焕,更新它的tasksize與oom_score_adj
155 selected = p;
156 selected_tasksize = tasksize;
157 selected_oom_score_adj = oom_score_adj;
158 lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n",
159 p->comm, p->pid, oom_score_adj, tasksize);
160 }
//selected非null参歹,說(shuō)明已經(jīng)找到了
161 if (selected) {
162 long cache_size = other_file * (long)(PAGE_SIZE / 1024);
163 long cache_limit = minfree * (long)(PAGE_SIZE / 1024);
164 long free = other_free * (long)(PAGE_SIZE / 1024);
165 trace_lowmemory_kill(selected, cache_size, cache_limit, free);
//關(guān)鍵打印
166 lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \
167 " to free %ldkB on behalf of '%s' (%d) because\n" \
168 " cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \
169 " Free memory is %ldkB above reserved\n",
170 selected->comm, selected->pid,
171 selected_oom_score_adj,
172 selected_tasksize * (long)(PAGE_SIZE / 1024),
173 current->comm, current->pid,
174 cache_size, cache_limit,
175 min_score_adj,
176 free);
//更新lowmem_deathpending_timeout
177 lowmem_deathpending_timeout = jiffies + HZ;
//設(shè)置進(jìn)程的標(biāo)記是TIF_MEMDIE
178 set_tsk_thread_flag(selected, TIF_MEMDIE);
//發(fā)送SIGKILL信號(hào),殺死這個(gè)進(jìn)程
179 send_sig(SIGKILL, selected, 0);
//更新一下rem值隆判,殺死了一個(gè)進(jìn)程所釋放的內(nèi)存加上去
180 rem += selected_tasksize;
181 }
182
183 lowmem_print(4, "lowmem_scan %lu, %x, return %lu\n",
184 sc->nr_to_scan, sc->gfp_mask, rem);
185 rcu_read_unlock();
186 return rem;
187}
上面代碼就是lowmemorykiller核心原理的實(shí)現(xiàn)犬庇,可以小總結(jié)一下僧界。
- 首先調(diào)用global_page_state,可以獲取當(dāng)前系統(tǒng)可用的(剩余)內(nèi)存大小
- 遍歷lowmem_minfree數(shù)組,根據(jù)other_free和other_file的剩余內(nèi)存的大小臭挽,確定當(dāng)前剩余內(nèi)存的最小級(jí)別的min_score_adj
- 有了上面的adj之后捂襟,遍歷所有進(jìn)程,獲取每個(gè)進(jìn)程的rss大小,然后不斷循環(huán)欢峰,每次比較進(jìn)程占用的內(nèi)存大小tasksize以及小于oom_score_adj葬荷,就能確定最終殺死哪個(gè)進(jìn)程了
- 確定的進(jìn)程保存在selected變量中,對(duì)他發(fā)送SIGKILL信號(hào)殺死纽帖,并且更新rem大小
所以通過(guò)上面的總結(jié)已經(jīng)可以回答我們第一個(gè)問(wèn)題宠漩,“LowmemoryKiller殺進(jìn)程的策略具體是怎么樣的??jī)?nèi)存低到什么情況下懊直,LowmemoryKiller開(kāi)始干活呢扒吁?”
1.2、拓展思考
1.2.1有沒(méi)有永遠(yuǎn)也殺不死的進(jìn)程呢室囊?
要回答這個(gè)問(wèn)題要看從哪個(gè)角度了雕崩,如果從native進(jìn)程的角度回答,確實(shí)是存在的融撞,我們通過(guò)前面幾篇的總結(jié)了解到晨逝,AMS自己會(huì)殺死進(jìn)程,在內(nèi)存緊張的時(shí)候也會(huì)通過(guò)lmkd請(qǐng)求lmk來(lái)殺進(jìn)程懦铺。如果我們寫一個(gè)native進(jìn)程同樣做到不死忙捉貌,比如我們給測(cè)試寫一個(gè)內(nèi)存加壓的程序。因?yàn)橐o對(duì)手機(jī)內(nèi)存加壓冬念,要保證這個(gè)加壓進(jìn)程不被殺死趁窃,即使低內(nèi)存,即使上層systemui執(zhí)行一鍵清理急前,都能存活醒陆,如何做到呢?
int main(int argc, char *argv[]) {
char text[100];
unsigned int size;
int percentage = atoi(argv[1]);
//外面?zhèn)饕粋€(gè)參數(shù)進(jìn)來(lái)裆针,占用百分之多少的內(nèi)存刨摩,最高門檻是60%
if (percentage >= 0 && percentage <= 60) {
printf("Memory footprint %d%%\n", percentage);
} else {
printf("Memory footprint %d%% error!! must be in range of 0-60%%\n", percentage);
return 0;
}
//修改oom_score_adj為-1000
sprintf(text, "/proc/%d/oom_score_adj", getpid());
int fd = open(text, O_WRONLY);
if (fd >= 0) {
sprintf(text, "%d", -1000); //讓自己不被殺死
write(fd, text, strlen(text));
close(fd);
}
char task_name[50];
char *pid = (char*)calloc(10,sizeof(char));
strcpy(task_name, "logcat");
sprintf(text, "/proc/%s/oom_score_adj", pid);
fd = open(text, O_WRONLY);
if (fd >= 0) {
sprintf(text, "%d", -1000); //讓logcat進(jìn)程不被殺死
write(fd, text, strlen(text));
close(fd);
}
size = (unsigned int)mygetsize();
mallocflag(percentage, size); //占用內(nèi)存
while(1) {//等待正常退出
sleep(3);
if((access("/sdcard/finishflag",F_OK)) == 0) {
printf("create memroy process now end.......\n");
free(addr);
addr = NULL;
break;
}
}
free(pid);
return 0;
}
void mallocflag(int percentage, unsigned int size) {
int i = 0;
if(addr != NULL) {
free(addr);
addr = NULL;
}
printf("size= %d kb percentage=%d\n",size,percentage);
float s=(float) size/1024/1024;
printf("phone mem size %.2f G \n",s);
float p =(float)percentage/100;
printf("p %.2f rate\n",p);
int sum=s*p*1204*1024*1024;
printf("sum %d bye",sum);
printf(" will malloc %d%% size = %d kb\n",percentage, sum);
addr = (char *)malloc(sum);
printf("malloc %d%% size = %dM\n",percentage, sum/1024/1024);
system("echo 2 start >> /sdcard/memory_log");
if(addr == NULL) {
printf("malloc %d%% fail \n", percentage);
system("echo 2 fail >> /sdcard/memory_log");
exit(0);
//如果申請(qǐng)失敗,嘗試申請(qǐng)一般的內(nèi)存
}
else {
myMalloc(addr, sum);
printf("malloc %d%% success \n", percentage);
system("echo 2 success >> /sdcard/memory_log");
}
}
然后寫Android.mk在源碼下面編譯就行了世吨,其實(shí)這么長(zhǎng)一段代碼澡刹,核心的地方就幾行,即把oom_score_adj修改為-1000
//修改oom_score_adj為-1000
sprintf(text, "/proc/%d/oom_score_adj", getpid());
int fd = open(text, O_WRONLY);
if (fd >= 0) {
sprintf(text, "%d", -1000); //讓自己不被殺死
write(fd, text, strlen(text));
close(fd);
}
因?yàn)?1000是最小的adj值耘婚,即使lmk把其他的進(jìn)程都?xì)⒐饬税战剑疾粫?huì)輪到自己,即使自己的占用的內(nèi)存很多,其次AMS也監(jiān)控不到嚷闭,因?yàn)閚ative程序是kernel管理的攒岛。當(dāng)我們把編譯好的程序放到system/bin下面就能被上層的APP所使用的,當(dāng)然這需要Root權(quán)限胞锰。所以從另外一個(gè)角度來(lái)講灾锯,對(duì)于市面上沒(méi)有Root權(quán)限的APP來(lái)說(shuō)存活手段就比較難了,因?yàn)槟銢](méi)有修改adj值的機(jī)會(huì)嗅榕。唉顺饮,要修改oom_score_adj結(jié)點(diǎn)同樣需要Root權(quán)限,且下次開(kāi)機(jī)就沒(méi)有效果了誊册。在系統(tǒng)中领突,有對(duì)一些APP保駕護(hù)航,比如Home案怯。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
24049 if (app == mHomeProcess) {
24050 if (adj > ProcessList.HOME_APP_ADJ) {
24051 // This process is hosting what we currently consider to be the
24052 // home app, so we don't want to let it go into the background.
24053 adj = ProcessList.HOME_APP_ADJ;
24054 schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
24055 app.cached = false;
24056 app.adjType = "home";
24057 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
24058 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
24059 }
24060 }
24061 if (procState > ActivityManager.PROCESS_STATE_HOME) {
24062 procState = ActivityManager.PROCESS_STATE_HOME;
24063 app.adjType = "home";
24064 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
24065 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app);
24066 }
24067 }
24068 }
HOME_APP_ADJ的值是600君旦,這個(gè)相對(duì)來(lái)說(shuō)很小了,系統(tǒng)中很多的進(jìn)程是900+嘲碱,所以系統(tǒng)中對(duì)于重要的進(jìn)程一般都會(huì)加以保護(hù)金砍,比如Home進(jìn)程的adj與調(diào)度組都得到了一定的優(yōu)先。
1.2.2麦锯、lmkd會(huì)不會(huì)被自己殺了呢恕稠?
對(duì)于這個(gè)疑問(wèn),我們查看一下lmkd進(jìn)程的oom_score_adj文件就好了
2|sakura:/proc/589 # cat oom_adj
-17
sakura:/proc/589 # cat oom_score_adj
-1000
值也是-1000扶欣,顯然不能被自己殺死
1.2.3鹅巍、給應(yīng)用開(kāi)發(fā)者的建議
對(duì)于App開(kāi)發(fā)者來(lái)說(shuō),怎么來(lái)存活呢料祠,市面上甭媾酰活手段很多,我之前也總結(jié)過(guò)Android進(jìn)程彼枵溃活的一般套路敛苇,感興趣可以看一看。對(duì)于系統(tǒng)來(lái)說(shuō)顺呕,對(duì)這么多的APP枫攀。一碗水得端平了,不偏袒誰(shuí)株茶,資源的分配策略希望每一個(gè)app都能遵守来涨,不要在做什么其他的保活手段忌卤,在必要的情況一下扫夜,系統(tǒng)也給了一些措施,ActivityManagerService會(huì)根據(jù)系統(tǒng)內(nèi)存以及應(yīng)用的狀態(tài)通過(guò)app.thread.scheduleTrimMemory發(fā)送通知給應(yīng)用程序驰徊,App中的onTrimMemory(int level) 和onLowMemory() 就會(huì)被回調(diào)笤闯,而Activity, Service, ContentProvider和Application都實(shí)現(xiàn)了這個(gè)接口,在回調(diào)中我們可以做一些內(nèi)存釋放的操作棍厂,這樣在同adj的時(shí)候颗味,我們的進(jìn)程就不會(huì)被中獎(jiǎng)了。
應(yīng)用處于Runnig狀態(tài)可能收到的level級(jí)別
TRIM_MEMORY_RUNNING_MODERATE 表示系統(tǒng)內(nèi)存已經(jīng)稍低
TRIM_MEMORY_RUNNING_LOW 表示系統(tǒng)內(nèi)存已經(jīng)相當(dāng)?shù)?br>
TRIM_MEMORY_RUNNING_CRITICAL 表示系統(tǒng)內(nèi)存已經(jīng)非常低牺弹,你的應(yīng)用程序應(yīng)當(dāng)考慮釋放部分資源
應(yīng)用的可見(jiàn)性發(fā)生變化時(shí)收到的級(jí)別
TRIM_MEMORY_UI_HIDDEN 表示應(yīng)用已經(jīng)處于不可見(jiàn)狀態(tài)浦马,可以考慮釋放一些與顯示相關(guān)的資源
應(yīng)用處于后臺(tái)時(shí)可能收到的級(jí)別
TRIM_MEMORY_BACKGROUND 表示系統(tǒng)內(nèi)存稍低,你的應(yīng)用被殺的可能性不大张漂。但可以考慮適當(dāng)釋放資源
TRIM_MEMORY_MODERATE 表示系統(tǒng)內(nèi)存已經(jīng)較低晶默,當(dāng)內(nèi)存持續(xù)減少,你的應(yīng)用可能會(huì)被殺死
TRIM_MEMORY_COMPLETE 表示系統(tǒng)內(nèi)存已經(jīng)非常低航攒,你的應(yīng)用即將被殺死磺陡,請(qǐng)釋放所有可能釋放的資源
那么我們一般需要釋放哪些資源呢?Android代碼內(nèi)存優(yōu)化建議-OnTrimMemory優(yōu)化
緩存 緩存包括一些文件緩存漠畜,圖片緩存等币他,在用戶正常使用的時(shí)候這些緩存很有作用,但當(dāng)你的應(yīng)用程序UI不可見(jiàn)的時(shí)候憔狞,這些緩存就可以被清除以減少內(nèi)存的使用.比如第三方圖片庫(kù)的緩存.
一些動(dòng)態(tài)生成動(dòng)態(tài)添加的View. 這些動(dòng)態(tài)生成和添加的View且少數(shù)情況下才使用到的View蝴悉,這時(shí)候可以被釋放,下次使用的時(shí)候再進(jìn)行動(dòng)態(tài)生成即可.比如原生桌面中瘾敢,會(huì)在OnTrimMemory的TRIM_MEMORY_MODERATE等級(jí)中拍冠,釋放所有AppsCustomizePagedView的資源,來(lái)保證在低內(nèi)存的時(shí)候簇抵,桌面不會(huì)輕易被殺掉.
最好的辦法是用TraceView或者M(jìn)emrroy Monitor來(lái)看哪些對(duì)象占用內(nèi)存大庆杜,在決定是否釋放。