Android后台杀死系列之三:LowMemoryKiller原理(4.3-6.0)下篇

猪小花1号2018-08-29 13:49

作者:李尚


Android5.0之后框架层的实现:LMKD服务

Android5.0将设置进程优先级的入口封装成了一个独立的服务lmkd服务,AMS不再直接访问proc文件系统,而是通过lmkd服务来进行设置,从init.rc文件中看到服务的配置。

service lmkd /system/bin/lmkd
    class core
    critical
    socket lmkd seqpacket 0660 system system

从配置中可以看出,该服务是通过socket与其他进行进程进行通信,其实就是AMS通过socket向lmkd服务发送请求,让lmkd去更新进程的优先级,lmkd收到请求后,会通过/proc文件系统去更新内核中的进程优先级。首先看一下5.0中这一块AMS有什么改变,其实大部分流程跟之前4.3源码类似,我们只看一下不同地方

ActivityManagerService

private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
        ProcessRecord TOP_APP, boolean doingAll, long now) {
    ...
    computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
    ...
    applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
}

private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
        long nowElapsed) {
    boolean success = true;

    if (app.curRawAdj != app.setRawAdj) {
        app.setRawAdj = app.curRawAdj;
    }

    int changes = 0;
      不同点1
    if (app.curAdj != app.setAdj) {
        ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
        if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
                "Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": "
                + app.adjType);
        app.setAdj = app.curAdj;
        app.verifiedAdj = ProcessList.INVALID_ADJ;
    }

从上面的不同点1可以看出,5.0之后是通过ProcessList类去设置oomAdj,其实这里就是通过socket与LMKD服务进行通信,向lmkd服务传递给LMK_PROCPRIO命令去更新进程优先级:

public static final void setOomAdj(int pid, int uid, int amt) {
    if (amt == UNKNOWN_ADJ)
        return;
   long start = SystemClock.elapsedRealtime();
    ByteBuffer buf = ByteBuffer.allocate(4 * 4);
    buf.putInt(LMK_PROCPRIO);
    buf.putInt(pid);
    buf.putInt(uid);
    buf.putInt(amt);
    writeLmkd(buf);
    long now = SystemClock.elapsedRealtime();
      }    

private static void writeLmkd(ByteBuffer buf) {
         for (int i = 0; i < 3; i++) {
        if (sLmkdSocket == null) {
          if (openLmkdSocket() == false) {
            ...
        try {
            sLmkdOutputStream.write(buf.array(), 0, buf.position());
            return;
            ...
    }

其实就是openLmkdSocket打开本地socket端口,并将优先级信息发送过去,那么lmkd服务端如何处理的呢,init.rc里配置的服务是在开机时启动的,来看看lmkd服务的入口:main函数

lmkd.c函数

int main(int argc __unused, char **argv __unused) {
    struct sched_param param = {
            .sched_priority = 1,
    };

    mlockall(MCL_FUTURE);
    sched_setscheduler(0, SCHED_FIFO, &param);
    if (!init())
        mainloop();

    ALOGI("exiting");
    return 0;
}

很简单,打开一个端口,并通过mainloop监听socket,如果有请求到来,就解析命令并执行,刚才传入的LMK_PROCPRIO命令对应的操作就是cmd_procprio,用来更新oomAdj,其更新新机制还是通过proc文件系统,不信?看下面代码:

static void cmd_procprio(int pid, int uid, int oomadj) {
    struct proc *procp;
    。。。
    还是利用/proc文件系统进行更新
    snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid);
    snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj));
    writefilestring(path, val);
   。。。
}

简单的流程图如下,同4.3不同的地方


以上就分析完了用户空间的操作如何影响到进程的优先级,并且将新的优先级写到内核中。最后看一下LomemoryKiller在什么时候、如何根据优先级杀死进程的:

LomemoryKiller内核部分:如何杀死

LomemoryKiller属于一个内核驱动模块,主要功能是:在系统内存不足的时候扫描进程队列,找到低优先级(也许说性价比低更合适)的进程并杀死,以达到释放内存的目的。对于驱动程序,入口是__init函数,先看一下这个驱动模块的入口:

static int __init lowmem_init(void)
{
    register_shrinker(&lowmem_shrinker);
    return 0;
}

可以看到在init的时候,LomemoryKiller将自己的lowmem_shrinker入口注册到系统的内存检测模块去,作用就是在内存不足的时候可以被回调,register_shrinker函数是一属于另一个内存管理模块的函数,如果一定要根下去的话,可以看一下它的定义,其实就是加到一个回调函数队列中去:

void register_shrinker(struct shrinker *shrinker)
{
    shrinker->nr = 0;
    down_write(&shrinker_rwsem);
    list_add_tail(&shrinker->list, &shrinker_list);
    up_write(&shrinker_rwsem);
}

最后,看一下,当内存不足触发回调的时候,LomemoryKiller是如何找到低优先级进程,并杀死的:入口函数就是init时候注册的lowmem_shrink函数(4.3源码,后面的都有微调但原理大概类似):

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
    struct task_struct *p;
    。。。
    关键点1 找到当前的内存对应的阈值
    for(i = 0; i < array_size; i++) {
        if (other_free < lowmem_minfree[i] &&
            other_file < lowmem_minfree[i]) {
            min_adj = lowmem_adj[i];
            break;
        }
    }
    。。。
    关键点2 找到优先级低于这个阈值的进程,并杀死

    read_lock(&tasklist_lock);
    for_each_process(p) {
        if (p->oomkilladj < min_adj || !p->mm)
            continue;
        tasksize = get_mm_rss(p->mm);
        if (tasksize <= 0)
            continue;
        if (selected) {
            if (p->oomkilladj < selected->oomkilladj)
                continue;
            if (p->oomkilladj == selected->oomkilladj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;

    }
    if(selected != NULL) {
        force_sig(SIGKILL, selected);
        rem -= selected_tasksize;
    }
    lowmem_print(4, "lowmem_shrink %d, %x, return %d\n", nr_to_scan, gfp_mask, rem);
    read_unlock(&tasklist_lock);
    return rem;
}

先看关键点1:其实就是确定当前低内存对应的阈值;关键点2 :找到比该阈值优先级低或者相等,并且内存占用较多的进程(tasksize = get_mm_rss(p->mm)其实就是获取内存占用)),将其杀死。如何杀死的呢?很直接,通过Linux的中的信号量,发送SIGKILL信号直接将进程杀死。到这就分析完了LomemoryKiller内核部分如何工作的。其实很简单,一句话:被动扫描,找到低优先级的进程,杀死。

总结

通过本篇文章,希望大家能有以下几点认知:

  • Android APP进程是有优先级的的,与进程是否被用户感知有直接关系
  • APP切换等活动都可能造成进程优先级的变化,都是利用AMS,并通过proc文件设置到内核的
  • LowmemoryKiller运行在内核,在内存需要缩减的时候,会选择低优先级的进程杀死


至于更加细节的内存的缩减、优先级的计算也许将来会放到单独的文章中说明,本文的目的是:能让大家对LowmemoryKiller的概念以及运行机制有个简单了解。

**仅供参考,欢迎指正**

参考文档


Android应用程序启动过程源代码分析
Android Framework架构浅析之【近期任务】
Android Low Memory Killer介绍
Android开发之InstanceState详解
对Android近期任务列表(Recent Applications)的简单分析
Android 操作系统的内存回收机制
Android LowMemoryKiller原理分析 精
Android进程生命周期与ADJ
Linux下/proc目录简介
Android系统中的进程管理:进程的创建 精
Google文档--进程和线程



相关阅读:Android后台杀死系列之三:LowMemoryKiller原理(4.3-6.0)上篇

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者李尚授权发布。