Android中,进程的生命周期都是由系统控制的。即使用户在界面上关掉一个应用,切换到了别的应用,那个应用的进程依然是存在于内存之中的。这样设计的目的是为了下次启动应用能更加快速。当然,随着系统运行时间的增长,内存中的进程可能会越来越多,而可用的内存则将会越来越少。Android Kernel会定时执行一次检查,杀死一些进程,释放掉内存。
那么,如何来判断,哪些进程是需要杀死的呢?答案就是:low memory killer 机制。
Android 的 low memory killer 是基于 linux 的OOM(out of memory)规则改进而来的。OOM 通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为 bad进程,杀死进程并释放内存。OOM 只有当系统内存不足的时候才会启动检查,而 low memory killer 则不仅是在应用程序分配内存发现内存不足时启动检查,它也会定时地进行检查。
Low memory killer 主要是通过进程的 oom_adj 来判定进程的重要程度的。oom_adj 的大小和进程的类型以及进程被调度的次序有关。
Low memory killer 的具体实现在 kernel,比如对于 android 4.4.3 所用的 kernel_3.4,代码在:linux/kernel/drivers/staging/android/lowmemorykiller.c
。
其原理很简单,在 linux 中,存在一个名为 kswapd 的内核线程,当linux回收存放分页的时候,kswapd 线程将会遍历一张 shrinker 链表,并执行回调,或者某个app分配内存,发现可用内存不足时,则内核会阻塞请求分配内存的进程分配内存的过程,并在该进程中去执行lowmemorykiller来释放内存。
struct shrinker 的定义在 linux/kernel/include/linux/shrinker.h
,具体如下:
所以,只要注册 shrinker,就可以在内存分页回收时根据规则释放内存。下面我们来看看lowmemorykiller 的具体实现。首先是定义 shrinker 结构体,lowmem_shrink 为回调函数的指针,当有内存分页回收的时候,获其他有需要的时机,这个函数将会被调用。
初始化模块时进行注册,模块退出时注销。
Android 中,存在着一张内存阈值表,这张阈值表是可以在 init.rc 中进行配置的,合理配置这张表,对于小内存设备有非常重要的作用。我们来看 lowmemorykiller.c 中这张默认的阈值表:
lowmem_adj 中各项数值代表阈值的警戒级数,lowmem_minfree 代表对应级数的剩余内存。也就是说,当系统的可用内存小于6MB时,警戒级数为0;当系统可用内存小于8M而大于6M时,警戒级数为1;当可用内存小于64M大于16MB时,警戒级数为12。Low memory killer的规则就是根据当前系统的可用内存多少来获取当前的警戒级数,如果进程的oom_adj大于警戒级数并且最大,进程将会被杀死(具有相同omm_adj的进程,则杀死占用内存较多的)。omm_adj越小,代表进程越重要。一些前台的进程,oom_adj会比较小,而后台的服务,omm_adj会比较大,所以当内存不足的时候,Low memory killer必然先杀掉的是后台服务而不是前台的进程。
OK,现在我们来看具体代码,也就是lowmem_shrink这个回调函数,先来:
第22行至第35行。通过global_page_state()函数获取系统当前可用的内存大小,然后根据可用内存大小及内存阈值表,来计算系统当前的警戒等级。
第40行,计算rem值。
第44行至第48行这个if-block,发现此时系统的内存状况还是很不错的,于是就什麽也不做,直接返回了。
第49行至第90行,遍历系统中所有的进程,选中将要杀掉的那个进程。第56行,跳过内核线程,内核线程不参加这个杀进程的游戏。第69行至第73行,进程的oom_score_adj小于警戒阈值,则无视。第74行,获取进程所占用的内存大小,RSS值。第78行至第84行,如果这个进程的oom_score_adj小于我们已经选中的那个进程的oom_score_adj,或者这个进程的oom_score_adj等于我们已经选中的那个进程的oom_score_adj,但其所占用的内存大小tasksize小于我们已经选中的那个进程所占用内存大小,则继续寻找下一个进程。第85至第89行,选中正在遍历的这个的进程,更新selected_tasksize为这个进程所占用的内存大小tasksize,更新selected_oom_score_adj为这个进程的oom_score_adj。
第91行至第99行,杀死选中的进程。先是更新lowmem_deathpending_timeout,然后便是给进程发送一个signal SIGKILL,接下来是设置进程的标记为TIF_MEMDIE,最后则是更新一下rem的值。
adj值的设置
在 lowmemorykiller 的code中我们有看到一个默认的阈值表。如我们前面所提到的那样,各种各样的设备、产品也可以自己定制这个值。这种定制主要是通过导出一些内核变量来实现的,具体代码如下:
lowmemorykiller 在此处总共导出了4个内核变量,其中两个是内核数组。我们可以在如下位置访问到这些内核变量:/sys/module/lowmemorykiller/parameters
。在 init.rc 文件中会设置这些内核变量的属性(system/core/rootdir/init.rc),以便于后面系统对这些值进行修改:
那通常情况下,比如在Nexus 7或类似的设备上,adj值又是在什麽时候设置的呢?设置的那些值依据是什麽呢,是一个经验值呢还是有什麽算法来算出那些值?
搜索android4.4.3_r1.1的整个codebase,我们发现lowmemorykiller所导出的那些值只在ProcessList.updateOomLevels()方法中被修改了。接着我们就来具体看一下这个方法的实现(实现此方法的文件位置为frameworks/base/services/java/com/android/server/am/ProcessList.java):
adj值是一组写死的固定的值,具体可以参考mOomAdj的定义。
第123行至第127行,是第一轮计算各个adj所对应的minimum free memory阈值。计算各个值的算式为(long)(low + ((high-low)scale))。low值和high值都是预定义的固定的经验值,比较关键的是那个scale值。在前面计算scale的部分,我们可以看到,它会先计算一个memory的scale值(为((float)(mTotalMemMb-300))/(700-300)),再计算一个屏幕分辨率的scale值(((float)(displayWidthdisplayHeight)-minSize)/(maxSize-minSize)),首先取scale值为这两个scale值中较大的那一个。再然后是一些容错处理,将scale值限制在0~1之间,以防止设备内存小于300MB,同时设备分辨率小于480800;或者,设备内存大于700MB,或设备分辨率大于1280800的情况出现时,出现太不合理的阈值。
第129行至133行,是第二轮计算各个adj所对应的minimum free memory阈值。计算各个值的算式为(long)((float)minfree_abs * mOomMinFree[i] / mOomMinFree[mOomAdj.length - 1])。此处给了各设备对low memory阈值进行定制的机会。各个设备可以在framework的config文件中定义config_lowMemoryKillerMinFreeKbytesAbsolute,以指定最大的adj所对应的free memory阈值,其他各个adj所对应的free memory阈值将依比例算出。
第135行至第142行,是第三轮计算各个adj所对应的minimum free memory阈值。计算各个值的算式为mOomMinFree[i] += (long)((float)minfree_adj mOomMinFree[i] / mOomMinFree[mOomAdj.length - 1])。此处是给特定设备微调low memory的阈值提供机会。不过我们仔细来看第二轮和第三轮的计算,这两轮计算似乎是可以合并为:(long)((float)(minfree_abs + minfree_adj) mOomMinFree[i] / mOomMinFree[mOomAdj.length - 1])。
后面则是构造适合设置给lowmemorykiller导出参数的字符串,并最终将构造的这组参数设置进去。
那这个ProcessList.updateOomLevels()方法又是在什麽时候会被调用到呢?是在ProcessList.applyDisplaySize()方法中:
在这个方法中,当能够从WMS中获取有效的屏幕分辨率时,会去更新oom levels,并且更新之后就不会再次去更新。我们再来追查ProcessList.applyDisplaySize(),是在ActivityManagerService.updateConfiguration():
我们总结一下OOM levels的更新:时机是在系统启动后,第一个configuration change的消息到AMS的时候;adj值为一组固定的预定义的值;各个adj所对应的min free阈值则根据系统的内存大小和屏幕的分辨率计算得出,各个设备还可以通过framework的config config_lowMemoryKillerMinFreeKbytesAdjust和config_lowMemoryKillerMinFreeKbytesAbsolute来定制最终各个adj所对应的min free阈值。
我们可以根据系统所吐出来的log看一下,事实是否如我们上面对code的分析那样。由某个设备抓到的系统内核吐出来的log,可以发现如下的这些行(只出现一次):
由前面lowmemorykiller的code不难看出,这些log在lowmem_autodetect_oom_adj_values()函数中吐出,是在内核空间吐出来的。吐出这些log的进程的pid为609。我们再来看一下这个进程到底是何方神圣:
谜底揭晓,是system_server。不难推测出来,这应当是在系统启动之后,AMS第一次接到configuration change的消息,去更新OOM level,lowmemorykiller的lowmem_autodetect_oom_adj_values()函数被调到,更新lowmemorykiller的lowmem_adj时而吐出来的。与我们前面对AMS code的分析基本一致。
还需要我们厘清的问题,lowmemorykiller的lowmem_shrink具体在什麽时机会被执行?
Done。