Android 6.0的lowmemorykiller机制

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

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

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

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

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

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

查看手机进程oom_adj值
cat /proc/进程id/oom_adj
进程id ps查看

当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()的方法,开始算出相关的阀值.代码如下:

private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
        ……
        final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
 
        for (int i=0; i<mOomAdj.length; i++) {
            int low = mOomMinFreeLow[i];
            int high = mOomMinFreeHigh[i];
            if (is64bit) {
                Slog.i("XXXXXX", "choosing minFree values for 64 Bit");
                // 64位系统中,第4,5等级会略大
                if (i == 4) high = (high*3)/2;
                else if (i == 5) high = (high*7)/4;
            } else {
                Slog.i("XXXXXX", "choosing minFree values for 32 Bit");
                low = mOomMinFreeLow32Bit[i];
                high = mOomMinFreeHigh32Bit[i];
            }           
            // mOomMinFree的值,是这样算出来的。但是scale的值是1,所以实际上他是high的值
            mOomMinFree[i] = (int)(low + ((high-low)*scale));
       }
        //当minfree_abs>=0或minfree_adj!=0时,mOomMinFree的值还要再算一次
        if (minfree_abs >= 0) {
            for (int i=0; i<mOomAdj.length; i++) {
                mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]
                        / mOomMinFree[mOomAdj.length - 1]);                    
            }
        }
 
        if (minfree_adj != 0) {
            for (int i=0; i<mOomAdj.length; i++) {
                mOomMinFree[i] += (int)((float)minfree_adj * mOomMinFree[i]
                        / mOomMinFree[mOomAdj.length - 1]);
                if (mOomMinFree[i] < 0) {
                    mOomMinFree[i] = 0;
                }            }
        }
 
      ……
 
        if (write) {
            ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
            buf.putInt(LMK_TARGET);
            for (int i=0; i<mOomAdj.length; i++) {
                if(i==5){
                    mOomMinFree[i] = 307200;
                }
                buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
                buf.putInt(mOomAdj[i]);
            }
            //调用writeLMkd,将内容传给lmkd.c,写入到minfree文件中。
            writeLmkd(buf);
            SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
        }
    }
上面可以看出,LowMemoryKiller的阀值是通过多个判断然后算出来的。不过,在正常情况下,除了4,5等级之外,LowMemoryKiller的0-3的阀值其实就是mOomMinFreeHigh32Bit[]里面的值。我们以后假如需要对LowMemoryKiller的阀值进行定制,只需要针对这个方法去修改即可。

 

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

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

static void ctrl_connect_handler(uint32_t events __unused) {
    //……
alen = sizeof(addr);
//接收到ActivictyManagerService传过来的阀值
    ctrl_dfd = accept(ctrl_lfd, &addr, &alen);
 
    if (ctrl_dfd < 0) {
        ALOGE("lmkd control socket accept failed; errno=%d", errno);
        return;
    }
 
    ALOGI("ActivityManager connected");
    maxevents++;
epev.events = EPOLLIN;
//将阀值保持进minfree
    epev.data.ptr = (void *)ctrl_data_handler;
//……

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

static void writefilestring(char *path, char *s) {
    int fd = open(path, O_WRONLY);
    int len = strlen(s);
    int ret;
 
    if (fd < 0) {
        ALOGE("Error opening %s; errno=%d", path, errno);
        return;
    }
 
    ret = write(fd, s, len);
    if (ret < 0) {
        ALOGE("Error writing %s; errno=%d", path, errno);
    } else if (ret < len) {
        ALOGE("Short write on %s; length=%d", path, ret);
    }
 
    close(fd);
}

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

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

        if (app == mHomeProcess) {
            if (adj > ProcessList.HOME_APP_ADJ) {
                // This process is hosting what we currently consider to be the
                // home app, so we don't want to let it go into the background.
                adj = ProcessList.HOME_APP_ADJ;
                schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
                app.cached = false;
                app.adjType = "home";
            }
            if (procState > ActivityManager.PROCESS_STATE_HOME) {
                procState = ActivityManager.PROCESS_STATE_HOME;
            }
        }

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

有关LowMemoryKiller的分析就到这里。
--------------------- 
作者:一直熊 
来源:CSDN 
原文:https://blog.csdn.net/u012440406/article/details/51960387 
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值