前面进程系列已经更新了七篇,本文(基于kernel 3.18),基于前两篇博客,继续梳理LMK杀进程机制下篇,主要总结LowmemoryKiller的中kernel的原理部分。
Android进程系列第一篇---进程基础
Android进程系列第二篇---Zygote进程的创建流程
Android进程系列第三篇---SystemServer进程的创建流程
Android进程系列第四篇---SystemServer进程的启动流程
Android进程系列第五篇---应用进程的创建流程
Android进程系列第六篇---LowmemoryKiller机制分析(上)
Android进程系列第七篇---LowmemoryKiller机制分析(中)
上文说到如果lmkd.c中的use_inkernel_interface等于1,那么就执行kernel空间的逻辑,lmkd中数据结构也不用更新,也不用lmkd中杀进程的逻辑,全部都交给LowmemoryKiller完成。在正式进入之前,思考几个问题。
- LowmemoryKiller杀进程的策略具体是怎么样的?内存低到什么情况下,LowmemoryKiller开始干活呢?
- 有没有永远也杀不死的进程?
- minfree水位线和对应的adj,应用开发者能不能擅自修改,让自己不易被杀死?
- lmkd担当着AMS到LowmemoryKiller的桥梁,那lmkd进程会不会被自己或者LowmemoryKiller杀了呢?
- 应用开发者如果使得进程活的更好?
下面先整体过一遍LowmemoryKiller的机制,在回头整理这些问题。
一、lowmemorykiller低内存时触发进程查杀
1.1、基本原理
在linux中,有一个名为kswapd的内核线程,当linux回收存放分页的时候,kswapd线程将会遍历一张shrinker链表,并执行回调,或者某个app启动,发现可用内存不足时,则内核会阻塞请求分配内存的进程分配内存的过程,并在该进程中去执行lowmemorykiller来释放内存。虽然之前没有接触过,大体的理解就是向系统注册了这个shrinker回调函数之后,当系统空闲内存页面不足时会调用这个回调函数。 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的注册与反注册
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}
注册完成之后,就回调lowmem_scan,这个基本上是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进程结构体对象
82 struct task_struct *tsk;
//我们需要选择一个进程杀掉,这个selected用来保存不幸中奖的那个进程
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;
//中奖的进程的内存占用大小
89 int selected_tasksize = 0;
//中奖的进程的oom_score_adj值
90 short selected_oom_score_adj;
91 int array_size = ARRAY_SIZE(lowmem_adj);
//global_page_state可以获取当前系统可用的(剩余)内存大小
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数组找出相应的最小adj值,目的就是根据剩余内存的大小,确定当前剩余内存的级别的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 //系统的空闲内存数,根据上面的逻辑判断出,low memory killer需要对adj高于多少(min_adj)的进程进行分析是否释放。
//发现min_score_adj值为OOM_SCORE_ADJ_MAX + 1了,说明当前系统很好,不需要杀进程来释放内存了
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 //内核一种同步机制 -- RCU同步机制
121 rcu_read_lock();
//遍历所有进程
122 for_each_process(tsk) {
123 struct task_struct *p;
124 short oom_score_adj;
125 //内核线程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;
// 如果当前找到的进程的oom_score_adj比当前需要杀的最小优先级还低,不杀
140 if (oom_score_adj < min_score_adj) {
141 task_unlock(p);
142 continue;
143 }
/获取进程的占用内存大小(rss值),也就是进程独占内存 + 共享库大小
144 tasksize = get_mm_rss(p->mm);
145 task_unlock(p);
146 if (tasksize <= 0)
147 continue;
//第一次循环,selected一定是null的
148 if (selected) {
//如果这个进程的oom_score_adj小于我们已经选中的那个进程的oom_score_adj,
//或者这个进程的oom_score_adj等于我们已经选中的那个进程的oom_score_adj,
// 但其所占用的内存大小tasksize小于我们已经选中的那个进程所占用内存大小,则继续寻找下一个进程
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 }
//已经找到了需要寻找的进程,更新它的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,说明已经找到了
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);
//关键打印
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;
//设置进程的标记是TIF_MEMDIE
178 set_tsk_thread_flag(selected, TIF_MEMDIE);
//发送SIGKILL信号,杀死这个进程
179 send_sig(SIGKILL, selected, 0);
//更新一下rem值,杀死了一个进程所释放的内存加上去
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核心原理的实现,可以小总结一下。
- 首先调用global_page_state,可以获取当前系统可用的(剩余)内存大小
- 遍历lowmem_minfree数组,根据other_free和other_file的剩余内存的大小,确定当前剩余内存的最小级别的min_score_adj
- 有了上面的adj之后,遍历所有进程,获取每个进程的rss大小,然后不断循环,每次比较进程占用的内存大小tasksize以及小于oom_score_adj,就能确定最终杀死哪个进程了
- 确定的进程保存在selected变量中,对他发送SIGKILL信号杀死,并且更新rem大小
所以通过上面的总结已经可以回答我们第一个问题,“LowmemoryKiller杀进程的策略具体是怎么样的?内存低到什么情况下,LowmemoryKiller开始干活呢?”
1.2、拓展思考
1.2.1有没有永远也杀不死的进程呢?
要回答这个问题要看从哪个角度了,如果从native进程的角度回答,确实是存在的,我们通过前面几篇的总结了解到,AMS自己会杀死进程,在内存紧张的时候也会通过lmkd请求lmk来杀进程。如果我们写一个native进程同样做到不死忙,比如我们给测试写一个内存加压的程序。因为要给对手机内存加压,要保证这个加压进程不被杀死,即使低内存,即使上层systemui执行一键清理,都能存活,如何做到呢?
int main(int argc, char *argv[]) {
char text[100];
unsigned int size;
int percentage = atoi(argv[1]);
//外面传一个参数进来,占用百分之多少的内存,最高门槛是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进程不被杀死
write(fd, text, strlen(text));
close(fd);
}
size = (unsigned int)mygetsize();
mallocflag(percentage, size); //占用内存
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);
//如果申请失败,尝试申请一般的内存
}
else {
myMalloc(addr, sum);
printf("malloc %d%% success \n", percentage);
system("echo 2 success >> /sdcard/memory_log");
}
}
然后写Android.mk在源码下面编译就行了,其实这么长一段代码,核心的地方就几行,即把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);
}
因为-1000是最小的adj值,即使lmk把其他的进程都杀光了,都不会轮到自己,即使自己的占用的内存很多,其次AMS也监控不到,因为native程序是kernel管理的。当我们把编译好的程序放到system/bin下面就能被上层的APP所使用的,当然这需要Root权限。所以从另外一个角度来讲,对于市面上没有Root权限的APP来说存活手段就比较难了,因为你没有修改adj值的机会。唉,要修改oom_score_adj结点同样需要Root权限,且下次开机就没有效果了。在系统中,有对一些APP保驾护航,比如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,这个相对来说很小了,系统中很多的进程是900+,所以系统中对于重要的进程一般都会加以保护,比如Home进程的adj与调度组都得到了一定的优先。
1.2.2、lmkd会不会被自己杀了呢?
对于这个疑问,我们查看一下lmkd进程的oom_score_adj文件就好了
2|sakura:/proc/589 # cat oom_adj
-17
sakura:/proc/589 # cat oom_score_adj
-1000
值也是-1000,显然不能被自己杀死
1.2.3、给应用开发者的建议
对于App开发者来说,怎么来存活呢,市面上保活手段很多,我之前也总结过Android进程保活的一般套路,感兴趣可以看一看。对于系统来说,对这么多的APP。一碗水得端平了,不偏袒谁,资源的分配策略希望每一个app都能遵守,不要在做什么其他的保活手段,在必要的情况一下,系统也给了一些措施,ActivityManagerService会根据系统内存以及应用的状态通过app.thread.scheduleTrimMemory发送通知给应用程序,App中的onTrimMemory(int level) 和onLowMemory() 就会被回调,而Activity, Service, ContentProvider和Application都实现了这个接口,在回调中我们可以做一些内存释放的操作,这样在同adj的时候,我们的进程就不会被中奖了。
应用处于Runnig状态可能收到的level级别
TRIM_MEMORY_RUNNING_MODERATE 表示系统内存已经稍低
TRIM_MEMORY_RUNNING_LOW 表示系统内存已经相当低
TRIM_MEMORY_RUNNING_CRITICAL 表示系统内存已经非常低,你的应用程序应当考虑释放部分资源
应用的可见性发生变化时收到的级别
TRIM_MEMORY_UI_HIDDEN 表示应用已经处于不可见状态,可以考虑释放一些与显示相关的资源
应用处于后台时可能收到的级别
TRIM_MEMORY_BACKGROUND 表示系统内存稍低,你的应用被杀的可能性不大。但可以考虑适当释放资源
TRIM_MEMORY_MODERATE 表示系统内存已经较低,当内存持续减少,你的应用可能会被杀死
TRIM_MEMORY_COMPLETE 表示系统内存已经非常低,你的应用即将被杀死,请释放所有可能释放的资源
那么我们一般需要释放哪些资源呢?Android代码内存优化建议-OnTrimMemory优化
-
缓存 缓存包括一些文件缓存,图片缓存等,在用户正常使用的时候这些缓存很有作用,但当你的应用程序UI不可见的时候,这些缓存就可以被清除以减少内存的使用.比如第三方图片库的缓存.
-
一些动态生成动态添加的View. 这些动态生成和添加的View且少数情况下才使用到的View,这时候可以被释放,下次使用的时候再进行动态生成即可.比如原生桌面中,会在OnTrimMemory的TRIM_MEMORY_MODERATE等级中,释放所有AppsCustomizePagedView的资源,来保证在低内存的时候,桌面不会轻易被杀掉.
-
最好的办法是用TraceView或者Memrroy Monitor来看哪些对象占用内存大,在决定是否释放。
网友评论