Android framework--谈谈AMS.updateOomAdjLocked

关于Android系统的内存回收机制,相信大家都不陌生,Android基于各个应用进程承载四大组件的状态对应用进程进行重要性评估,并在系统内存紧张时根据重要性由低到高来选择杀死应用进程,以达到释放内存的目的。重要性评估由AMS执行,具体来说就是AMS.updateOomAdjLocked函数,反过来说,AMS.updateOomAdjLocked的作用就是更新应用进程的重要性。

应用进程(ProcessRecord)的重要性由三个状态值表示:

  • adj:LMK杀进程的评分依据
  • procState:指示进程状态
  • schedGroup:指示进程调度策略

本文不会分析该函数的具体执行,而是讨论这三个状态值之间的差异,代码参考Android N。

基本的思考

既然要评估进程的重要性,并以此作为LMK回收进程的依据,理论上来讲由单个状态来指示重要性,并告知LMK应该是最好的方案,简单粗暴,逻辑清晰。大家从很多地方都可以得知,oom_adj就是提供给LMK进行内存回收的依据,但是对于procState和schedGroup要么一笔带过,要么干脆不提及。但是既然存在这么两个东西,必然有其存在的意义。

我们先大概讲下AMS.updateOomAdjLocked的执行流程:

updateOomAdjLocked(ProcessRecord app)
    -updateOomAdjLocked(ProcessRecord app, int cachedAdj,
               ProcessRecord TOP_APP, boolean doingAll, long now) 
        -computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
            boolean doingAll, long now)
        -applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
            long nowElapsed)
updateOomAdjLocked()
    -computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
            boolean doingAll, long now)
        -applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
            long nowElapsed)

有上述两种执行路径,相同的空格缩进表示函数调用栈的同一级,注意updateOomAdjLocked有多个重载版本。重点说明出下面几点:

  • updateOomAdjLocked在应用进程的组件运行状态发生改变时被调用,比如有Service启动,有广播接收者收到广播,有Activity启动等,这很好理解,因为进程重要性的计算就依赖于组件运行状态,既然组件运行状态发生了改变,就应该实时更新;
  • computeOomAdjLocked根据一定规则计算出三个状态值,这个规则跟Android将进程划分的5个优先级有关系,即前台进程、可见进程、服务进程、后台进程、空进程,这里不详细说明;
  • applyOomAdjLocked将computeOomAdjLocked计算出的三个状态值应用起来,即真正发挥这三个状态值的作用。

既然要分析这三个状态值的作用,看源码如何使用这些值就是最好的办法,从上面可以知道,applyOomAdjLocked是我们的切入点。

applyOomAdjLocked

下面代码中涉及的ProcessRecord中cur开头的字段均为computeOomAdjLocked函数计算出的当前状态值,比如curAdj,curProcState,curSchedGroup,下面从代码的角度分别描述它们的作用。

adj

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

curAdj是computeOomAdjLocked计算出的adj值,赋值给setAdj,并且调用ProcessList.setOomAdj,继续往下看:

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);
    }
}
private static void writeLmkd(ByteBuffer buf) {

    for (int i = 0; i < 3; i++) {
        if (sLmkdSocket == null) {
                if (openLmkdSocket() == false) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ie) {
                    }
                    continue;
                }
        }

        try {
            sLmkdOutputStream.write(buf.array(), 0, buf.position());
            return;
        } catch (IOException ex) {
            Slog.w(TAG, "Error writing to lowmemorykiller socket");

            try {
                sLmkdSocket.close();
            } catch (IOException ex2) {
            }

            sLmkdSocket = null;
        }
    }
}
private static boolean openLmkdSocket() {
    try {
        sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
        sLmkdSocket.connect(
            new LocalSocketAddress("lmkd",
                    LocalSocketAddress.Namespace.RESERVED));
        sLmkdOutputStream = sLmkdSocket.getOutputStream();
    } catch (IOException ex) {
        Slog.w(TAG, "lowmemorykiller daemon socket open failed");
        sLmkdSocket = null;
        return false;
    }

    return true;
}

最终是往名为”lmkd”的Socket发送数据,数据内容为pid、uid及对应的adj值,”lmkd”由lmkd进程创建,所以后面的事情就由lmkd来处理。简单地说,lmkd根据各个进程的adj值以及预先设定的内存阈值,根据一定的策略杀进程来释放内存,这里不关心具体的实现,只关心lmkd利用adj值做了哪些事情。也就是说,lmkd的策略仅依赖于adj值,跟procState和schedGroup无关。

procState

if (app.repProcState != app.curProcState) {
    app.repProcState = app.curProcState;
    changes |= ProcessChangeItem.CHANGE_PROCESS_STATE;
    if (app.thread != null) {
        try {
            if (false) {
                //RuntimeException h = new RuntimeException("here");
                Slog.i(TAG, "Sending new process state " + app.repProcState
                        + " to " + app /*, h*/);
            }
            app.thread.setProcessState(app.repProcState);
        } catch (RemoteException e) {
        }
    }
}

调用了应用进程ApplicationThread的setProcessState方法:

public void setProcessState(int state) {
    updateProcessState(state, true);
}

public void updateProcessState(int processState, boolean fromIpc) {
    synchronized (this) {
        if (mLastProcessState != processState) {
            mLastProcessState = processState;
            // Update Dalvik state based on ActivityManager.PROCESS_STATE_* constants.
            final int DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
            final int DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
            int dalvikProcessState = DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE;
            // TODO: Tune this since things like gmail sync are important background but not jank perceptible.
            if (processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
                dalvikProcessState = DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE;
            }
            VMRuntime.getRuntime().updateProcessState(dalvikProcessState);
            if (false) {
                Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
                        + (fromIpc ? " (from ipc": ""));
            }
        }
    }
}

最后调用了VMRuntime.getRuntime().updateProcessState(dalvikProcessState),传进来的参数processState根据是否小于ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND被转换成了DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE以及DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE。实际上这是告诉ART运行时当前进程状态是可感知还是不可感知,用来切换虚拟机的GC算法,即前台GC或是后台GC。前台GC效率较高但会产生碎片,后台GC效率较低但不存在碎片问题,刚好适应应用进程处于前后台的两种情形。具体可参考:
https://blog.csdn.net/luoshengyang/article/details/45301715

schedGroup

if (app.setSchedGroup != app.curSchedGroup) {
    int oldSchedGroup = app.setSchedGroup;
    app.setSchedGroup = app.curSchedGroup;
    if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
            "Setting sched group of " + app.processName
            + " to " + app.curSchedGroup);
    if (app.waitingToKill != null && app.curReceiver == null
            && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
        app.kill(app.waitingToKill, true);
        success = false;
    } else {
        int processGroup;
        switch (app.curSchedGroup) {
            case ProcessList.SCHED_GROUP_BACKGROUND:
                processGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
                break;
            case ProcessList.SCHED_GROUP_DEFAULT:
                processGroup = Process.THREAD_GROUP_DEFAULT;
                break;
            case ProcessList.SCHED_GROUP_SYSTEM:
                processGroup = Process.THREAD_GROUP_SYSTEM;
                break;
            case ProcessList.SCHED_GROUP_AUDIO_APP:
                processGroup = Process.THREAD_GROUP_AUDIO_APP;
                break;
            case ProcessList.SCHED_GROUP_AUDIO_SYS:
                processGroup = Process.THREAD_GROUP_AUDIO_SYS;
                break;
            case ProcessList.SCHED_GROUP_TOP_APP:
                processGroup = Process.THREAD_GROUP_TOP_APP;
                break;
            default:
                processGroup = Process.THREAD_GROUP_DEFAULT;
                break;
        }
        long oldId = Binder.clearCallingIdentity();
        try {
            Process.setProcessGroup(app.pid, processGroup);  // 设置进程调度策略
            if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
                // do nothing if we already switched to RT
                if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
                    // Switch VR thread for app to SCHED_FIFO
                    if (mInVrMode && app.vrThreadTid != 0) {
                        try {
                            Process.setThreadScheduler(app.vrThreadTid,
                                Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
                        } catch (IllegalArgumentException e) {
                            // thread died, ignore
                        }
                    }
                    if (mUseFifoUiScheduling) {
                        // Switch UI pipeline for app to SCHED_FIFO
                        app.savedPriority = Process.getThreadPriority(app.pid);
                        try {
                            Process.setThreadScheduler(app.pid,
                                Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
                        } catch (IllegalArgumentException e) {
                            // thread died, ignore
                        }
                        if (app.renderThreadTid != 0) {
                            try {
                                Process.setThreadScheduler(app.renderThreadTid,
                                    Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
                            } catch (IllegalArgumentException e) {
                                // thread died, ignore
                            }
                            if (DEBUG_OOM_ADJ) {
                                Slog.d("UI_FIFO", "Set RenderThread (TID " +
                                    app.renderThreadTid + ") to FIFO");
                            }
                        } else {
                            if (DEBUG_OOM_ADJ) {
                                Slog.d("UI_FIFO", "Not setting RenderThread TID");
                            }
                        }
                    } else {  // 提高前台UI相关线程优先级为-10
                        // Boost priority for top app UI and render threads
                        Process.setThreadPriority(app.pid, -10);
                        if (app.renderThreadTid != 0) {
                            try {
                                Process.setThreadPriority(app.renderThreadTid, -10);
                            } catch (IllegalArgumentException e) {
                                // thread died, ignore
                            }
                        }
                    }
                }
            } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
                       app.curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
                // Reset VR thread to SCHED_OTHER
                // Safe to do even if we're not in VR mode
                if (app.vrThreadTid != 0) {
                    Process.setThreadScheduler(app.vrThreadTid, Process.SCHED_OTHER, 0);
                }
                if (mUseFifoUiScheduling) {
                    // Reset UI pipeline to SCHED_OTHER
                    Process.setThreadScheduler(app.pid, Process.SCHED_OTHER, 0);
                    Process.setThreadPriority(app.pid, app.savedPriority);
                    if (app.renderThreadTid != 0) {
                        Process.setThreadScheduler(app.renderThreadTid,
                            Process.SCHED_OTHER, 0);
                        Process.setThreadPriority(app.renderThreadTid, -4);
                    }
                } else {  // 恢复前台UI相关线程优先级为0
                    // Reset priority for top app UI and render threads
                    Process.setThreadPriority(app.pid, 0);
                    if (app.renderThreadTid != 0) {
                        Process.setThreadPriority(app.renderThreadTid, 0);
                    }
                }
            }
        } catch (Exception e) {
            Slog.w(TAG, "Failed setting process group of " + app.pid
                    + " to " + app.curSchedGroup);
            e.printStackTrace();
        } finally {
            Binder.restoreCallingIdentity(oldId);
        }
    }
}

总结下,主要做了两件事,参考代码里的中文注释:

1、调用Process.setProcessGroup(int pid, int group)设置进程调度策略,native层代码不再详细分析,原理是利用linux的cgroup机制,将进程根据状态放入预先设定的cgroup分组中,这些分组包含了对cpu使用率、cpuset、cpu调频等子资源的配置,已满足特定状态进程对系统资源的需求。

2、当schedGroup在ProcessList.SCHED_GROUP_TOP_APP跟非ProcessList.SCHED_GROUP_TOP_APP间切换时调用Process.setThreadPriority(int tid, int priority),即应用进入前台和退出前台时改变UI相关线程优先级,这里的UI相关线程包括主线程和RenderThread,以提升用户界面的响应速度。

综上所述,三个状态各司其职,从不同方面为保证应用的流畅运行保驾护航,缺一不可。

    原文作者:阳光下的小海
    原文地址: https://blog.csdn.net/alvinhuangjh/article/details/80240507
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞