Android 6.0的lowmemorykiller机制

来源:互联网 发布:php取数组中的最大值 编辑:程序博客网 时间:2024/05/17 04:50

转自:http://blog.csdn.net/u012440406/article/details/51960387

参考:http://www.cnblogs.com/frydsh/p/3735953.html

最近在处理一些lowmemorykiller相关的问题,于是对lowmemorykiller机制作了一个简单的了解。在这里总结一下。

首先,是lowmemorykiller的一些背景知识。

众所周知,Andorid的实质是一个Linux的操作系统。所以和其他操作系统一样,每个程序,每个进程运行,都需要一定内存空间进行支撑。而进程的内存空间只是虚拟内存,程序运行需要的是实实在在的内存(物理内存,即RAM)。所以在必要的时候,操作系统会将程序运行中申请的内存映射到RAM中。RAM作为进程运作不可缺的资源,对系统稳定性有着决定性影响。所以我们必须对内存相关有一个简单直观的认知。进程空间1和RAM之间的关系大致如图:



内存相关的介绍,在这里我只是做一个简单介绍。假如想深入了解请自行了解操作系统相关知识。


简单对内存有一个了解之后,我们来简单介绍一下OOM。


OOM全称Out Of Memory,是linux当中,内存保护机制的一种。该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核将该进程杀掉。

当Kernel遇到OOM的时候,可以有2种选择:

1)  产生kernelpanic(死机)

2)  启动OOM killer,选择一个或多个“合适”的进程,干掉那些选择中的进程,从而释放内存。

 

在OOM机制当中,有几个参数是必须了解的,这几个参数分别是oom_adj,oom_score_adj,oom_score。每个进程都会有这样的3个参数,他们位于/proc/XXX/目录下(XXX为进程的ID)。在Linux中,系统就是通过算分去杀死进程的。至于分数的值,就是这3个参数的值。

简单来说,系统是这样进行算分的:算分主要分2部分,一部分是系统打分,主要根据该进程的内存使用情况(oom_score),另一部分是用户大份额,就是oom_score_adj。每个进程的实际得分是综合这2个参数的值的。而oom_adj只是一个旧的接口参数,在普通的linux系统中和oom_score_adj是差不多的(但是在Android中是很有用的)。


OOM是android的LowMemoryKiller的基础。了解完OOM之后,我们终于可以引入LowMemoryKiller了。

在Android中,及时用户退出当前应用程序后,应用程序还是会存在于系统当中,这是为了方便程序的再次启动。但是这样的话,随着打开的程序的数量的增加,系统的内存就会不足,从而需要杀掉一些进程来释放内存空间。至于是否需要杀进程以及杀什么进程,这个就是由Android的内部机制LowMemoryKiller机制来进行的。

Andorid的Low Memory Killer是在标准的linux lernel的OOM基础上修改而来的一种内存管理机制。当系统内存不足时,杀死不必要的进程释放其内存。不必要的进程的选择根据有2个:oom_adj和占用的内存的大小。oom_adj代表进程的优先级,数值越高,优先级月低,越容易被杀死;对应每个oom_adj都可以有一个空闲进程的阀值。Android Kernel每隔一段时间会检测当前空闲内存是否低于某个阀值。假如是,则杀死oom_adj最大的不必要的进程,如果有多个,就根据oom_score_adj去杀死进程,,直到内存恢复低于阀值的状态。

 

LowMemoryKiller的值的设定,主要保存在2个文件之中,分别是/sys/module/lowmemorykiller/parameters/adj与/sys/module/lowmemorykiller/parameters/minfree。adj保存着当前系统杀进程的等级,minfree则是保存着对应的阀值。他们的对应关系如下:



举个例子说明一下上表,当当前系统内存少于55296×4K(即216MB)时,Android就会找出当前oom_adj≥9的进程,根据进程的等级,先把oom_adj数值最大的进程给杀掉,释放他的内存,当他们的oom_adj相等时,就对比他们的oom_score_adj,然后oom_score_adj越大,也越容易杀掉。


在这里,也许有人会问,为什么采用LowMemoryKiller而不用OOM呢?我们来对比一下两者,就可以得出答案了。



使用LowMemoryKiller可以使系统内存较低时,调出进程管理器结束不必要的人进程释放空间。在安卓中,如果等到真正的OOM时,也许进程管理器就已经没法启动了。

 

上面提到的oom_adj,其实在Android中并不只有6个,在Android 6.0中,一个设置了16个adj,adj的具体设置与描述如下:


对LowMemoryKiller相关的知识简介就到这里。下面我们就主要介绍一下lowMemorykiller的运行原理。


上面其实已经说过,LowMemoryKiller是对多个内存阀值的控制来选择杀进程的。但是,这些阀值是怎样联系在一起的呢?下面就我的理解,简单说一下其运行的原理。

 

首先,LowMemoryKiller是随着系统的启动而启动的。当前主要的LowMemoryKiller的代码主要在\system\core\lmkd的目录下,之前的代码\kernel\drivers\staging\android\lowmemorykiller.c已经不再使用。


LowMemoryKiller在系统启动的时候就已经由init进程一并启动了。LowMemoryKiller启动就是,就会不断监测系统的运行情况和内存情况,当内存少于minfree限定的阀值的时候,lowMemoryKiller遍历当前进程的oom_score_adj,把大于对应阀值的进程进行kill操作。例如,在当前设置中,当系统内存少于315M时,系统就会自动把进程中oom_score_adj的值少于1000的杀掉,当系统内存少于216时,系统就会自动把进程中oom_score_adj的值少于529的杀掉,如此类推。

 

至于oom_adj和oom_score_adj是由谁去控制并写入的呢?

 

在系统当中,oom_adj和oom_score_adj是由ActivityManagerService去控制的,上层应用的启动都离不开AcitivityManagerService的调用与分配资源。有关oom_adj与oom_score_adj会在以后分析ActivityManagerService的时候加入相对详细的论述。在这里就不详细说明。


有关Minfree的值的写入,其实可以找到很多个地方,但是在最开始(还在用lowmemorykiller.c)的时候,是可以在lowmemorykiller.c中设置的。但是现在已经不用lowmemorykiller.c了,所以相对设置的地方也不一样了。经查找验证,Minfree的阀值控制,是由ActivictyManagerService和lowmemorykiller一并控制写入的。

 

在ActivictyManagerService中,会调用ProcessList的applyDisplaySize()方法,从而调用updateOomLevels()的方法,开始算出相关的阀值.代码如下:

[java] view plain copy
  1. private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {  
  2.         ……  
  3.         final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;  
  4.   
  5.         for (int i=0; i<mOomAdj.length; i++) {  
  6.             int low = mOomMinFreeLow[i];  
  7.             int high = mOomMinFreeHigh[i];  
  8.             if (is64bit) {  
  9.                 Slog.i("XXXXXX""choosing minFree values for 64 Bit");  
  10.                 // 64位系统中,第4,5等级会略大  
  11.                 if (i == 4) high = (high*3)/2;  
  12.                 else if (i == 5) high = (high*7)/4;  
  13.             } else {  
  14.                 Slog.i("XXXXXX""choosing minFree values for 32 Bit");  
  15.                 low = mOomMinFreeLow32Bit[i];  
  16.                 high = mOomMinFreeHigh32Bit[i];  
  17.             }             
  18.             // mOomMinFree的值,是这样算出来的。但是scale的值是1,所以实际上他是high的值  
  19.             mOomMinFree[i] = (int)(low + ((high-low)*scale));  
  20.        }  
  21.         //当minfree_abs>=0或minfree_adj!=0时,mOomMinFree的值还要再算一次  
  22.         if (minfree_abs >= 0) {  
  23.             for (int i=0; i<mOomAdj.length; i++) {  
  24.                 mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]  
  25.                         / mOomMinFree[mOomAdj.length - 1]);                      
  26.             }  
  27.         }  
  28.   
  29.         if (minfree_adj != 0) {  
  30.             for (int i=0; i<mOomAdj.length; i++) {  
  31.                 mOomMinFree[i] += (int)((float)minfree_adj * mOomMinFree[i]  
  32.                         / mOomMinFree[mOomAdj.length - 1]);  
  33.                 if (mOomMinFree[i] < 0) {  
  34.                     mOomMinFree[i] = 0;  
  35.                 }            }  
  36.         }  
  37.   
  38.       ……  
  39.   
  40.         if (write) {  
  41.             ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));  
  42.             buf.putInt(LMK_TARGET);  
  43.             for (int i=0; i<mOomAdj.length; i++) {  
  44.                 if(i==5){  
  45.                     mOomMinFree[i] = 307200;  
  46.                 }  
  47.                 buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);  
  48.                 buf.putInt(mOomAdj[i]);  
  49.             }  
  50.             //调用writeLMkd,将内容传给lmkd.c,写入到minfree文件中。  
  51.             writeLmkd(buf);  
  52.             SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));  
  53.         }  
  54.     }  

上面可以看出,LowMemoryKiller的阀值是通过多个判断然后算出来的。不过,在正常情况下,除了4,5等级之外,LowMemoryKiller的0-3的阀值其实就是mOomMinFreeHigh32Bit[]里面的值。我们以后假如需要对LowMemoryKiller的阀值进行定制,只需要针对这个方法去修改即可。

 

另外,刚刚说到,Minfree的阀值控制,是由ActivictyManagerService和LowMemoryKiller一并控制写入的。ActivictyManagerService把值确定了,通过socket机制,和LowMemoryKiller进行通讯,然后LowMemoryKiller就会把值写入minfree中。

在LowMemoryKiller的代码(lmkd.c)中,接受写入minfree的关键代码如下:

[cpp] view plain copy
  1. static void ctrl_connect_handler(uint32_t events __unused) {  
  2.     //……  
  3. alen = sizeof(addr);  
  4. //接收到ActivictyManagerService传过来的阀值  
  5.     ctrl_dfd = accept(ctrl_lfd, &addr, &alen);  
  6.   
  7.     if (ctrl_dfd < 0) {  
  8.         ALOGE("lmkd control socket accept failed; errno=%d", errno);  
  9.         return;  
  10.     }  
  11.   
  12.     ALOGI("ActivityManager connected");  
  13.     maxevents++;  
  14. epev.events = EPOLLIN;  
  15. //将阀值保持进minfree  
  16.     epev.data.ptr = (void *)ctrl_data_handler;  
  17. //……  

ctrl_connect_handler()在接受到数据之后,经过多步的处理最后会调用writefilestring()将值进行写入。

[cpp] view plain copy
  1. static void writefilestring(char *path, char *s) {  
  2.     int fd = open(path, O_WRONLY);  
  3.     int len = strlen(s);  
  4.     int ret;  
  5.   
  6.     if (fd < 0) {  
  7.         ALOGE("Error opening %s; errno=%d", path, errno);  
  8.         return;  
  9.     }  
  10.   
  11.     ret = write(fd, s, len);  
  12.     if (ret < 0) {  
  13.         ALOGE("Error writing %s; errno=%d", path, errno);  
  14.     } else if (ret < len) {  
  15.         ALOGE("Short write on %s; length=%d", path, ret);  
  16.     }  
  17.   
  18.     close(fd);  
  19. }  

Minfree的值是每次开机都会进行写入的,所以假如我们只是单纯地在手机上,直接修改adj和minfree的值,重启之后是不会生效的。

那假如我们想定制某个应用的adj呢?假如只是定制单个应用的adj,其实我们可以在ActivityManagerService中的computeOomAdjLocked方法中进行定制。系统会调用这个方法不停更新正在运行的进程的adj。例如,假如我们想修改launcher默认的adj(默认launcher在后台运行时的adj为6),我们可以在computeOomAdjLocked中的:

[java] view plain copy
  1. if (app == mHomeProcess) {  
  2.     if (adj > ProcessList.HOME_APP_ADJ) {  
  3.         // This process is hosting what we currently consider to be the  
  4.         // home app, so we don't want to let it go into the background.  
  5.         adj = ProcessList.HOME_APP_ADJ;  
  6.         schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;  
  7.         app.cached = false;  
  8.         app.adjType = "home";  
  9.     }  
  10.     if (procState > ActivityManager.PROCESS_STATE_HOME) {  
  11.         procState = ActivityManager.PROCESS_STATE_HOME;  
  12.     }  
  13. }  

进行修改即可。

我们可以通过app这个对象去获得更新的进程的包名以便定制。


有关LowMemoryKiller的分析就到这里。