LMK简介

Linux的内存的使用原则就是不要浪费内存,所以在程序退出时在一段时间内还停留在内存中,这也是我们下一次打开程序时发现快一些的原因。但是这样带来的坏处就是如果驻留在内存中的程序多了,容易导致OOM(out of memory)的可能。Linux中使用内存监控机制来避免OOM发生。

OOM Killer

Linux原本存在一个内存监控机制OOM Killer,一旦发现内存使用进入一个临界值就会自动按照一定的策略来清理。它的核心思想是,

  • 按照优先级,从低到高来杀死进程,回收内存资源。
  • 一方面要考虑杀死进程给系统带来的损坏要尽量小,另一方面要释放尽量多的内存。

具体的做法是OOM Killer会根据一些参考因素,例如进程消耗内存,运行时间,OOM权重等指标计算出一个oom_score分数,这个分数越低,进程被杀死的概率越小,被杀死的时间越晚。

LMK

在Android中存在另一个内存监控机制Low memory killer(LMK)。它实现一个不同级别的killer,根据进程的oom_adj 来杀死进程,释放内存。oom_adj的大小和进程的类型以及进程被调度的次序有关,这个值越小,程序越重要,被杀的可能性越低。其源码位于,kernel/drivers/staging/android/LowMemoryKiller.c。该文件中定义了两个数组,用来调整killer行为。

static short lowmem_adj[6] = {
        0,
        1,
        6,
        12,
};
static int lowmem_adj_size = 4;
/*static*/ int lowmem_minfree[6] = {
        3 * 512,        /* 6MB */
        2 * 1024,       /* 8MB */
        4 * 1024,       /* 16MB */
        16 * 1024,      /* 64MB */
};

上面定义的两个数组时一一对应的,其中lowmem_adj表示的是被处理某一个级别的adj的值,lowmem_minfree则表示该级别对应的内存阈值。比如说adj=0的级别,它对应的内存阈值是6M,也就是在可用内存小于6M时,会清除adj大于等于0的所有进程。所以可以看出adj越小,它被杀死的可能越小。

LMK参数

Kernel代码中设定了lowmem_adj和lowmem_minfree的默认值,可以通过设置下面的文件来修改这两组值。需要注意的是最多只能设置6个级别,并且minfree的单位是page。

  • /sys/module/lowmemorykiller/parameters/adj
  • /sys/module/lowmemorykiller/parameters/minfree (以页为单位,一般是4KB大小)

例如可以在init.rc中修改其默认值,

   write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15 
   write /proc/sys/vm/overcommit_memory 1
   write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144

但是在Android运行时,AMS还会通过updateOomLevels()对LMK的参数进行调整。

LMK驱动

LMK使用了kernel中的shrinker机制,在驱动加载时,向系统注册了一个shrinker。当系统空闲内存页面不足时就会调用该函数。LMK的shrinker实现如下,

kernel\drivers\staging\android\lowmemorykiller.c
static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
{
    struct task_struct *tsk;
    struct task_struct *selected = NULL;
    int rem = 0;
    int tasksize;
    int i;
    short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
    int minfree = 0;
    int selected_tasksize = 0;
    short selected_oom_score_adj;
    int array_size = ARRAY_SIZE(lowmem_adj);
    int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
    int other_file = global_page_state(NR_FILE_PAGES) -
                        global_page_state(NR_SHMEM);

    if (lowmem_adj_size < array_size)
        array_size = lowmem_adj_size;
    if (lowmem_minfree_size < array_size)
        array_size = lowmem_minfree_size;
    for (i = 0; i < array_size; i++) {  
        //依次遍历策略阀值数组,从小到大,根据当前memory free情况,取触发adj值
        minfree = lowmem_minfree[i];
        if (other_free < minfree && other_file < minfree) {
            min_score_adj = lowmem_adj[i];
            break;
        }
    }
//这里得到的min_score_adj  就是此时内存状态下 将会kill掉的最小score_adj 
......
for_each_process(tsk) {
......
tasksize = get_mm_rss(p->mm);
......
        if (selected) {
            if (oom_score_adj < selected_oom_score_adj)
                continue;
            if (oom_score_adj == selected_oom_score_adj &&
                tasksize <= selected_tasksize)
                continue;
        }//可以看到 遍历一圈process 只为找到一个 oom_score_adj tasksize 最大的process
        selected = p;
        selected_tasksize = tasksize;
        selected_oom_score_adj = oom_score_adj;
    }
    if (selected) {
        lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \
                "   to free %ldkB on behalf of '%s' (%d) because\n" \
                "   cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \
                "   Free memory is %ldkB above reserved\n",
                 selected->comm, selected->pid,
                 selected_oom_score_adj,
                 selected_tasksize * (long)(PAGE_SIZE / 1024),
                 current->comm, current->pid,
                 other_file * (long)(PAGE_SIZE / 1024),
                 minfree * (long)(PAGE_SIZE / 1024),
                 min_score_adj,
                 other_free * (long)(PAGE_SIZE / 1024));

        trace_lowmem_kill(selected,  other_file, minfree, min_score_adj, other_free);

        lowmem_deathpending_timeout = jiffies + HZ;
        send_sig(SIGKILL, selected, 0);  //发送kill signal 去kill selected的process
        set_tsk_thread_flag(selected, TIF_MEMDIE);
        rem -= selected_tasksize;
    }
}

lowmem_shrink()中,首先根据当前的内存状态找到一个合适的ADJ值,再根据该ADJ找到tasksize最大的进程将其杀死。在判断内存状态时需要注意一下,与minfree做比较的是free和cache,也就是说只有当free和cache都小于minfree时才满足ADJ的条件。

LMK设定

LMK的策略是通过驱动来执行的,但其策略的参数是在应用层设定。参数的默认值可以通过上文讲到的配置文件来修改,在Android中参数是由AMS设置的。AMS中定义了ADJ和minfree相关的数组资源。

frameworks/base/services/core/java/com/android/server/am/ProcessList.java
// These are the various interesting memory levels that we will give to
// the OOM killer.  Note that the OOM killer only supports 6 slots, so we
// can't give it a different value for every possible kind of process.
private final int[] mOomAdj = new int[] {
        FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
        BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
};
// These are the low-end OOM level limits.  This is appropriate for an
// HVGA or smaller phone with less than 512MB.  Values are in KB.
private final int[] mOomMinFreeLow = new int[] {
        12288, 18432, 24576,
        36864, 43008, 49152
};
// These are the high-end OOM level limits.  This is appropriate for a
// 1280x800 or larger screen with around 1GB RAM.  Values are in KB.
private final int[] mOomMinFreeHigh = new int[] {
        73728, 92160, 110592,
        129024, 147456, 184320
};
// The actual OOM killer memory levels we are using.
private final int[] mOomMinFree = new int[mOomAdj.length];

策略参数的更新是由updateOomLevels()完成的。最终计算出来的minfree会与mOomMinFreeLow,mOomMinFreeHigh,minfree_adj,minfree_abs等多个参数相关。

frameworks/base/services/core/java/com/android/server/am/ProcessList.java
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
    ……
    int minfree_adj = Resources.getSystem().getInteger(
            com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
    int minfree_abs = Resources.getSystem().getInteger(
            com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
    ……
    for (int i=0; i<mOomAdj.length; i++) {
        int low = mOomMinFreeLow[i];
        int high = mOomMinFreeHigh[i];
        if (is64bit) {
            // Increase the high min-free levels for cached processes for 64-bit
            if (i == 4) high = (high*3)/2;
            else if (i == 5) high = (high*7)/4;
        }
        mOomMinFree[i] = (int)(low + ((high-low)*scale));
    }

    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;
            }
        }
    }
    ……
    }

AMS回收机制

Android用用在各种activity生命周期切换时,会触发AMS中的回收机制。在AMS的回收过程中,还会去维护一个ADJ变量,作为LMK行为的参考依据。AMS回收机制的入口为trimApplications(),它在很多地方都有调用。

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void trimApplications() {
        synchronized (this) {
            int i;

            // First remove any unused application processes whose package
            // has been removed.
            for (i=mRemovedProcesses.size()-1; i>=0; i--) {
                final ProcessRecord app = mRemovedProcesses.get(i);
                if (app.activities.size() == 0
                        && app.curReceiver == null && app.services.size() == 0) {
                    Slog.i(
                        TAG, "Exiting empty application process "
                        + app.processName + " ("
                        + (app.thread != null ? app.thread.asBinder() : null)
                        + ")\n");
                    if (app.pid > 0 && app.pid != MY_PID) {
                        app.kill("empty", false);
                    } else {
                        try {
                            app.thread.scheduleExit();
                        } catch (Exception e) {
                            // Ignore exceptions.
                        }
                    }
                    cleanUpApplicationRecordLocked(app, false, true, -1);
                    mRemovedProcesses.remove(i);

                    if (app.persistent) {
                        addAppLocked(app.info, false, null /* ABI override */);
                    }
                }
            }

            // Now update the oom adj for all processes.

            updateOomAdjLocked();

        }
    }

mRemovedProcesses 列表中主要包含了 crash 的进程、5 秒内没有响应并被用户选在强制关闭的进程、以及应用开发这调用 killBackgroundProcess 想要杀死的进程。调用 Process.killProcess 将所有此类进程全部杀死。updateOomAdjLocked()计算更新所有process的oomadj。

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void updateOomAdjLocked() {
......
        // First update the OOM adjustment for each of the
        // application processes based on their current state.
        int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
        int nextCachedAdj = curCachedAdj+1;
        int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
        int nextEmptyAdj = curEmptyAdj+2;
        for (int i=N-1; i>=0; i--) {
            ProcessRecord app = mLruProcesses.get(i);
            if (!app.killedByAm && app.thread != null) {
                app.procStateChanged = false;
                computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
    ......
     applyOomAdjLocked(app, TOP_APP, true, now);
......
}

computeOomAdjLocked()计算当前process的ADJ。ADJ值为-17~15,越小优先级越高。AMS中根据进程的类型定义了ADJ的值,他们的意义分别如下:

ADJ Description
CACHED_APP_MAX_ADJ = 15 当前只运行了不可见的Activity组件的进程
CACHED_APP_MIN_ADJ = 9
SERVICE_B_ADJ = 8 B list of Service。和A list相比,他们对用户的黏合度要小些
PREVIOUS_APP_ADJ = 7 用户前一次交互的进程。按照用户的使用习惯,人们经常会在几个常用的进程间切换,所以这类进程得到再次运行的概率比较大
HOME_APP_ADJ = 6 Launcher进程,他对用户的重要性不言而喻
SERVICE_ADJ = 5 当前运行了application service的进程
EAVY_WEIGHT_APP_ADJ = 4 重量级应用程序进程
BACKUP_APP_ADJ = 3 用于承载backup相关操作的进程
PERCEPTIBLE_APP_ADJ = 2 这类进程用户感觉到但看不见,如后台运行的音乐播放器
VISIBLE_APP_ADJ = 1 前台可见的Activity进程,如果轻易杀死这类进程将严重影响用户体验
FOREGROUND_APP_ADJ = 0 当前正在前台运行的进程,也就是用户正在交互的那个程序
PERSISTENT_SERVICE_ADJ = -11 与系统进程或Persistent进程绑定的进程,说明该进程很
PERSISTENT_PROC_ADJ = -12 Persistent性质的进程,如telephony
SYSTEM_ADJ = -16 系统进程

这些是系统提供的adj,我们还可以改变自己进程的adj值,有以下两种方式:

  • 写/proc/pid/oom_adj 值,在init.rc文件中就经常看到下面的语句。它设置进程的adj 值为-16,属于系统进程永远不会被杀死。

    on early-init
    write /proc/1/oom_adj -16
  • 设置persistent属性。在AndroidManifest.xml文件中设置这个属性为true,即可将其adj的值设置为-12,处于这个级别的进程基本上也不会被杀死,比如电话。

继续updateOomAdjLocked()流程。通过computeOomAdjLocked()得到ADJ值后,applyOomAdjLocked()将其经过一定的修整,设置到对应的process。大体流程如下,

  • 必须是非 persistent 进程,即非系统进程;
  • 必须是空进程,即进程中没有任何 activity 存在。如果杀死存在 Activity 的进程,有可能关闭用户正在使用的程序,或者使应用程序恢复的时延变大,从而影响用户体验;
  • 必须无 broadcast receiver。运行 broadcast receiver 一般都在等待一个事件的发生,用户并不希望此类程序被系统强制关闭;
  • 进程中 service 的数量必须为 0。存在 service 的进程很有可能在为一个或者多个程序提供某种服务,如 GPS 定位服务。杀死此类进程将使其他进程无法正常服务。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    private final boolean applyOomAdjLocked(ProcessRecord app,
            ProcessRecord TOP_APP, boolean doingAll, long now) {
        ......
        if (app.curAdj != app.setAdj) {
            ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(
                TAG, "Set " + app.pid + " " + app.processName +
                " adj " + app.curAdj + ": " + app.adjType);
            app.setAdj = app.curAdj;
        }
        ......
    }

setOomAdj()通过lmkd将ADJ值写入到proc文件系统对应的节点上,其路径为"/proc/<pid>/oom_score_adj"。

frameworks/base/services/core/java/com/android/server/am/ProcessList.java
    /**
     * Set the out-of-memory badness adjustment for a process.
     *
     * @param pid The process identifier to set.
     * @param uid The uid of the app
     * @param amt Adjustment value -- lmkd allows -16 to +15.
     *
     * {@hide}
     */
    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();
        if ((now-start) > 250) {
            Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid
                    + " = " + amt);
        }
    }

戈壁老王
143 声望60 粉丝

做为一个不称职的老年码农,一直疏忽整理笔记,开博记录一下,用来丰富老年生活,