Android——LMK 机制源代码研究分析
# 一、目标
本篇文章主要分析 Andoird 核心机制与服务中的 ——LMK 机制,低内存管理机制(根据需要杀死进程来释放需要的内存)。源码分析主要通过 http://androidxref.com/,Android 版本为 Marshmallow-Android 6.0.1_r10, 内核版本为 3.18,机制架构如下:
源码位置如下:
源代码名称
路径位置
lowmemorykiller.c
/drivers/staging/android/lowmemorykiller.c
lmkd.c
/system/core/lmkd/lmkd.c
ProcessList.java
/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
ActivityManagerService.java
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
# 二、核心服务原理分析
# (一) LMK 简介
为了防止剩余内存过低,Android 在内核空间采用 LMK(Low Memory Killer)
机制,LMK 是通过注册 shrinker 来触发低内存回收的,但是这个机制可能会拖慢 Shrinkers 内存扫描速度,已从内核 4.12 中移除,后续会采用用户空间的 LMKD + memory cgroups 机制。
进程刚启动时,ADJ 等于 INVALID_ADJ,当执行完 attachApplication (),该进程的 curAdj 和 setAdj 不相等,则会触发执行 setOomAdj () 将该进程的节点 /proc/pid/oom_score_adj 写入 oomadj 的值。下图参数为 Android 原生阈值,当系统剩余空闲内存抵御某阈值(比如 147MB),则从 ADJ 大于或等于相应阈值(比如 900)的进程中,选择 ADJ 值最大的进程,如果存在多个 ADJ 相同的进程,则选择内存最大的进程。如下是 64 位机器,LMK 默认阈值图:
# (二)lmkd
lmkd
,全称为 Low Memory Killer Daemon,用以监控正在运行的 Android 系统的内存) 状态,以及通过杀死最不重要进程来应对高内存压力,以保持系统在可接受的水平上运行。
过去,Android 使用内存 LMK 驱动程序来监控系统内存的压力,这是一种依赖于硬编码值的硬件机制。从 Kernel 4.12 开始,LMK 驱动程序从上游内核中移除,由应用空间的 lmkd 执行内存监控和进程终止任务。
Android 10 以及更高版本支持新的 lmkd 模式,它使用 PSI 监视器来检测内存压力。PSI 用以测量由于内存不足导致任务延迟的时间。由于这些岩石会直接影响到用户体验,因此它们代表了确定内存压力严重的便捷指标。PSI 监视器允许特权用户进程(例如 lmkd)指定这些延迟的阈值,然后订阅当突破阈值时来自 kernel 的事件。
# (三)原理分析
于 Linux 系统来说,底层内核的内存监控机制为 OOMKiller。一旦发现系统的可用内存达到临界值,OOM 的管理者就会自动回收内存。根据策略的不同,OOM 的处理手段略有差异。它的核心思想是按照优先级顺序,从低到高逐步杀掉进程,回收内存。优先级的设定策略一方面要考虑对系统的损害程度(例如系统的核心进程,优先级通常较高),另一方面也希望尽可能多地释放无用内存。一个合理的策略至少要综合一下几个因素:进程消耗的内存;进程占用的 CPU 时间;oom_adj(OOM 权重)。对于 Linux 内核中的 OOM Killer,内核所管理的进程都有一个衡量其 oom 权重的值,存储在 /proc//oom_adj 中。根据这一权重值以及其他若干因素,系统会实时给每个进程评分,以决定 OOM 应该杀死哪些进程。比如 oom_score 分数越低的进程,杀死的概率越小,或者说被杀死的时间越晚。
对于 Android 系统来说,我们常常在使用的过程中从一个应用返回到桌面,然后再打开其他的应用进行使用。而此时前一个应用会驻留在内存中,当再次打开该应用时就可以直接显示使用。通过这种方法可以提升用户体验以及提高应用打开速度。但是系统内存是有限的,不可能一直将全部应用驻留在系统内存中。基于 Linux 内核 OOM Killer 的核心思想,Android 系统扩展出了自己的内存监控体系。因为 Linux 下的杀死内存要等到系统资源快要不够用的时候才会产生效果。而 Android 实现了不同梯级的 Killer,名为 Low Memory Killer(LMK)。所以 Low Memory Killer 的作用就是当内存处于低水平时,杀死系统中余留的暂时还不使用的进程,来释放内存。
我们知道,从 Zygote 中孵化出来的进程都会记录在 ActivityManagerService.mLruProcesses 列表中,ActivityManagerService 的核心业务之一就是实时更新进程的状态,根据状态计算出进程对应的 OomAdj 值,这个值会传递到 kernel 中,在 kernel 中有一个低内存回收机制,在内存达到一定阈值时会触发清理 OomAdj 值高的进程,这就是 LMK 的工作原理。
用户在启动一个进程之后,通常伴随着启动一个 Activity 游览页面或者一个 Service 播放音乐等等,这个时候此进程的 adj 被 AMS 提高,LMK 就不会杀死这个进程,当这个进程要做的事情做完了,退出后台了,此进程的 adj 很快又被 AMS 降低。当需要杀死一个进程释放内存时,一般先根据当前手机剩余内存的状态,在 minfree 节点中找到当前等级,再根据这个等级去 adj 节点中找到这个等级应该杀掉的进程的优先级, 之后遍历所有进程并比较进程优先级 adj 与优先级阈值,并杀死优先级低于阈值的进程,达到释放内存的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ![img](https://raw.githubusercontent.com/y1seco/blog_image/master/img/202205121659479.jpg) AMS负责了系统中四大组件的启动、切换、调度以及应用进程管理和调度工作。在应用程序的使用过程中,AMS会根据四大组件关键生命周期,在mLruProcesses中时时地设定对应进程的adj值(更新进程优先级),在内存低于阈值时,LMK会选择adj优先级最大(如果adj相等则选择同adj中内存占用最大)的进程杀掉,释放内存。 总的来说,```Framework层通过调整adj的值和阈值数组,输送给kernel中的lmk,为lmk提供杀进程的原材料```,因为用户空间和内核空间相互隔离,就采用了文件节点进行通讯,用socket将adj的值与阈值数组传给lmkd(5.0之后不在由AMS直接与lmk通信,引入lmkd守护进程),lmkd将这些值写到内核节点中。lmk通过读取这些节点,实现进程的kill。 # 三、源代码分析 ## (一) ActivityManagerService.java ### 1. updateConfiguration方法 ```java public void updateConfiguration(Configuration values) { enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, "updateConfiguration()"); synchronized(this) { if (values == null && mWindowManager != null) { values = mWindowManager.computeNewConfiguration(); } if (mWindowManager != null) { mProcessList.applyDisplaySize(mWindowManager); } final long origId = Binder.clearCallingIdentity(); if (values != null) { Settings.System.clearConfiguration(values); } updateConfigurationLocked(values, null, false, false); Binder.restoreCallingIdentity(origId); } }
这里 updateConfiguration 是 ActivityManagerService (AMS) 对外提供的 binder 接口,调用后可以更新窗口的配置。
# 2. applyOomAdjLocked 方法
2.1 applyOomAdjLocked 方法执行在 updateOomAdjLocked 中,最终通过它把 computeOomAdjLocked 和 updateOomAdjLocked 计算好的 adj 更新并保存。时序图如下:
2.2 源码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 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 ; 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; } if (app.setSchedGroup != app.curSchedGroup) { app.setSchedGroup = app.curSchedGroup; if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Setting process group of " + app.processName + " to " + app.curSchedGroup); if (app.waitingToKill != null && app.curReceiver == null && app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { app.kill(app.waitingToKill, true ); success = false ; } else { if (true ) { long oldId = Binder.clearCallingIdentity(); try { Process.setProcessGroup(app.pid, app.curSchedGroup); } catch (Exception e) { Slog.w(TAG, "Failed setting process group of " + app.pid + " to " + app.curSchedGroup); e.printStackTrace(); } finally { Binder.restoreCallingIdentity(oldId); } } else { if (app.thread != null ) { try { app.thread.setSchedulingGroup(app.curSchedGroup); } catch (RemoteException e) { } } } Process.setSwappiness(app.pid, app.curSchedGroup <= Process.THREAD_GROUP_BG_NONINTERACTIVE); } } ... if (app.repProcState != app.curProcState) { app.repProcState = app.curProcState; changes |= ProcessChangeItem.CHANGE_PROCESS_STATE; if (app.thread != null ) { try { if (false ) { Slog.i(TAG, "Sending new process state " + app.repProcState + " to " + app ); } app.thread.setProcessState(app.repProcState); } catch (RemoteException e) { } } } ... return success; }
# 3. updateOomAdjLocked 无参方法
updateOomAdjLocked 方法在应用进程的组件运行状态发生改变时被调用,比如有 Service 启动,有广播接收者收到广播,有 Activity 启动等。因为进程的重要性的计算就依赖于组件运行状态,既然组件运行状态发生了改变,就应该实时更新。
3.1 执行 oom 更新之前一些基本参数的初始化重置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 final void updateOomAdjLocked () { final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null ; final long now = SystemClock.uptimeMillis(); final long nowElapsed = SystemClock.elapsedRealtime(); final long oldTime = now - ProcessList.MAX_EMPTY_TIME; final int N = mLruProcesses.size(); ... mAdjSeq++; mNewNumServiceProcs = 0 ; mNewNumAServiceProcs = 0 ; final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES; final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit; int numSlots = (ProcessList.CACHED_APP_MAX_ADJ - ProcessList.CACHED_APP_MIN_ADJ + 1 ) / 2 ; int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs; if (numEmptyProcs > cachedProcessLimit) { numEmptyProcs = cachedProcessLimit; } mEmptyRemainingCapacity = emptyProcessLimit - numEmptyProcs; int emptyFactor = numEmptyProcs/numSlots; if (emptyFactor < 1 ) emptyFactor = 1 ; int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1 )/numSlots; if (cachedFactor < 1 ) cachedFactor = 1 ; int stepCached = 0 ; int stepEmpty = 0 ; int numCached = 0 ; int numEmpty = 0 ; int numTrimming = 0 ; mNumNonCachedProcs = 0 ; mNumCachedHiddenProcs = 0 ; int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ; int nextCachedAdj = curCachedAdj+1 ; int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ; int nextEmptyAdj = curEmptyAdj+2 ; boolean retryCycles = false ; ...
3.2 调用 computeOomAdjLocked 方法计算进程的 oom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 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); if (app.curAdj >= ProcessList.UNKNOWN_ADJ) { switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: case ActivityManager.PROCESS_STATE_CACHED_RECENT: app.curRawAdj = curCachedAdj; app.curAdj = app.modifyRawOomAdj(curCachedAdj); if (DEBUG_LRU && false ) Slog.d(TAG_LRU, "Assigning activity LRU #" + i + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj + ")" ); if (curCachedAdj != nextCachedAdj) { stepCached++; if (stepCached >= cachedFactor) { stepCached = 0 ; curCachedAdj = nextCachedAdj; nextCachedAdj += 2 ; if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; } } } break ; default : app.curRawAdj = curEmptyAdj; app.curAdj = app.modifyRawOomAdj(curEmptyAdj); ... int cycleCount = 0 ; while (retryCycles) { cycleCount++; retryCycles = false ; for (int i=0 ; i<N; i++) { ProcessRecord app = mLruProcesses.get(i); if (!app.killedByAm && app.thread != null && app.containsCycle == true ) { app.adjSeq--; app.completedAdjSeq--; } } ...
3.3 调用 applyOomAdjLocked 设置进程的 oom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 for (int i=N-1 ; i>=0 ; i--) { ProcessRecord app = mLruProcesses.get(i); if (!app.killedByAm && app.thread != null) { applyOomAdjLocked(app, true , now, nowElapsed); switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: mNumCachedHiddenProcs++; numCached++; if (numCached > cachedProcessLimit) { app.kill("cached #" + numCached, true ); } break ; case ActivityManager.PROCESS_STATE_CACHED_EMPTY: if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES && app.lastActivityTime < oldTime) { app.kill("empty for " + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime) / 1000 ) + "s" , true ); } else { numEmpty++; if (numEmpty > emptyProcessLimit) { app.kill("empty #" + numEmpty, true ); } } break ; default : mNumNonCachedProcs++; break ; } ... if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.killedByAm) { numTrimming++; } } }
1.1 当执行完上述代码后,根据执行结果设置当前的内存等级,并根据当前的内存等级主动去回收内存
public static final int ADJ_MEM_FACTOR_NORMAL=0;// 正常等级
public static final int ADJ_MEM_FACTOR_MODERATE=1;// 中等等级
public static final int ADJ_MEM_FACTOR_LOW=2;// 存在低内存
public static final int ADJ_MEM_FACTOR_CRITICAL=3;// 严重低内存
对应的更细节的内存回收策略如下:
TRIM_MEEMORY_COMPLETTE=80
TRIM_MEEMORY_MODERATE=60
TRIM_MEEMORY_BACKGROUND=40
TRIM_MEEMORY_UI_HIDDEN=20
TRIM_MEEMORY_RUNNING_CRITICAL=15,对应 ADJ_MEM_FACTOR_CRITICAL
TRIM_MEEMORY_RUNNING_LOW=10,对应 ADJ_MEM_FACTOR_LOW
TRIM_MEEMORY_RUNNING_MODERATE=5,对应 ADJ_MEM_FACTOR_MODERATE 和 ADJ_MEM_FACTOR_NORMAL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 mNumServiceProcs = mNewNumServiceProcs; final int numCachedAndEmpty = numCached + numEmpty; int memFactor; if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) { if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL; } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW; } else { memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE; } } else { memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL; } if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor + " last=" + mLastMemoryLevel + " allowLow=" + mAllowLowerMemLevel + " numProcs=" + mLruProcesses.size() + " last=" + mLastNumProcesses); if (memFactor > mLastMemoryLevel) { if (!mAllowLowerMemLevel || mLruProcesses.size() >= mLastNumProcesses) { memFactor = mLastMemoryLevel; if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!" ); } } if (memFactor != mLastMemoryLevel) { EventLogTags.writeAmMemFactor(memFactor, mLastMemoryLevel); } mLastMemoryLevel = memFactor; mLastNumProcesses = mLruProcesses.size(); boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !isSleepingLocked(), now); final int trackerMemFactor = mProcessStats.getMemFactorLocked(); if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) { if (mLowRamStartTime == 0 ) { mLowRamStartTime = now; } int step = 0 ; int fgTrimLevel; switch (memFactor) { case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; break ; case ProcessStats.ADJ_MEM_FACTOR_LOW: fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; break ; default : fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; break ; } int factor = numTrimming/3 ; int minFactor = 2 ; if (mHomeProcess != null) minFactor++; if (mPreviousProcess != null) minFactor++; if (factor < minFactor) factor = minFactor; int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE; for (int i=N-1 ; i>=0 ; i--) { ProcessRecord app = mLruProcesses.get(i); if (allChanged || app.procStateChanged) { setProcessTrackerStateLocked(app, trackerMemFactor, now); app.procStateChanged = false ; } if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.killedByAm) { if (app.trimMemoryLevel < curLevel && app.thread != null) { try { if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of " + app.processName + " to " + curLevel); app.thread.scheduleTrimMemory(curLevel); } catch (RemoteException e) { } ... app.trimMemoryLevel = curLevel; step++; if (step >= factor) { step = 0 ; switch (curLevel) { case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE; break ; case ComponentCallbacks2.TRIM_MEMORY_MODERATE: curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; break ; } } } ...
3.5 重新计算进程的 PSS 值,外加一些额外的结束工作
Item
全称
含义
等价
USS
Unique Set Size
物理内存
进程独占的内存
PSS
Proportional Set Size
物理内存
PSS=USS + 按比例包含共享库
RSS
Resident Set Size
物理内存
RSS=USS + 包含共享库
VSS
Virtual Set Size
虚拟内存
VSS=RSS + 未分配实际物理内存
以 SystemUI 进程来说:
一般来说内存占用大小有如下规律:VSS>=RSS>=PSS>=USS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (mAlwaysFinishActivities) { mStackSupervisor.scheduleDestroyAllActivities(null, "always-finish" ); } ArrayList<UidRecord> becameIdle = null; if (mLocalPowerManager != null) { mLocalPowerManager.startUidChanges(); } ... if (becameIdle != null) { for (int i = becameIdle.size() - 1 ; i >= 0 ; i--) { mServices.stopInBackgroundLocked(becameIdle.get(i).uid); } } ...
# 4. computeOomAdjLocked 方法
computeOomAdjLocked 函数根据一定规则计算出三个状态值,这个规则与 Android 将进程划分的 5 个优先级有关系,即前台进程、可见进程、服务进程、后台进程、空进程。下面我们对 computeOomAdjLocked 函数进行分段研究。
4.1 根据参数及进程的状态,决定是否需要进行后续的计算,并初始化一些变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private final int computeOomAdjLocked (ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP, boolean doingAll, long now) { if (mAdjSeq == app.adjSeq) { return app.curRawAdj; } if (app.thread == null ) { app.adjSeq = mAdjSeq; app.curSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; return (app.curAdj=app.curRawAdj=ProcessList.CACHED_APP_MAX_ADJ); } app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; app.adjSource = null ; app.adjTarget = null ; app.empty = false ; app.cached = false ; final int activitiesSize = app.activities.size(); if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) { ... } final int PROCESS_STATE_CUR_TOP = mTopProcessState; ... }
4.2 1.1 这部分代码包含前台 Activity 的进程,运行测试类的进程、处理广播的进程以及包含正在运行服务的进程,其中 oom_adj 都被赋值为 FOREGROUND_APP_ADJ。从 LMK 的角度看,它们的重要性是一致的,但这些进程的 proState 不同,于是从 AMS 主动回收内存的角度来看,它们的重要性不同。
对于其它种类的进程,这部分代码先将它们的 oom_adj 设置为 UNKNOW_ADJ,proc_state 设置为 PROCESS_STATE_CACHED_EMPTY,在后续的流程中再作进一步处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 ... int adj;int schedGroup;int procState;boolean foregroundActivities = false ; BroadcastQueue queue ; if (app == TOP_APP) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_TOP_APP; app.adjType = "top-activity" ; foregroundActivities = true ; procState = PROCESS_STATE_CUR_TOP; } else if (app.instrumentationClass != null) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.adjType = "instrumentation" ; procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; } else if ((queue = isReceivingBroadcast(app)) != null) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = (queue == mFgBroadcastQueue) ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; app.adjType = "broadcast" ; procState = ActivityManager.PROCESS_STATE_RECEIVER; } else if (app.executingServices.size() > 0 ) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = app.execServicesFg ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; procState = ActivityManager.PROCESS_STATE_SERVICE; } else { adj = cachedAdj; procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; app.cached = true ; app.empty = true ; app.adjType = "cch-empty" ; } ...
4.3 这部分代码主要处理包含 Activity,但是 Activity 不在前台的进程。注意到这些进程包括之前提到的正在处理广播、服务或测试的进程以及 oom_adj 暂时为 UNKNOW_ADJ 的进程。不过只有 UNKNOW_ADJ 对应的进程,才有可能进行实际的更新。进程中若存在可见 Activity 时,进程的 oom_adj 为 VISIBLE_APP_ADJ;否则若进程中存在处于 PAUSING、PAUSED 或 STOPPING 状态的 Activity 时,进程的 oom_adj 为 PERCEPTIBLE_APP_ADJ;其余的进程仍是 UNKNOW_ADJ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 ... if (!foregroundActivities && activitiesSize > 0 ) { int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX; for (int j = 0 ; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); ... if (r.visible) { if (adj > ProcessList.VISIBLE_APP_ADJ) { adj = ProcessList.VISIBLE_APP_ADJ; app.adjType = "visible" ; } if (procState > PROCESS_STATE_CUR_TOP) { procState = PROCESS_STATE_CUR_TOP; } schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false ; app.empty = false ; foregroundActivities = true ; if (r.task != null && minLayer > 0 ) { final int layer = r.task.mLayerRank; if (layer >= 0 && minLayer > layer) { minLayer = layer; } } break ; } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "pausing" ; } if (procState > PROCESS_STATE_CUR_TOP) { procState = PROCESS_STATE_CUR_TOP; } schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false ; app.empty = false ; foregroundActivities = true ; } else if (r.state == ActivityState.STOPPING) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "stopping" ; } ... if (!r.finishing) { if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; } } app.cached = false ; app.empty = false ; foregroundActivities = true ; } else { if (procState > ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; app.adjType = "cch-act" ; } } if (adj == ProcessList.VISIBLE_APP_ADJ) { adj += minLayer; } } } ...
4.4 该部分代码主要用于处理一些特殊的进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 ... if (adj > ProcessList.PERCEPTIBLE_APP_ADJ || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { if (app.foregroundServices) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; app.cached = false ; app.adjType = "fg-service" ; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; } else if (app.forcingToForeground != null) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; app.cached = false ; app.adjType = "force-fg" ; app.adjSource = app.forcingToForeground; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; } } if (app == mHeavyWeightProcess) { if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) { adj = ProcessList.HEAVY_WEIGHT_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false ; app.adjType = "heavy" ; } if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; } } if (app == mHomeProcess) { if (adj > ProcessList.HOME_APP_ADJ) { adj = ProcessList.HOME_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false ; app.adjType = "home" ; } if (procState > ActivityManager.PROCESS_STATE_HOME) { procState = ActivityManager.PROCESS_STATE_HOME; } } if (app == mPreviousProcess && app.activities.size() > 0 ) { if (adj > ProcessList.PREVIOUS_APP_ADJ) { adj = ProcessList.PREVIOUS_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false ; app.adjType = "previous" ; } if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; } } app.adjSeq = mAdjSeq; app.curRawAdj = adj; app.hasStartedServices = false ; if (mBackupTarget != null && app == mBackupTarget.app) { if (adj > ProcessList.BACKUP_APP_ADJ) { ... adj = ProcessList.BACKUP_APP_ADJ; if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) { procState = ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; } app.adjType = "backup" ; app.cached = false ; } ...
至此,我们可以看出 computeOomAdjLocked 处理一个进程时,按照重要性由高到低的顺序,逐步判断该进程是否满足对应的条件。尽管计算一个进程的 oom_adj 时会经过上述所有的判断,但当一个进程已经满足重要性较高的条件时,后续的判断实际上不会更改它已经获得的 oom_adj。上述代码的逻辑图如下所示:
4.5 该部分代码主要是处理包含服务的进程,进一步分段说明。
4.5.1 Unbounded Service 的处理
当进程中包含 Unbounded Service 时,进程的 oom_adj 先按照 Unbounded Service 的处理方式进行调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ... for (int is = app.services.size()-1 ; is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); is--) { ServiceRecord s = app.services.valueAt(is); if (s.startRequested) { app.hasStartedServices = true ; if (procState > ActivityManager.PROCESS_STATE_SERVICE) { procState = ActivityManager.PROCESS_STATE_SERVICE; } if (app.hasShownUi && app != mHomeProcess) { if (adj > ProcessList.SERVICE_ADJ) { app.adjType = "cch-started-ui-services" ; } } else { if (now < (s.lastActivity + ActiveServices.MAX_SERVICE_INACTIVITY)) { if (adj > ProcessList.SERVICE_ADJ) { adj = ProcessList.SERVICE_ADJ; app.adjType = "started-services" ; app.cached = false ; } } if (adj > ProcessList.SERVICE_ADJ) { app.adjType = "cch-started-services" ; } } }
从上述代码可以看出,当进程中含有 Unbounded Service 时,如果进程之前没有启动过 UI,且 Unbounded Service 存活的时间没有超时,进程的 oom_adj 才能被调整为 SERVICE_ADJ;否则进程的 oom_adj 仍然是 UNKNOW_ADJ 或其他大于 500 的值。
4.5.2 Bounded Service 的处理
该部分代码表示进程按照 Unbounded Service 的方式调增 oom_adj,然后再按照 Bouneded Service 的方式进一步调整。若 Service 仅为 Unbounded Service 或 Bounded Service 中的一种时,computeOomAdjLocked 函数的第五部分只会按照一种方式调增 oom_adj。Bounded Service 的处理方式,远比 Unbounded Service 复杂,依赖于客户端的 oom_adj 和绑定服务时使用的 flag。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 ... for (int conni = s.connections.size()-1 ; conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); conni--) { ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni); for (int i = 0 ; i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); i++) { ConnectionRecord cr = clist.get(i); if (cr.binding.client == app) { continue ; } if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0 ) { ProcessRecord client = cr.binding.client; int clientAdj = computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now); int clientProcState = client.curProcState; if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; } String adjType = null; if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0 ) { if (app.hasShownUi && app != mHomeProcess) { if (adj > clientAdj) { adjType = "cch-bound-ui-services" ; } app.cached = false ; clientAdj = adj; clientProcState = procState; } else { if (now >= (s.lastActivity + ActiveServices.MAX_SERVICE_INACTIVITY)) { if (adj > clientAdj) { adjType = "cch-bound-services" ; } clientAdj = adj; } } } if (adj > clientAdj) { if (app.hasShownUi && app != mHomeProcess && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { adjType = "cch-bound-ui-services" ; } else { if ((cr.flags&(Context.BIND_ABOVE_CLIENT |Context.BIND_IMPORTANT)) != 0 ) { adj = clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ ? clientAdj : ProcessList.PERSISTENT_SERVICE_ADJ; } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0 && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ && adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { adj = clientAdj; } else { if (adj > ProcessList.VISIBLE_APP_ADJ) { adj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ); } } if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0 ) { ... } else { ... } ... if (procState > clientProcState) { procState = clientProcState; } ... } if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0 ) { app.treatLikeActivity = true ; } final ActivityRecord a = cr.activity; if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0 ) { if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ && (a.visible || a.state == ActivityState.RESUMED || a.state == ActivityState.PAUSING)) { adj = ProcessList.FOREGROUND_APP_ADJ; if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0 ) { if ((cr.flags&Context.BIND_IMPORTANT) != 0 ) { schedGroup = ProcessList.SCHED_GROUP_TOP_APP; } else { schedGroup = ProcessList.SCHED_GROUP_DEFAULT; } } app.cached = false ; app.adjType = "service" ; app.adjTypeCode = ActivityManager.RunningAppProcessInfo .REASON_SERVICE_IN_USE; app.adjSource = a; app.adjSourceProcState = procState; app.adjTarget = s.name; } } } } ...
以上就是计算含有 Service 进程的 oom_adj 的全部过程。从代码上看进程仅含有 Unbounded Service 时,整个计算过程比较简单,只要进程没有显示 UI 且 Service 的存在没有超时时,进程的 oom_adj 就被调整为 SERVICE_ADJ。当进程含有 Bounded Service 时,整个计算的复杂度就大大提高,它将考虑到 Bound 使用 flag 以及客户端的情况,综合调整进程的 oom_adj。
4.6 这部分代码主要是用来处理含有 ContentProvider 的进程。由于 ContentProvider 也有客户端,因此同样需要根据客户端进程调整到当前进程的 oom_adj。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 ... for (int provi = app.pubProviders.size()-1 ; provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); provi--) { ContentProviderRecord cpr = app.pubProviders.valueAt(provi); for (int i = cpr.connections.size()-1 ; i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); i--) { ContentProviderConnection conn = cpr.connections.get(i); ProcessRecord client = conn.client; if (client == app) { continue ; } int clientAdj = computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now); int clientProcState = client.curProcState; if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; } if (adj > clientAdj) { if (app.hasShownUi && app != mHomeProcess && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { app.adjType = "cch-ui-provider" ; } else { adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ ? clientAdj : ProcessList.FOREGROUND_APP_ADJ; app.adjType = "provider" ; } app.cached &= client.cached; app.adjTypeCode = ActivityManager.RunningAppProcessInfo .REASON_PROVIDER_IN_USE; app.adjSource = client; app.adjSourceProcState = clientProcState; app.adjTarget = cpr.name; } ... if (cpr.hasExternalProcessHandles()) { if (adj > ProcessList.FOREGROUND_APP_ADJ) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false ; app.adjType = "provider" ; app.adjTarget = cpr.name; } if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; } } } } if (app.lastProviderTime > 0 && (app.lastProviderTime+CONTENT_PROVIDER_RETAIN_TIME) > now) { if (adj > ProcessList.PREVIOUS_APP_ADJ) { adj = ProcessList.PREVIOUS_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false ; app.adjType = "provider" ; } if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; } } ...
从代码上看,处理含有 ContentProvider 进程时,相对比较简单。基本上与处理含有 Unbounded Service 的进程一致,只是最后增加了一些特殊情况的处理。
4.7 这部分代码主要是针对 Service 进程作一些处理,同时判断的依据与前一次记录的 Service 进程总数有关。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 ... if (adj == ProcessList.SERVICE_ADJ) { if (doingAll) { app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3 ); mNewNumServiceProcs++; ... if (!app.serviceb) { if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) { app.serviceHighRam = true ; app.serviceb = true ; ... } else { mNewNumAServiceProcs++; ... } } else { app.serviceHighRam = false ; } } if (app.serviceb) { adj = ProcessList.SERVICE_B_ADJ; } } app.curRawAdj = adj; ... if (adj > app.maxAdj) { adj = app.maxAdj; if (app.maxAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { schedGroup = ProcessList.SCHED_GROUP_DEFAULT; } } ... return app.curRawAdj;
# (二)ProcessList.java
# 1. updataOomLevels 方法
updateOomLevels 方法只是简单的计算出 oomMinFree 数组的值和 oomAdj 值,然后通过 writeLmkd 将数据发送给 lmkd。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 private void updateOomLevels (int displayWidth, int displayHeight, boolean write) { float scaleMem = ((float )(mTotalMemMb-350 ))/(700 -350 ); int minSize = 480 *800 ; int maxSize = 1280 *800 ; float scaleDisp = ((float )(displayWidth*displayHeight)-minSize)/(maxSize-minSize); if (false ) { Slog.i("XXXXXX" , "scaleMem=" + scaleMem); Slog.i("XXXXXX" , "scaleDisp=" + scaleDisp + " dw=" + displayWidth + " dh=" + displayHeight); } float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp; if (scale < 0 ) scale = 0 ; else if (scale > 1 ) scale = 1 ; 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); if (false ) { Slog.i("XXXXXX" , "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs); } 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) { if (i == 4 ) high = (high*3 )/2 ; else if (i == 5 ) high = (high*7 )/4 ; } mOomMinFree[i] = (int )(low + ((high-low)*scale)); } ... mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024 ) / 3 ; int reserve = displayWidth * displayHeight * 4 * 3 / 1024 ; int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust); int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute); ... if (write) { ByteBuffer buf = ByteBuffer.allocate(4 * (2 *mOomAdj.length + 1 )); buf.putInt(LMK_TARGET); for (int i=0 ; i<mOomAdj.length; i++) { buf.putInt((mOomMinFree[i]*1024 )/PAGE_SIZE); buf.putInt(mOomAdj[i]); } writeLmkd(buf); SystemProperties.set ("sys.sysctl.extra_free_kbytes" , Integer.toString(reserve)); } }
# 2. writeLmkd 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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.c
lmk 与大多数守护进程一样,由 init 进程启动:
1 2 3 4 5 6 7 8 9 10 11 service lmkd /system/bin/lmkd class core user lmkd group lmkd system readproc capabilities DAC_OVERRIDE KILL IPC_LOCK SYS_NICE SYS_RESOURCE critical socket lmkd seqpacket+passcred 0660 system system writepid /dev/cpuset/system-background/tasks on property:lmkd.reinit=1 exec_background /system/bin/lmkd --reinit
这里创建的 socket lmkd 的 user/group 都是 system,而它的权限是 0660,所以只有 system 应用才能读写(一般是 activity manager)。
# 1. main 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 int main (int argc __unused, char **argv __unused) { struct sched_param param = { .sched_priority = 1 , }; mlockall(MCL_FUTURE); sched_setscheduler(0 , SCHED_FIFO, ¶m); if (!init()) mainloop(); ALOGI("exiting" ); return 0 ; } int main (int argc, char **argv) { ... update_props(); ctx = create_android_logger(KILLINFO_LOG_TAG); if (!init()) { if (!use_inkernel_interface) { ... if (mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) { ALOGW("mlockall failed %s" , strerror(errno)); } struct sched_param param = { .sched_priority = 1 , }; if (sched_setscheduler(0 , SCHED_FIFO, ¶m)) { ALOGW("set SCHED_FIFO failed %s" , strerror(errno)); } } mainloop(); } android_log_destroy(&ctx); ALOGI("exiting" ); return 0 ; }
在 shed_setscheduler () 中,设置此线程的调度策略为 SCHED_FIFO,即为先进先出;通过 SHED_FIFO 这种实时调度策略,优先级从 1 (low)->99(high)。param 中主要设置 sched_priority,实时线程通常比普通线程有更高的优先级。然后就调用 init 进行初始化,进入 mainloop () 中循环监听 socket。
在新版代码中可以看到 lmkd 的核心部分在 step2(init)和 step5(mainloop),之后将单独说明
这里需注意到 mlockall 函数,在新的 LMK 驱动程序中
1 2 3 if (mlockall (MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) { ALOGW ("mlockall failed %s" , strerror (errno)); }
mlockall 函数将调用进程的全部虚拟地址空间加锁。防止出现内存交换,将该进程的地址空间交换到外存上。
mlockall 将所有映射到进程地址空间的内存上锁。这些页包括: 代码段,数据段,栈段,共享库,共享内存,user space kernel data,memory-mapped file. 当函数成功返回的时候,所有的被映射的页都在内存中。
flags 可取两个值:MCL_CURRENT,MCL_FUTURE
MCL_CURRENT: 表示对所有已经映射到进程地址空间的页上锁
MCL_FUTURE: 表示对所有将来映射到进程地空间的页都上锁。
函数返回: 成功返回 0,出错返回 - 1
此函数有两个重要的应用: real-time algorithms (实时算法) 和 high-security data processing (机密数据的处理)
real-time algorithms:对时间要非常高。
如果进程执行了一个 execve 类函数,所有的锁都会被删除。
内存锁不会被子进程继承。
内存锁不会叠加,即使多次调用 mlockall 函数,只调用一次 munlock 就会解锁
# 2. init
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 static int init (void ) { struct epoll_event epev ; int i; int ret; page_k = sysconf(_SC_PAGESIZE); if (page_k == -1 ) page_k = PAGE_SIZE; page_k /= 1024 ; epollfd = epoll_create(MAX_EPOLL_EVENTS); if (epollfd == -1 ) { ALOGE("epoll_create failed (errno=%d)" , errno); return -1 ; } ctrl_lfd = android_get_control_socket("lmkd" ); if (ctrl_lfd < 0 ) { ALOGE("get lmkd control socket failed" ); return -1 ; } ret = listen(ctrl_lfd, 1 ); if (ret < 0 ) { ALOGE("lmkd control socket listen failed (errno=%d)" , errno); return -1 ; } epev.events = EPOLLIN; epev.data.ptr = (void *)ctrl_connect_handler; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1 ) { ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)" , errno); return -1 ; } maxevents++; use_inkernel_interface = !access(INKERNEL_MINFREE_PATH, W_OK); if (use_inkernel_interface) { ALOGI("Using in-kernel low memory killer interface" ); } else { ret = init_mp(MEMPRESSURE_WATCH_LEVEL, (void *)&mp_event); if (ret) ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer" ); } for (i = 0 ; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) { procadjslot_list[i].next = &procadjslot_list[i]; procadjslot_list[i].prev = &procadjslot_list[i]; } return 0 ; }
# 2.1 创建 epoll
1 2 3 4 5 epollfd = epoll_create(MAX_EPOLL_EVENTS); if (epollfd == -1 ) { ALOGE("epoll_create failed (errno=%d)" , errno); return -1 ; }
整个 lmkd 都是依赖 epoll 机制,这里创建了 9 个 event:
1 2 3 4 5 #define MAX_EPOLL_EVENTS (1 + MAX_DATA_CONN + VMPRESS_LEVEL_COUNT + 1 + 1)
# 2.2 初始化 socket lmkd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ctrl_sock.sock = android_get_control_socket("lmkd" ); if (ctrl_sock.sock < 0 ) { ALOGE("get lmkd control socket failed" ); return -1 ; } ret = listen(ctrl_sock.sock, MAX_DATA_CONN); if (ret < 0 ) { ALOGE("lmkd control socket listen failed (errno=%d)" , errno); return -1 ; } epev.events = EPOLLIN; ctrl_sock.handler_info.handler = ctrl_connect_handler; epev.data.ptr = (void *)&(ctrl_sock.handler_info); if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_sock.sock, &epev) == -1 ) { ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)" , errno); return -1 ; } maxevents++;
ctrl_sock 主要存储的是 socket lmkd 的 fd 和 handle info,主要注意这里的 ctrl_connect_handler ()
该函数时 socket /dev/socket/lmkd 有信息时的处理函数,lmkd 的客户端 AMS.mProcessList 会通过 socket /dev/socket/lmkd 与 lmkd 进行通信。
# 2.3 确定是否使用 LMK 驱动程序
1 2 3 4 #define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree" has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK); use_inkernel_interface = has_inkernel_module;
通过 access
函数确认旧的节点是否还存在,用于确认 kernel 是否还在使用 LMK 程序(kernel 4.12 已废弃)
# 2.4 init_monitors
该函数是 init 函数中的核心了,这里用来注册 PSI 的监视器策略(Android11 及以后)或者是 common 的 adj 策略 (vmpressure,Android11 之前),并将其添加到 epoll 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static bool init_monitors () { use_psi_monitors = property_get_bool("ro.lmk.use_psi" , true ) && init_psi_monitors(); if (!use_psi_monitors && (!init_mp_common(VMPRESS_LEVEL_LOW) || !init_mp_common(VMPRESS_LEVEL_MEDIUM) || !init_mp_common(VMPRESS_LEVEL_CRITICAL))) { ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer" ); return false ; } if (use_psi_monitors) { ALOGI("Using psi monitors for memory pressure detection" ); } else { ALOGI("Using vmpressure for memory pressure detection" ); } return true ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 - 如果使用vmpressure,则通过init_mp_common 来初始化kill 策略; - 如果使用PSI,则通过init_psi_monitors 来初始化kill 策略; 所以lmkd 中如果使用 PSI ,要求 ro.lmk.use_psi 为 true(注:博主说的其实有点问题,property_get_bool函数中的参数true为默认值,该值不设置即默认返回为true,并不需要设置为true)。 另外,lmkd 支持旧模式的kill 策略,只要 ro.lmk.use_new_strategy 设为false,或者将ro.lmk.use_minfree_levels 设为true(针对非低内存设备,即ro.config.low_ram 不为true)。 继续深入分析init_psi_monitors()函数。 ```c static bool init_psi_monitors() { /* * When PSI is used on low-ram devices or on high-end devices without memfree levels * use new kill strategy based on zone watermarks, free swap and thrashing stats */ bool use_new_strategy = property_get_bool("ro.lmk.use_new_strategy", low_ram_device || !use_minfree_levels); /* In default PSI mode override stall amounts using system properties */ if (use_new_strategy) { /* Do not use low pressure level */ psi_thresholds[VMPRESS_LEVEL_LOW].threshold_ms = 0; psi_thresholds[VMPRESS_LEVEL_MEDIUM].threshold_ms = psi_partial_stall_ms; psi_thresholds[VMPRESS_LEVEL_CRITICAL].threshold_ms = psi_complete_stall_ms; } // mp应该时memory pressure的意思 if (!init_mp_psi(VMPRESS_LEVEL_LOW, use_new_strategy)) { return false; } if (!init_mp_psi(VMPRESS_LEVEL_MEDIUM, use_new_strategy)) { destroy_mp_psi(VMPRESS_LEVEL_LOW); return false; } if (!init_mp_psi(VMPRESS_LEVEL_CRITICAL, use_new_strategy)) { destroy_mp_psi(VMPRESS_LEVEL_MEDIUM); destroy_mp_psi(VMPRESS_LEVEL_LOW); return false; } return true; }
函数比较简单的,最开始的变量 use_new_strategy
用以确认是使用 PSI 策略还是 vmpressure。如果是使用 PSI 策略, psi_thresholds
数组中的 threshold_ms
需要重新赋值为 prop 指定的值(也就是说支持动态配置)。最后通过 init_mp_psi
为每个级别的 strategy 进行最后的注册,当然对于 PSI,只有 some 和 full 等级,所以与 level 中的 medium 和 critical 分别对应。
这里的 psi_thresholds
数组中 threshold_ms
通过 prop:
ro.lmk.psi_partial_stall_ms low_ram 默认为 200ms,PSI 默认为 70ms;
ro.lmk.psi_complete_stall_ms 默认 700ms;
对于 init_mp_psi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static bool init_mp_psi (enum vmpressure_level level, bool use_new_strategy) { int fd; if (!psi_thresholds[level].threshold_ms) { return true ; } fd = init_psi_monitor(psi_thresholds[level].stall_type, psi_thresholds[level].threshold_ms * US_PER_MS, PSI_WINDOW_SIZE_MS * US_PER_MS); if (fd < 0 ) { return false ; } vmpressure_hinfo[level].handler = use_new_strategy ? mp_event_psi : mp_event_common; vmpressure_hinfo[level].data = level; if (register_psi_monitor(epollfd, fd, &vmpressure_hinfo[level]) < 0 ) { destroy_psi_monitor(fd); return false ; } maxevents++; mpevfd[level] = fd; return true ; }
通过 init_psi_monitor 将不同 level 的值写入节点 /proc/pressure/memory,后期阈值如果超过了设定就会触发一次 epoll;
根据 use_new_strategy,选择是新策略 mp_event_psi,还是旧模式 mp_event_common,详细的策略见第 8 节和第 10 节;
通过 register_psi_monitor 将节点 /proc/pressure/memory 添加到 epoll 中监听;
1 2 3 4 5 6 static void mp_event_psi (int data, uint32_t events, struct polling_params *poll_params) { enum reclaim_state { NO_RECLAIM = 0 , KSWAPD_RECLAIM, DIRECT_RECLAIM, };
# 2.5 标记进入 lmkd 流程
1 2 property_set("sys.lmk.reportkills" , "1" );
# 2.6 其他初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 memset (killcnt_idx, KILLCNT_INVALID_IDX, sizeof (killcnt_idx)); if (reread_file(&file_data) == NULL ) { ALOGE("Failed to read %s: %s" , file_data.filename, strerror(errno)); } pidfd = TEMP_FAILURE_RETRY(pidfd_open(getpid(), 0 )); if (pidfd < 0 ) { pidfd_supported = (errno != ENOSYS); } else { pidfd_supported = true ; close(pidfd); }
这里主要是 reread_file
函数,用来占坑。通过读取 /proc/zoneinfo
,创建一个最大 size 的 buffer,后面的其他节点都直接使用该 buffer,而不用再重新 malloc。详细看 reread_file()
中的 buf 变量。
另外,通过 sys_pidfd_open
,确定是否支持 pidfd_open
的 syscall。
至此,init 基本剖析完成,主要:
创建 epoll,用以监听 9 个 event;
初始化 socket /dev/socket/lmkd
,并将其添加到 epoll 中;
根据 prop ro.lmk.use_psi
确认是否使用 PSI 还是 vmpressure;
根据 prop ro.lmk.use_new_strategy
或者通过 prop ro.lmk.use_minfree_levels
和 prop ro.config.low_ram
使用 PSI 时的新策略还是旧策略;
新、旧策略主要体现在 mp_event_psi
和 mp_event_common
处理,而本质都是通过节点 /proc/pressure/memory
获取内存压力是否达到 some/full 指定来确认是否触发 event;
后期 epoll 触发主要的处理函数是 mp_event_psi
或 mp_event_common
;
# 3. mainloop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static void mainloop (void ) { while (1 ) { struct epoll_event events [maxevents ]; int nevents; int i; ctrl_dfd_reopened = 0 ; nevents = epoll_wait(epollfd, events, maxevents, -1 ); if (nevents == -1 ) { if (errno == EINTR) continue ; ALOGE("epoll_wait failed (errno=%d)" , errno); continue ; } for (i = 0 ; i < nevents; ++i) { if (events[i].events & EPOLLERR) ALOGD("EPOLLERR on event #%d" , i); if (events[i].data.ptr) (*(void (*)(uint32_t ))events[i].data.ptr)(events[i].events); } } }
在 mainloop 方法中,epoll_wait 在等到 lmkd socket 的 listen 事件到来,然后再调用 event.data.ptr 方法。在 Init 方法中,我们将 event.data.ptr 指向 ctrl_connect_handler 方法。
# 4.ctrl_connect_handler 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static void ctrl_connect_handler (uint32_t events __unused) { struct sockaddr addr ; socklen_t alen; struct epoll_event epev ; if (ctrl_dfd >= 0 ) { ctrl_data_close(); ctrl_dfd_reopened = 1 ; } alen = sizeof (addr); 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; epev.data.ptr = (void *)ctrl_data_handler; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_dfd, &epev) == -1 ) { ALOGE("epoll_ctl for data connection socket failed; errno=%d" , errno); ctrl_data_close(); return ; } }
ctrl_connect_handler 方法在处理 lmkd socket 的 listen 事件时,会像 epoll 创建另一个 epoll 事件一样,用于处理 lmkd socket 的 accept 事件,accetpt 事件的处理方法为 ctrl_data_handler
。
# 5. ctrl_data_handler 方法
1 2 3 4 5 6 7 8 9 10 static void ctrl_data_handler (uint32_t events) { if (events & EPOLLHUP) { ALOGI("ActivityManager disconnected" ); if (!ctrl_dfd_reopened) ctrl_data_close(); } else if (events & EPOLLIN) { ctrl_command_handler(); } }
当时添加到 epoll 时是以 EPOLLIN 添加的,所以这里接着会调用 ctrl_command_handler
# 6. ctrl_command_handler 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 static void ctrl_command_handler (void ) { int ibuf[CTRL_PACKET_MAX / sizeof (int )]; int len; int cmd = -1 ; int nargs; int targets; len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX); if (len <= 0 ) return ; nargs = len / sizeof (int ) - 1 ; if (nargs < 0 ) goto wronglen; cmd = ntohl(ibuf[0 ]); switch (cmd) { case LMK_TARGET: targets = nargs / 2 ; if (nargs & 0x1 || targets > (int )ARRAY_SIZE(lowmem_adj)) goto wronglen; cmd_target(targets, &ibuf[1 ]); break ; case LMK_PROCPRIO: if (nargs != 3 ) goto wronglen; cmd_procprio(ntohl(ibuf[1 ]), ntohl(ibuf[2 ]), ntohl(ibuf[3 ])); break ; case LMK_PROCREMOVE: if (nargs != 1 ) goto wronglen; cmd_procremove(ntohl(ibuf[1 ])); break ; default : ALOGE("Received unknown command code %d" , cmd); return ; } return ; wronglen: ALOGE("Wrong control socket read length cmd=%d len=%d" , cmd, len); }
主要处理从 ProcessList.java 中发出的几个 lmk command:
1 2 3 4 5 6 enum lmk_cmd { LMK_TARGET, LMK_PROCPRIO, LMK_PROCREMOVE, };
其中 LMK_TARGET 用于更新系统 oom_adj,framework 发出该 command 的方法是 PorcessList.updateOomLevels ();LMK_PROCPRIO 用于更新进程 adj,framework 发出该 command 的方法是 PorcessList.setOomAdj ();LMK_PROCPRIO 用于移除进程,framework 发出该 command 的方法是 PorcessList.remove ()。
# 6.1 cmd_target 方法
从 ProcessList.java 中得知在 ProcessList 构造时会初始化一次,另外会在 ATMS.updateConfiguration 是会触发:
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
1 2 3 4 5 6 7 8 9 10 11 12 public boolean updateConfiguration (Configuration values) { mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()" ); synchronized (mGlobalLock) { ... mH.sendMessage(PooledLambda.obtainMessage( ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal, DEFAULT_DISPLAY)); ... } }
最终会调用到 ProcessList.updateOomLevels ()
frameworks/base/servcies/core/java/com/android/server/am/ProcessList.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private void updateOomLevels (int displayWidth, int displayHeight, boolean write) { ... if (write) { ByteBuffer buf = ByteBuffer.allocate(4 * (2 * mOomAdj.length + 1 )); buf.putInt(LMK_TARGET); for (int i = 0 ; i < mOomAdj.length; i++) { buf.putInt((mOomMinFree[i] * 1024 )/PAGE_SIZE); buf.putInt(mOomAdj[i]); } writeLmkd(buf, null); ... } }
系统通过这个函数计算 oom adj 的 minfree,并将各个级别的 minfree 和 oom_adj_score 传入到 lmkd 中,继续跟进 lmkd 的 cmd_target:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static void cmd_target (int ntargets, int *params) { int i; if (ntargets > (int )ARRAY_SIZE(lowmem_adj)) return ; for (i = 0 ; i < ntargets; i++) { lowmem_minfree[i] = ntohl(*params++); lowmem_adj[i] = ntohl(*params++); } lowmem_targets_size = ntargets; if (use_inkernel_interface) { char minfreestr[128 ]; char killpriostr[128 ]; minfreestr[0 ] = '\0' ; killpriostr[0 ] = '\0' ; for (i = 0 ; i < lowmem_targets_size; i++) { char val[40 ]; if (i) { strlcat(minfreestr, "," , sizeof (minfreestr)); strlcat(killpriostr, "," , sizeof (killpriostr)); } snprintf (val, sizeof (val), "%d" , lowmem_minfree[i]); strlcat(minfreestr, val, sizeof (minfreestr)); snprintf (val, sizeof (val), "%d" , lowmem_adj[i]); strlcat(killpriostr, val, sizeof (killpriostr)); } writefilestring(INKERNEL_MINFREE_PATH, minfreestr); writefilestring(INKERNEL_ADJ_PATH, killpriostr); } }
cmd_target () 函数的目的就是将 framework 传过来的数值记录到数组中,即将 minfreestr
和 killpriostr
然后将这两个数组拼接成字符串输入,然后写入到内核对应的位置。
# 6.2 cmd_procprio 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static void cmd_procprio (int pid, int uid, int oomadj) { struct proc *procp ; char path[80 ]; char val[20 ]; if (oomadj < OOM_DISABLE || oomadj > OOM_ADJUST_MAX) { ALOGE("Invalid PROCPRIO oomadj argument %d" , oomadj); return ; } 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); if (use_inkernel_interface) return ; procp = pid_lookup(pid); if (!procp) { procp = malloc (sizeof (struct proc)); if (!procp) { return ; } procp->pid = pid; procp->uid = uid; procp->oomadj = oomadj; proc_insert(procp); } else { proc_unslot(procp); procp->oomadj = oomadj; proc_slot(procp); } }
因为现在使用的是内核接口,所以只需要将 oomadj 的数值写入到 /proc/[pid]/oom_source_adj
中即可起到更新进程的 oomadj 的效果。接下来分析 command 处理方法 cmd_procremove 方法。
# 6.3 cmd_procremove 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static void cmd_procremove (int pid) { if (use_inkernel_interface) return ; pid_remove(pid); kill_lasttime = 0 ; } static int pid_remove (int pid) { int hval = pid_hashfn(pid); struct proc *procp ; struct proc *prevp ; for (procp = pidhash[hval], prevp = NULL ; procp && procp->pid != pid; procp = procp->pidhash_next) prevp = procp; if (!procp) return -1 ; if (!prevp) pidhash[hval] = procp->pidhash_next; else prevp->pidhash_next = procp->pidhash_next; proc_unslot(procp); free (procp); return 0 ; }
如果未使用内核接口则调用 pid_remove
进行移除工作。
整理如下:
lmkd.c
对应方法
执行动作
LMK_PROCPRIO
cmd_procprio
写 /proc/oom_score_adj
LMK_TARGET
cmd_target
写 /sys/moudle/lowmemorykiller/parameters/minfree 写 /sys/module/lowmemorykiller/parameters/adj
LMK_PROCREMOVE
cmd_procremove
删除 /proc/
# 7. mp_event_common(回收进程)
前面和 socket lmkd 相关的内容主要用于设置 lmk 参数和进程 oomadj。当系统的物理内存不足时,将会触发 mp 事件,这个时候 lmkd 就需要通过杀死一些进程来释放内存页了。
前面我们已经知道,mp 事件发生后,执行的是 mp_event_common
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static void mp_event_common (int data, uint32_t events __unused) { int ret; unsigned long long evcount; int64_t mem_usage, memsw_usage; int64_t mem_pressure; enum vmpressure_level lvl ; union meminfo mi ; union zoneinfo zi ; static struct timeval last_report_tm ; static unsigned long skip_count = 0 ; enum vmpressure_level level = (enum vmpressure_level)data; long other_free = 0 , other_file = 0 ; int min_score_adj; int pages_to_free = 0 ; int minfree = 0 ; static struct reread_data mem_usage_file_data = { .filename = MEMCG_MEMORY_USAGE, .fd = -1 , }; static struct reread_data memsw_usage_file_data = { .filename = MEMCG_MEMORYSW_USAGE, .fd = -1 , }; ... ... }
前面这一部分是函数用到的变量的定义。在 ANSI C 那个年代,局部变量都要在函数里先声明,但这对代码的可读性其实没有什么帮助(因为定义跟使用他的上下文脱节了)。这里我们先直接忽略它们,后面再在具体的上下文来看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static void mp_event_common(int data, uint32_t events __unused) { ... ... enum vmpressure_level lvl; enum vmpressure_level level = (enum vmpressure_level)data; /* * Check all event counters from low to critical * and upgrade to the highest priority one. By reading * eventfd we also reset the event counters. */ for (lvl = VMPRESS_LEVEL_LOW; lvl < VMPRESS_LEVEL_COUNT; lvl++) { if (mpevfd[lvl] != -1 && TEMP_FAILURE_RETRY(read(mpevfd[lvl], &evcount, sizeof(evcount))) > 0 && evcount > 0 && lvl > level) { level = lvl; } } ... ... }
前面 init_mp_common
函数里面,我们把 evfd
存在了 mpevfd
数组里,为的就是这个时候能够通过读取它们的来判断是否有更高级别的 mp 事件。至此,变量 level
表示当前发生的最高 level 的 mp 事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static void mp_event_common(int data, uint32_t events __unused) { ... ... static struct timeval last_report_tm; static unsigned long skip_count = 0; if (kill_timeout_ms) { struct timeval curr_tm; gettimeofday(&curr_tm, NULL); if (get_time_diff_ms(&last_report_tm, &curr_tm) < kill_timeout_ms) { skip_count++; return; } } if (skip_count > 0) { ALOGI("%lu memory pressure events were skipped after a kill!", skip_count); skip_count = 0; } ... ... }
kill_timeout_ms
是我们在 main
函数里通过读系统属性设置的,表示上一次 kill 后,等多 kill_timeout_ms
再杀下一个。 last_report_tm
在后面我们成功回收进程后会更新他的时间。这里要注意, skip_count
和 last_report_tm
都是 static
变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 union zoneinfo { struct { int64_t nr_free_pages; int64_t nr_file_pages; int64_t nr_shmem; int64_t nr_unevictable; int64_t workingset_refault; int64_t high; /* fields below are calculated rather than read from the file */ int64_t totalreserve_pages; } field; int64_t arr[ZI_FIELD_COUNT]; }; union meminfo { struct { int64_t nr_free_pages; int64_t cached; int64_t swap_cached; int64_t buffers; int64_t shmem; int64_t unevictable; int64_t free_swap; int64_t dirty; /* fields below are calculated rather than read from the file */ int64_t nr_file_pages; } field; int64_t arr[MI_FIELD_COUNT]; }; static void mp_event_common(int data, uint32_t events __unused) { ... ... union meminfo mi; union zoneinfo zi; if (meminfo_parse(&mi) < 0 || zoneinfo_parse(&zi) < 0) { ALOGE("Failed to get free memory!"); return; } ... ... }
union meminfo mi
和 union zoneinfo zi
表示系统当前的内存使用情况。 meminfo_parse
和 zoneinfo_parse
分别读取 /proc/meminfo
和 /proc/zoneinfo
并将解析得到的数据填充到 mi/zi
。(读者可以开个机器,然后 cat /proc/meminfo
看看具体的输出。关于他们解释,参考 man 5 proc
)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 static void mp_event_common(int data, uint32_t events __unused) { ... ... long other_free = 0, other_file = 0; int min_score_adj; if (use_minfree_levels) { int i; other_free = mi.field.nr_free_pages - zi.field.totalreserve_pages; if (mi.field.nr_file_pages > (mi.field.shmem + mi.field.unevictable + mi.field.swap_cached)) { other_file = (mi.field.nr_file_pages - mi.field.shmem - mi.field.unevictable - mi.field.swap_cached); } else { other_file = 0; } min_score_adj = OOM_SCORE_ADJ_MAX + 1; for (i = 0; i < lowmem_targets_size; i++) { minfree = lowmem_minfree[i]; if (other_free < minfree && other_file < minfree) { min_score_adj = lowmem_adj[i]; break; } } if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) { if (debug_process_killing) { ALOGI("Ignore %s memory pressure event " "(free memory=%ldkB, cache=%ldkB, limit=%ldkB)", level_name[level], other_free * page_k, other_file * page_k, (long)lowmem_minfree[lowmem_targets_size - 1] * page_k); } return; } /* Free up enough pages to push over the highest minfree level */ pages_to_free = lowmem_minfree[lowmem_targets_size - 1] - ((other_free < other_file) ? other_free : other_file); goto do_kill; } ... ... }
use_minfree_levels
同样是从系统属性读取的配置,表示使用当我们准备杀死应用的时候,使用系统剩余的内存和文件缓存阈值作为判断依据。
other_free
表示系统可用的内存页的数目。
nr_file_pages
等于 mi->field.cached
(文件在内存中的缓存)加上 mi->field.swap_cached
(swap 出去又读进了内存的数据)加上 mi->field.buffers
(硬盘的一个临时缓存),
mi.shmem
表示 tmpfs 使用的内存数, unevictable
表示那些不能 swap out 的内存。
最后 other_file
基本就等于除 tmpfs 和 unevictable 外的缓存在内存的文件所占用的 page 数。
有了 other_free
和 other_file
后,我们根据 lowmem_minfree
的值来确定 min_score_adj
。 min_score_adj
表示可以回收的最低的 oomadj 值(oomadj 越大,优先级越低,越容易被杀死),oomadj 小于 min_score_adj
的进程在这次回收过程中不会被杀死。
回想一下前面的 cmd_target
, lowmem_minfree
和 lowmem_adj
值就是在他里面设置的。
goto do_kill
在函数比较靠后的地方,我们后面再看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 struct { int64_t min_nr_free_pages; /* recorded but not used yet */ int64_t max_nr_free_pages; } low_pressure_mem = { -1, -1 }; static void mp_event_common(int data, uint32_t events __unused) { ... ... if (level == VMPRESS_LEVEL_LOW) { record_low_pressure_levels(&mi); } ... ... } void record_low_pressure_levels(union meminfo *mi) { if (low_pressure_mem.min_nr_free_pages == -1 || low_pressure_mem.min_nr_free_pages > mi->field.nr_free_pages) { if (debug_process_killing) { ALOGI("Low pressure min memory update from %" PRId64 " to %" PRId64, low_pressure_mem.min_nr_free_pages, mi->field.nr_free_pages); } low_pressure_mem.min_nr_free_pages = mi->field.nr_free_pages; } /* * Free memory at low vmpressure events occasionally gets spikes, * possibly a stale low vmpressure event with memory already * freed up (no memory pressure should have been reported). * Ignore large jumps in max_nr_free_pages that would mess up our stats. */ if (low_pressure_mem.max_nr_free_pages == -1 || (low_pressure_mem.max_nr_free_pages < mi->field.nr_free_pages && mi->field.nr_free_pages - low_pressure_mem.max_nr_free_pages < low_pressure_mem.max_nr_free_pages * 0.1)) { if (debug_process_killing) { ALOGI("Low pressure max memory update from %" PRId64 " to %" PRId64, low_pressure_mem.max_nr_free_pages, mi->field.nr_free_pages); } low_pressure_mem.max_nr_free_pages = mi->field.nr_free_pages; } }
low_pressure_mem.min_nr_free_pages
记录的是目前遇到的最低可用内存页数, low_pressure_mem.max_nr_free_pages
记录的是目前遇到的最大可用的内存页数。就像代码中注释说的,有时候可用的内存数会突然暴涨,这里过滤掉了这种情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #define MEMCG_MEMORY_USAGE "/dev/memcg/memory.usage_in_bytes" #define MEMCG_MEMORYSW_USAGE "/dev/memcg/memory.memsw.usage_in_bytes" static void mp_event_common(int data, uint32_t events __unused) { ... ... if (level_oomadj[level] > OOM_SCORE_ADJ_MAX) { /* Do not monitor this pressure level */ return; } int64_t mem_usage, memsw_usage; static struct reread_data mem_usage_file_data = { .filename = MEMCG_MEMORY_USAGE, .fd = -1, }; static struct reread_data memsw_usage_file_data = { .filename = MEMCG_MEMORYSW_USAGE, .fd = -1, }; if ((mem_usage = get_memory_usage(&mem_usage_file_data)) < 0) { goto do_kill; } if ((memsw_usage = get_memory_usage(&memsw_usage_file_data)) < 0) { goto do_kill; } ... ... } static int64_t get_memory_usage(struct reread_data *file_data) { int ret; int64_t mem_usage; char buf[32]; if (reread_file(file_data, buf, sizeof(buf)) < 0) { return -1; } if (!parse_int64(buf, &mem_usage)) { ALOGE("%s parse error", file_data->filename); return -1; } if (mem_usage == 0) { ALOGE("No memory!"); return -1; } return mem_usage; }
get_memory_usage
的实现很简单,就是读取 reread_data.filename
的内容并转换为 int64
。这里还需要注意的是 mem_usage_file_data
和 memsw_usage_file_data
是静态变量。第一次打开文件后,会把文件描述符缓存在 reread_data.fd
里。
mem_usage
是所用的内存数, memsw_usage
是内存数加上 swap out 的内存数。接下来的代码根据这两个数据来计算内存压力(压力越大,swap 出去的内存就越多):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static void mp_event_common(int data, uint32_t events __unused) { ... ... // Calculate percent for swappinness. int64_t mem_pressure = (mem_usage * 100) / memsw_usage; if (enable_pressure_upgrade && level != VMPRESS_LEVEL_CRITICAL) { // We are swapping too much. if (mem_pressure < upgrade_pressure) { level = upgrade_level(level); if (debug_process_killing) { ALOGI("Event upgraded to %s", level_name[level]); } } } // If the pressure is larger than downgrade_pressure lmk will not // kill any process, since enough memory is available. if (mem_pressure > downgrade_pressure) { if (debug_process_killing) { ALOGI("Ignore %s memory pressure", level_name[level]); } return; } else if (level == VMPRESS_LEVEL_CRITICAL && mem_pressure > upgrade_pressure) { if (debug_process_killing) { ALOGI("Downgrade critical memory pressure"); } // Downgrade event, since enough memory available. level = downgrade_level(level); } ... ... }
注意这里 mem_pressure
计算的是 内存数 / (内存数 + swap)
, mem_pressure
越小,内存压力就越大。
enable_pressure_upgrade
、 upgrade_pressure
和 downgrade_pressure
的值是我们在 main
函数里根据系统属性设置的。
在内存压力比较大并且 enable_pressure_upgrade
打开的情况下,我们把内存压力向上提升一个等级(以期释放更多的内存);在内存压力小于 downgrade_pressure
的时候,内存是充足的,没有必要通过杀死应用来回收内存;如果内存压力中等(upgrade_pressure < mem_pressure < downgrade_pressure)但是 level 却是 critical,就给他降一级。
lmkd 在给 mp level 升级的时候需要打开 enable_pressure_upgrade(默认关闭),而降级却总是可行的,说明 lmkd 尽力在不杀死应用的情况下满足系统的内存需求。
到目前为止,我们得到了三组跟内存压力相关的参数:
在 use_minfree_levels
的情况下, min_score_adj
和 pages_to_free
内存压力 level
接下来,我们开始真正的进程回收工作:
# kill process
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static void mp_event_common(int data, uint32_t events __unused) { ... ... do_kill: if (low_ram_device) { /* For Go devices kill only one task */ if (find_and_kill_processes(level, level_oomadj[level], 0) == 0) { if (debug_process_killing) { ALOGI("Nothing to kill"); } } } else { ... ... } }
回收对象时分两大类,小内存设备和 “大” 内存设备。小内存设备一次就杀一个进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static int find_and_kill_processes(enum vmpressure_level level, int min_score_adj, int pages_to_free) { int i; int killed_size; int pages_freed = 0; for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) { struct proc *procp; while (true) { procp = kill_heaviest_task ? proc_get_heaviest(i) : proc_adj_lru(i); if (!procp) break; killed_size = kill_one_process(procp, min_score_adj, level); if (killed_size >= 0) { pages_freed += killed_size; if (pages_freed >= pages_to_free) { return pages_freed; } } } } return pages_freed; }
这里我们从 oomadj 最大的应用开始回收,直到回收的内存页数达到 pages_to_free
。对 low_ram_device
来说, pages_to_free
为 0,只有一个进程会被回收。
kill_heaviest_task
是从系统属性读的,默认为 false
。打开的情况下,在相关 oomadj 的进程里,我们优先回收使用内存最多的那个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static struct proc *proc_get_heaviest(int oomadj) { struct adjslot_list *head = &procadjslot_list[ADJTOSLOT(oomadj)]; struct adjslot_list *curr = head->next; struct proc *maxprocp = NULL; int maxsize = 0; while (curr != head) { int pid = ((struct proc *)curr)->pid; int tasksize = proc_get_size(pid); if (tasksize <= 0) { struct adjslot_list *next = curr->next; pid_remove(pid); curr = next; } else { if (tasksize > maxsize) { maxsize = tasksize; maxprocp = (struct proc *)curr; } curr = curr->next; } } return maxprocp; }
如果是 false
,调用的则是 proc_adj_lru
:
1 2 3 4 5 6 7 8 9 static struct adjslot_list *adjslot_tail(struct adjslot_list *head) { struct adjslot_list *asl = head->prev; return asl == head ? NULL : asl; } static struct proc *proc_adj_lru(int oomadj) { return (struct proc *)adjslot_tail(&procadjslot_list[ADJTOSLOT(oomadj)]); }
这里我们取的是列表的尾端;而插入新元素时,我们总是把它放在头端。
kill_one_process
通过向应用发送信号 SIGKILL
来杀死对方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 /* Kill one process specified by procp. Returns the size of the process killed */ static int kill_one_process(struct proc* procp, int min_score_adj, enum vmpressure_level level) { int pid = procp->pid; uid_t uid = procp->uid; char *taskname; int tasksize; int r; taskname = proc_get_name(pid); if (!taskname) { pid_remove(pid); return -1; } tasksize = proc_get_size(pid); if (tasksize <= 0) { pid_remove(pid); return -1; } r = kill(pid, SIGKILL); ALOGI( "Killing '%s' (%d), uid %d, adj %d\n" " to free %ldkB because system is under %s memory pressure oom_adj %d\n", taskname, pid, uid, procp->oomadj, tasksize * page_k, level_name[level], min_score_adj); pid_remove(pid); if (r) { ALOGE("kill(%d): errno=%d", pid, errno); return -1; } else { return tasksize; } return tasksize; }
接下来我们看不是 low_ram_device
的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static void mp_event_common(int data, uint32_t events __unused) { ... ... do_kill: if (low_ram_device) { ... ... } else { if (!use_minfree_levels) { /* If pressure level is less than critical and enough free swap then ignore */ if (level < VMPRESS_LEVEL_CRITICAL && mi.field.free_swap > low_pressure_mem.max_nr_free_pages) { if (debug_process_killing) { ALOGI("Ignoring pressure since %" PRId64 " swap pages are available ", mi.field.free_swap); } return; } /* Free up enough memory to downgrate the memory pressure to low level */ if (mi.field.nr_free_pages < low_pressure_mem.max_nr_free_pages) { pages_to_free = low_pressure_mem.max_nr_free_pages - mi.field.nr_free_pages; } else { if (debug_process_killing) { ALOGI("Ignoring pressure since more memory is " "available (%" PRId64 ") than watermark (%" PRId64 ")", mi.field.nr_free_pages, low_pressure_mem.max_nr_free_pages); } return; } min_score_adj = level_oomadj[level]; } ... ... } }
前面我们总结过,在 !use_minfree_levels
的情况下,我们只有一个 mp level
,还需要 min_score_adj
和 pages_to_free
才能开始回收进程。
low_pressure_mem.max_nr_free_pages
是前面我们在 record_low_pressure_levels
中记录的, free_swap
是系统 swap 分区空余的大小;如果内存压力不是 critical 并且 swap 分区还足够大,就不回收进程了(lmkd 也是不容易啊,只有在实在没有办法了才杀我们应用)。
此外,在空余内存页比我们遇到过的发生 mp 事件时系统剩余内存最多的那次还要多的时候(比以往最好的情况还要好),也不回收应用。即便真的需要回收内存,我们也只回收到系统(内存)状态跟以往最好的那次为止( pages_to_free = max_nr_free_pages - nr_free_pages
)。
这里计算出 pages_to_free
和 min_scrore_adj
后,我们下面就该回收进程了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static void mp_event_common(int data, uint32_t events __unused) { ... ... do_kill: if (low_ram_device) { ... ... } else { if (!use_minfree_levels) { // compute pages_to_free & min_score_adj } pages_freed = find_and_kill_processes(level, min_score_adj, pages_to_free); if (use_minfree_levels) { ALOGI("Killing because cache %ldkB is below " "limit %ldkB for oom_adj %d\n" " Free memory is %ldkB %s reserved", other_file * page_k, minfree * page_k, min_score_adj, other_free * page_k, other_free >= 0 ? "above" : "below"); } if (pages_freed < pages_to_free) { ALOGI("Unable to free enough memory (pages to free=%d, pages freed=%d)", pages_to_free, pages_freed); } else { ALOGI("Reclaimed enough memory (pages to free=%d, pages freed=%d)", pages_to_free, pages_freed); gettimeofday(&last_report_tm, NULL); } } }
find_and_kill_processes
的实现在前面我们已经看了,他根据进程的 oomajd 值从大到小回收那些 oomadj 值比 min_score_adj
大的应用,并且只回收 pages_to_free
个内存页就停止。
最后把当前时间记录在 last_report_tm
,他表示上次成功回收进程的时间(和 kill_timeout_ms
组合使用)。
# (四) lowermemorykiller.c
# 1. LMK 的驱动加载函数
1 2 3 4 5 static int __init lowmem_init (void ) { register_shrinker(&lowmem_shrinker); return 0 ; }
init 方法中注册了一个 shrinker 到内核的 shrinker 链表。当内存不足时,kswapd 线程会遍历一张 shrinker 链表,并回调已注册的 shrinker 函数来回收内存 page,kswapd 还会周期性唤醒来执行内存操作。每个 zone 维护 active_list 和 inactive_list 链表,内核根据页面活动状态将 page 在这两个链表之间移动,最终通过 shrink_slab 和 shrink_zone 来回收内存页。
# 2. 数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 static short lowmem_adj[6 ] = { 0 , 1 , 6 , 12 , }; static int lowmem_adj_size = 4 ;static int lowmem_minfree[6 ] = { 3 * 512 , 2 * 1024 , 4 * 1024 , 16 * 1024 , };
第一个数组 lowmem_adj 最多有 6 个元素(默认只定义了 4 个),它表示可用内存容量处于 “某层级” 时需要被处理的 adj 值;第二个数组则是对 “层级” 的描述。举个例子,lowmwm_minfree 的第一个元素是 3512,即 3 512*lowmwm_adj_size=6MB。也就是说,当可用内存小于 6MB 时,Killer 需要清理 adj 值为 0(即 lowmem_adj 的第一个元素)以下的所有进程。其中 adj 的取值范围是 - 17~15,数字越小表示进程级别越高(通常只有 0~15 被使用)。
lowmem_adj 和 lowmem_adj_size 这两个数组只是系统的预定义值,我们还可以根据项目的实际需求来做定制。Android 系统提供了相应的文件来供我们修改这两组值,路径如下:
1 2 /sys/module/lowmemorykiller/parameters/adj /sys/module/lowmemorykiller/parameters/minfree
也可以在 init.rc 中加入语句
1 2 write /sys/module/lowmemorykiller/parameters/adj 0 ,8 write /sys/module/lowmemorykiller/parameters/minfree 1024 ,4096
# 3. Adj 进程规划
ADJ
Description
UNKNOW_ADJ
一般指将要会缓存进程,无法获取确定值
HIDDEN_APP_MAX_ADJ=15
当前只运行了不可见的 Activity 组件的进程,分别为不可见进程 adj 的最大值、最小值
HIDDEN_APP_MIN_ADJ=9
SERVICE_B_ADJ=8
B list of Service。和 A list 相比,它们对用户的黏合度要小一些
PREVIOUS_APP_ADJ=7
用户前一次交互的进程。按照用户的使用习惯,人们经常会在几个常用进程间切换,所以这类进程得到再次运行的概率比较大
HOME_APP_ADJ=6
Home 进程
SERVICE_ADJ=5
当前运行了 application service 的进程
BACKUP_APP_ADJ=4
用于承载 backup 相关操作的进程
HEAVY_WEIGHT_APP_ADJ=3
重量级应用程序进程
PERCEPTIBLE_APP_ADJ=2
这类进程能被用户感觉到不可见,如后台运行的音乐播放器
VISIBLE_APP_ADJ=1
有前台可见的 Activity 的进程,如果轻易杀死这类进程将严重影响用户的体验。
FOREGROUND_APP_ADJ=0
当前正在前台运行的进程,也就是用户正在交互的那个程序
PERSISTENT_PROC_ADJ=-12
Persistent 性质的进程,如 telephony
SYSTEM_ADJ=-16
系统进程
NATIVE_ADJ=-17
native 进程(不被系统管理)
通过以下方法,我们可以自己改变进程的 oom_adj 值:
# 3.1 写文件
和前面的 adj 和 minfree 类似,进程的 oom_adj 也可以通过写文件的形式修改,路径为 /proc//oom_adj。比如 init.rc 中就有如下语句:
1 2 on early-init write /proc/1 /oom_adj-16
PID 值为 1 的进程是 init 程序,这里将此进程的 adj 改为 - 16,以保证它不会被杀死。
# 3.2 android: persistent
对于某些非常重要的应用程序,我们不希望它们被系统杀死。一个最简单的方法就是在它的 AndroidManifest.xml 文件中给 “application” 标签添加 “android:persistent==true” 属性。不过将应用程序设置为常驻内存要特别慎重,如果应用程序本身不够完善,而系统又不能通过支持方式回收它的话,则有可能导致意想不到的问题。
# 4. lowmem_shrinker 结构体
1 2 3 4 5 static struct shrinker lowmem_shrinker = { .scan_objects = lowmem_scan, .count_objects = lowmem_count, .seeks = DEFAULT_SEEKS * 16 };
# 4.1 lowmem_scan 方法
当系统内存不足时会回调 lowmem_scan 方法来 kill 应用以达到释放内存的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 static unsigned long lowmem_scan (struct shrinker *s, struct shrink_control *sc) { struct task_struct *tsk ; struct task_struct *selected = NULL ; unsigned long 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) - total_swapcache_pages(); 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++) { minfree = lowmem_minfree[i]; if (other_free < minfree && other_file < minfree) { min_score_adj = lowmem_adj[i]; break ; } } lowmem_print(3 , "lowmem_scan %lu, %x, ofree %d %d, ma %hd\n" , sc->nr_to_scan, sc->gfp_mask, other_free, other_file, min_score_adj); if (min_score_adj == OOM_SCORE_ADJ_MAX + 1 ) { lowmem_print(5 , "lowmem_scan %lu, %x, return 0\n" , sc->nr_to_scan, sc->gfp_mask); return 0 ; } selected_oom_score_adj = min_score_adj; rcu_read_lock(); for_each_process(tsk) { struct task_struct *p ; short oom_score_adj; if (tsk->flags & PF_KTHREAD) continue ; p = find_lock_task_mm(tsk); if (!p) continue ; if (test_tsk_thread_flag(p, TIF_MEMDIE) && time_before_eq(jiffies, lowmem_deathpending_timeout)) { task_unlock(p); rcu_read_unlock(); return 0 ; } oom_score_adj = p->signal->oom_score_adj; if (oom_score_adj < min_score_adj) { task_unlock(p); continue ; } tasksize = get_mm_rss(p->mm); task_unlock(p); if (tasksize <= 0 ) continue ; if (selected) { if (oom_score_adj < selected_oom_score_adj) continue ; if (oom_score_adj == selected_oom_score_adj && tasksize <= selected_tasksize) continue ; } selected = p; selected_tasksize = tasksize; selected_oom_score_adj = oom_score_adj; lowmem_print(2 , "select '%s' (%d), adj %hd, size %d, to kill\n" , p->comm, p->pid, oom_score_adj, tasksize); } if (selected) { long cache_size = other_file * (long )(PAGE_SIZE / 1024 ); long cache_limit = minfree * (long )(PAGE_SIZE / 1024 ); long free = other_free * (long )(PAGE_SIZE / 1024 ); trace_lowmemory_kill(selected, cache_size, cache_limit, free ); 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, cache_size, cache_limit, min_score_adj, free ); lowmem_deathpending_timeout = jiffies + HZ; set_tsk_thread_flag(selected, TIF_MEMDIE); send_sig(SIGKILL, selected, 0 ); rem += selected_tasksize; } lowmem_print(4 , "lowmem_scan %lu, %x, return %lu\n" , sc->nr_to_scan, sc->gfp_mask, rem); rcu_read_unlock(); return rem; }
lowmem_scan 方法的主要思想就是首先获取当前内存剩余量,根据剩余量获取对应的 min free adj 值;接着遍历当前系统中的所有进程,从中挑选出进程的 oom adj 最大者,如果存在进程 oom adj 值相同,则挑选出其中占用内存最大的那个进程;最后向这个进程发送 SIGKILL 信息,以达到杀死该进程释放内存的效果。
# 4.2 lowmem_count 方法
1 2 3 4 5 6 7 8 static unsigned long lowmem_count (struct shrinker *s, struct shrink_control *sc) { return global_page_state(NR_ACTIVE_ANON) + global_page_state(NR_ACTIVE_FILE) + global_page_state(NR_INACTIVE_ANON) + global_page_state(NR_INACTIVE_FILE); }
lowmem_count 方法就是通过 shrinker 链表来判断 lmk 是否可用,如果可用则返回各部分占用的内存大小。
# 5. 整体流程
# 四、oom adj 算法
Android 进程在不同的时候处于不同的进程状态,也会根据重要性动态调整进程的 oom score。这样在 lmkd 中可以根据当前的内存使用情况,找到合适的 oom_score_adj,并将其 kill 以满足内存的持续使用。
下面主要分析 oom adj 算法作为 lmkd 机制的补充
Android 系统中计算各进程 adj 算法的核心方法
updateOomAdjLocked:更新 adj,当目标进程为空或者被杀则返回 false;否则返回 true
computeOomAdjLocked:计算 adj,返回计算后 RawAdj 值
appOomAdjLocked:应用 adj,当需要杀掉目标进程则返回 false;否则返回 true
当 Android 四大组件状态改变时会 updataOomAdjLocked () 来同步更新相应进程的 ADJ 优先级。这里需要说明一下,当同一个进程有多个决定其优先级的组件状态时,取优先级最高的 ADJ 最为最终的 ADJ。另外,进程会通过设置 maxAdj 来限定 ADJ 的上限。关于分析进程 ADJ 相关信息,常用命令如下:
dumpsys meminfo
dumpsys activity o
dumpsys activity p
# 1. ADJ < 0 的进程
NATIVE_ADJ (-1000):是由 init 进程 fork 出来的 Native 进程,并不受 system 管控;
SYSTEM_ADJ (-900):是指 system_server 进程;
PERSISTENT_PROC_ADJ (-800): 是指在 AndroidManifest.xml 中声明 android:persistent=”true” 的系统 (即带有 FLAG_SYSTEM 标记) 进程,persistent 进程一般情况并不会被杀,即便被杀或者发生 Crash 系统会立即重新拉起该进程。
PERSISTENT_SERVICE_ADJ (-700):是由 startIsolatedProcess () 方式启动的进程,或者是由 system_server 或者 persistent 进程所绑定 (并且带有 BIND_ABOVE_CLIENT 或者 BIND_IMPORTANT) 的服务进程
BACKUP_APP_ADJ (300):执行 bindBackupAgent () 过程的进程
HEAVY_WEIGHT_APP_ADJ (400): realStartActivityLocked () 过程,当应用的 privateFlags 标识 PRIVATE_FLAG_CANT_SAVE_STATE 的进程;
HOME_APP_ADJ (600):当类型为 ACTIVITY_TYPE_HOME 的应用,比如桌面 APP
PREVIOUS_APP_ADJ (700):用户上一个使用的 APP 进程
# 1.1 SYSTEM_ADJ(-900)
SYSTEM_ADJ: 仅指 system_server 进程。在执行 SystemServer 的 startBootstrapServices () 过程会调用 AMS.setSystemProcess (),将 system_server 进程的 maxAdj 设置成 SYSTEM_ADJ,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void setSystemProcess () { ... ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( "android" , STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY); mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader()); synchronized (this ) { ProcessRecord app = newProcessRecordLocked(info, info.processName, false , 0 ); app.persistent = true ; app.pid = MY_PID; app.maxAdj = ProcessList.SYSTEM_ADJ; app.makeActive(mSystemThread.getApplicationThread(), mProcessStats); synchronized (mPidsSelfLocked) { mPidsSelfLocked.put(app.pid, app); } updateLruProcessLocked(app, false , null ); updateOomAdjLocked(); } ... }
# 1.2 PERSISTENT_PROC_ADJ(-800)
PERSISTENT_PROC_ADJ:在 AndroidManifest.xml 中申明 android:persistent=”true” 的系统 (即带有 FLAG_SYSTEM 标记) 进程,称之为 persistent 进程。对于 persistent 进程常规情况都不会被杀,一旦被杀或者发生 Crash,进程会立即重启。
场景一:newProcessRecordLocked
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 final ProcessRecord newProcessRecordLocked (ApplicationInfo info, String customProcess, boolean isolated, int isolatedUid) { String proc = customProcess != null ? customProcess : info.processName; final int userId = UserHandle.getUserId(info.uid); int uid = info.uid; ... final ProcessRecord r = new ProcessRecord (stats, info, proc, uid); if (!mBooted && !mBooting && userId == UserHandle.USER_SYSTEM && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) { r.persistent = true ; r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ; } if (isolated && isolatedUid != 0 ) { r.maxAdj = ProcessList.PERSISTENT_SERVICE_ADJ; } return r; }
场景二: addAppLocked
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 final ProcessRecord addAppLocked (ApplicationInfo info, String customProcess, boolean isolated, String abiOverride) { ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName, info.uid, true ); } else { app = null ; } if (app == null ) { app = newProcessRecordLocked(info, customProcess, isolated, 0 ); updateLruProcessLocked(app, false , null ); updateOomAdjLocked(); } ... if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) { app.persistent = true ; app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ; } if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0 ) { mPersistentStartingProcesses.add(app); startProcessLocked(app, "added application" , customProcess != null ? customProcess : app.processName, abiOverride); } return app; }
# 1.3 PERSISTENT_SERVICE_ADJ(-700)
PERSISTENT_SERVICE_ADJ: startIsolatedProcess () 方式启动的进程,或者是由 system_server 或者 persistent 进程所绑定的服务进程。
场景一: newProcessRecordLocked
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 final ProcessRecord newProcessRecordLocked (ApplicationInfo info, String customProcess, boolean isolated, int isolatedUid) { String proc = customProcess != null ? customProcess : info.processName; final int userId = UserHandle.getUserId(info.uid); int uid = info.uid; ... final ProcessRecord r = new ProcessRecord(stats, info, proc, uid); if (!mBooted && !mBooting && userId == UserHandle.USER_SYSTEM && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) { r.persistent = true ; r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ; } if (isolated && isolatedUid != 0 ) { r.maxAdj = ProcessList.PERSISTENT_SERVICE_ADJ; } return r; }
调用链如下:
1 2 3 4 5 6 7 8 9 10 11 startOtherServices WebViewUpdateService.prepareWebViewInSystemServer WebViewUpdateServiceImpl.prepareWebViewInSystemServer WebViewUpdater.prepareWebViewInSystemServer WebViewUpdater.onWebViewProviderChanged SystemImpl.onWebViewProviderChanged WebViewFactory.onWebViewProviderChanged WebViewLibraryLoader.prepareNativeLibraries WebViewLibraryLoader.createRelros WebViewLibraryLoader.createRelroFile AMS.startIsolatedProcess
# 1.4 BACKUP_APP_ADL(300)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (mBackupTarget != null && app == mBackupTarget.app) { if (adj > ProcessList.BACKUP_APP_ADJ) { adj = ProcessList.BACKUP_APP_ADJ; if (procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; } app.adjType = "backup" ; app.cached = false ; } if (procState > ActivityManager.PROCESS_STATE_BACKUP) { procState = ActivityManager.PROCESS_STATE_BACKUP; app.adjType = "backup" ; } }
# 1.5 HEAVY_WEIGHT_APP_ADJ(400)
realStartActivityLocked () 过程,当应用的 privateFlags 标识 PRIVATE_FLAG_CANT_SAVE_STATE,设置 mHeavyWeightProcess 值;
finishHeavyWeightApp (), 置空 mHeavyWeightProcess 值;
# 1.6 HOME_APP_ADJ(600)
当类型为 ACTIVITY_TYPE_HOME 的应用启动后会设置 mHomeProcess, 比如桌面 APP
# 1.7 PREVIOUS_APP_ADJ(700)
场景 1: 用户上一个使用的包含 UI 的进程,为了给用户在两个 APP 之间更好的切换体验,将上一个进程 ADJ 设置到 PREVOUS_APP_ADJ 的档次。当 activityStoppedLocked () 过程会更新上一个应用
1 2 3 4 5 6 7 8 9 10 11 12 if (app == mPreviousProcess && app.activities.size() > 0 ) { if (adj > ProcessList.PREVIOUS_APP_ADJ) { adj = ProcessList.PREVIOUS_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false ; app.adjType = "previous" ; } if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; app.adjType = "previous" ; } }
场景 2: 当 provider 进程,上一次使用时间不超过 20S 的情况下,优先级不低于 PREVIOUS_APP_ADJ。provider 进程这个是 Android 7.0 以后新增的逻辑 ,这样做的好处是在内存比较低的情况下避免拥有 provider 的进程出现颠簸,也就是启动后杀,然后又被拉。
1 2 3 4 5 6 7 8 9 10 11 12 13 if (app.lastProviderTime > 0 && (app.lastProviderTime+mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) { if (adj > ProcessList.PREVIOUS_APP_ADJ) { adj = ProcessList.PREVIOUS_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false ; app.adjType = "recent-provider" ; } if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; app.adjType = "recent-provider" ; } }
# 2. FOREGROUND_APP_ADJ(0)
场景 1:满足以下任一条件的进程都属于 FOREGROUND_APP_ADJ (0) 优先级:
正处于 resumed 状态的 Activity
正执行一个生命周期回调的 Service(比如执行 onCreate,onStartCommand,onDestroy 等)
正执行 onReceive () 的 BroadcastReceiver
通过 startInstrumentation () 启动的进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_TOP_APP; app.adjType = "top-activity" ; foregroundActivities = true ; procState = PROCESS_STATE_CUR_TOP; } else if (app.instr != null) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.adjType = "instrumentation" ; procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; } else if (isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = (mTmpBroadcastQueue.contains(mFgBroadcastQueue)) ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; app.adjType = "broadcast" ; procState = ActivityManager.PROCESS_STATE_RECEIVER; } else if (app.executingServices.size() > 0 ) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = app.execServicesFg ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; app.adjType = "exec-service" ; procState = ActivityManager.PROCESS_STATE_SERVICE; } else if (app == TOP_APP) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.adjType = "top-sleeping" ; foregroundActivities = true ; procState = PROCESS_STATE_CUR_TOP; } else { schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; adj = cachedAdj; procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; app.cached = true ; app.empty = true ; app.adjType = "cch-empty" ; }
场景 2: 当客户端进程 activity 里面调用 bindService () 方法时 flags 带有 BIND_ADJUST_WITH_ACTIVITY 参数,并且该 activity 处于可见状态,则当前服务进程也属于前台进程,源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 for (int is = app.services.size()-1 ; is >= 0 ; is--) { ServiceRecord s = app.services.valueAt(is); for (int conni = s.connections.size()-1 ; conni >= 0 ; conni--) { ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni); for (int i = 0 ; i < clist.size(); i++) { ConnectionRecord cr = clist.get(i); if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0 ) { ... } final ActivityRecord a = cr.activity; if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0 ) { if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ && (a.visible || a.state == ActivityState.RESUMED || a.state == ActivityState.PAUSING)) { adj = ProcessList.FOREGROUND_APP_ADJ; if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0 ) { if ((cr.flags&Context.BIND_IMPORTANT) != 0 ) { schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND; } else { schedGroup = ProcessList.SCHED_GROUP_DEFAULT; } } app.cached = false ; app.adjType = "service" ; app.adjTypeCode = ActivityManager.RunningAppProcessInfo .REASON_SERVICE_IN_USE; app.adjSource = a; app.adjSourceProcState = procState; app.adjTarget = s.name; } } } } }
场景 3: 对于 provider 客户端进程,还有以下两个条件能成为前台进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 for (int provi = app.pubProviders.size()-1 ; provi >= 0 ; provi--) { ContentProviderRecord cpr = app.pubProviders.valueAt(provi); for (int i = cpr.connections.size()-1 ; i >= 0 ; i--) { ContentProviderConnection conn = cpr.connections.get(i); ProcessRecord client = conn.client; int clientAdj = computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now); if (adj > clientAdj) { if (app.hasShownUi && app != mHomeProcess && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { ... } else { adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ ? clientAdj : ProcessList.FOREGROUND_APP_ADJ; adjType = "provider" ; } app.cached &= client.cached; } ... } if (cpr.hasExternalProcessHandles()) { if (adj > ProcessList.FOREGROUND_APP_ADJ) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false ; app.adjType = "ext-provider" ; app.adjTarget = cpr.name; } } }
# 3. VISIBLE_APP_ADJ(100)
可见进程:当 ActivityRecord 的 visible=true,也就是 Activity 可见的进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 if (!foregroundActivities && activitiesSize > 0 ) { int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX; for (int j = 0 ; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.visible) { if (adj > ProcessList.VISIBLE_APP_ADJ) { adj = ProcessList.VISIBLE_APP_ADJ; app.adjType = "vis-activity" ; } if (procState > PROCESS_STATE_CUR_TOP) { procState = PROCESS_STATE_CUR_TOP; app.adjType = "vis-activity" ; } schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false ; app.empty = false ; foregroundActivities = true ; final TaskRecord task = r.getTask(); if (task != null && minLayer > 0 ) { final int layer = task.mLayerRank; if (layer >= 0 && minLayer > layer) { minLayer = layer; } } break ; } ... } if (adj == ProcessList.VISIBLE_APP_ADJ) { adj += minLayer; } }
从 Android P 开始,进一步细化 ADJ 级别,增加了 VISIBLE_APP_LAYER_MAX (99),是指 VISIBLE_APP_ADJ (100) 跟 PERCEPTIBLE_APP_ADJ (200) 之间有 99 个槽,则可见级别 ADJ 的取值范围为 [100,199]。 算法会根据其所在 task 的 mLayerRank 来调整其 ADJ,100 加上 mLayerRank 就等于目标 ADJ,layer 越大,则 ADJ 越小。
当 TaskRecord 顶部的 ActivityRecord 为空或者结束或者不可见时,则设置该 TaskRecord 的 mLayerRank 等于 - 1; 每个 ActivityDisplay 的 baseLayer 都是从 0 开始,从最上面的 TaskRecord 开始,第一个 ADJ=100,从上至下依次加 1,直到 199 为上限。
# 4. PERCEPTIBLE_APP_ADJ(200)
可感知进程:当该进程存在不可见的 Activity,但 Activity 正处于 PAUSING、PAUSED、STOPPING 状态,则为 PERCEPTIBLE_APP_ADJ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 if (!foregroundActivities && activitiesSize > 0 ) { int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX; for (int j = 0 ; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.visible) { ... } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "pause-activity" ; } if (procState > PROCESS_STATE_CUR_TOP) { procState = PROCESS_STATE_CUR_TOP; app.adjType = "pause-activity" ; } schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false ; app.empty = false ; foregroundActivities = true ; } else if (r.state == ActivityState.STOPPING) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "stop-activity" ; } if (!r.finishing) { if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; app.adjType = "stop-activity" ; } } app.cached = false ; app.empty = false ; foregroundActivities = true ; } } }
满足以下任一条件的进程也属于可感知进程:
foregroundServices 非空:前台服务进程,执行 startForegroundService () 方法
app.forcingToImportant 非空:执行 setProcessImportant () 方法,比如 Toast 弹出过程。
hasOverlayUi 非空:非 activity 的 UI 位于屏幕最顶层,比如显示类型 TYPE_APPLICATION_OVERLAY 的窗口
# 5. SERVICE_ADJ(500)
服务进程:没有启动过 Activity,并且 30 分钟之内活跃过的服务进程。 startRequested 为 true,则代表执行 startService () 且没有 stop 的进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 for (int is = app.services.size()-1 ; is >= 0 ; is--) { ServiceRecord s = app.services.valueAt(is); if (s.startRequested) { app.hasStartedServices = true ; if (procState > ActivityManager.PROCESS_STATE_SERVICE) { procState = ActivityManager.PROCESS_STATE_SERVICE; app.adjType = "started-services" ; } if (app.hasShownUi && app != mHomeProcess) { if (adj > ProcessList.SERVICE_ADJ) { app.adjType = "cch-started-ui-services" ; } } else { if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { if (adj > ProcessList.SERVICE_ADJ) { adj = ProcessList.SERVICE_ADJ; app.adjType = "started-services" ; app.cached = false ; } } } } for (int conni = s.connections.size()-1 ; conni >= 0 ; conni--) { ... } }
# 6. SERVICE_B_ADJ(800)
进程由 SERVICE_ADJ (500) 降低到 SERVICE_B_ADJ (800),有以下两种情况:
A 类 Service 占比过高:当 A 类 Service 个数 > Service 总数的 1/3 时,则加入到 B 类 Service。换句话说,B Service 的个数至少是 A Service 的 2 倍。
内存紧张并且 A 类 Service 占用内存较高:当系统内存紧张级别 (mLastMemoryLevel) 高于 ADJ_MEM_FACTOR_NORMAL,且该应用所占内存 lastPss 大于或等于 CACHED_APP_MAX_ADJ 级别所对应的内存阈值的 1/3(默认值阈值约等于 110MB)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 if (adj == ProcessList.SERVICE_ADJ) { if (doingAll) { app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3 ); mNewNumServiceProcs++; if (!app.serviceb) { if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) { app.serviceHighRam = true ; app.serviceb = true ; } else { mNewNumAServiceProcs++; } } else { app.serviceHighRam = false ; } } if (app.serviceb) { adj = ProcessList.SERVICE_B_ADJ; } }
# 7. CACHED_APP_MIN_ADJ(900)
缓存进程优先级从 CACHED_APP_MIN_ADJ (900) 到 CACHED_APP_MAX_ADJ (906)。
ADJ 的转换算法:
cached: 900, 901, 903, 905
empty: 900, 902, 904, 906
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 final int N = mLruProcesses.size(); int numSlots = (ProcessList.CACHED_APP_MAX_ADJ - ProcessList.CACHED_APP_MIN_ADJ + 1 ) / 2 ; int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;if (numEmptyProcs > cachedProcessLimit) { numEmptyProcs = cachedProcessLimit; } int emptyFactor = numEmptyProcs/numSlots;int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1 )/numSlots; app.curRawAdj = curEmptyAdj; ... app.curAdj = app.modifyRawOomAdj(curEmptyAdj); ... } }
# 8. schedGroup
Android 进程优先级 ADJ 的每一个 ADJ 级别往往都有多种场景,使用 adjType 完美地区分相同 ADJ 下的不同场景; 不同 ADJ 进程所对应的 schedGroup 不同,从而分配的 CPU 资源也不同,schedGroup 大体分为 TOP (T)、前台 (F)、后台 (B); ADJ 跟 AMS 中的 procState 有着紧密的联系。
adj:通过调整 oom_score_adj 来影响进程寿命 (Lowmemorykiller 杀进程策略);
schedGroup:影响进程的 CPU 资源调度与分配;
procState:从进程所包含的四大组件运行状态来评估进程状态,影响 framework 的内存控制策略。比如控制缓存进程和空进程个数上限依赖于 procState,再比如控制 APP 执行 handleLowMemory () 的触发时机等。
为了说明整体关系,以 ADJ 为中心来讲解跟 adjType,schedGroup,procState 的对应关系,下面以一幅图来诠释整个 ADJ 算法的精髓,几乎涵盖了 ADJ 算法调整的绝大多数场景。
# 9. sys.lmk.minfree_levels
ProcessList.java 会在初始化时将 oom_adj 的 minfree 水位更新给 lmkd,并由 lmkd 配置到 prop sys.lmk.minfree_levels 中。
frameworks/base/services/core/java/com/android/server/am/ProcessList.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void updateOomLevels (int displayWidth, int displayHeight, boolean write) { ... if (write) { ByteBuffer buf = ByteBuffer.allocate(4 * (2 * mOomAdj.length + 1 )); buf.putInt(LMK_TARGET); for (int i = 0 ; i < mOomAdj.length; i++) { buf.putInt((mOomMinFree[i] * 1024 )/PAGE_SIZE); buf.putInt(mOomAdj[i]); } writeLmkd(buf, null); ... } }
通过代码可知,会将 mOomMinFree 的水位数组以 page 形式传递给 lmkd。
mOomAdj 数组是代码固定的:
1 2 3 4 private final int [] mOomAdj = new int [] { FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ, PERCEPTIBLE_LOW_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_LMK_FIRST_ADJ };
目前没有配置的方式,如果需要修改等级,只能修改源码。
mOomMinFree 数组是通过算法计算
上图大致整理了整个水位的计算过程,下面详细剖析这个过程。
# 9.1 确定 scale
float scaleMem = ((float) (mTotalMemMb - 350)) / (700 - 350);
// Scale buckets from screen size.
int minSize = 480 * 800; // 384000
int maxSize = 1280 * 800; // 1024000 230400 870400 .264
float scaleDisp = ((float)(displayWidth * displayHeight) - minSize) / (maxSize - minSize);
if (false) {
Slog.i("XXXXXX", "scaleMem=" + scaleMem);
Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth
+ " dh=" + displayHeight);
}
float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
if (scale < 0) scale = 0;
else if (scale > 1) scale = 1;
主要分两部分,内存和分辨率。
确认内存是否超过 700M,如果低于 700M,将会有个 scaleMem 百分比;
确认分辨率是否超过 1280*800,如果低于,将会有个 scaleDisp 百分比;
通过 scaleMem 和 scaleDisp,取其中最大百分比,不超过 1;
按照目前设备来说,手机设备的内存基本都是超过 700M 的,所以,无论手机分辨率,scale 都为 1 的。
# 9.2 初步计算 mOomMinFree
1 2 3 4 5 6 7 8 9 10 for (int i = 0 ; i < mOomAdj.length; i++) { int low = mOomMinFreeLow[i]; int high = mOomMinFreeHigh[i]; if (is64bit) { if (i == 4 ) high = (high * 3 ) / 2 ; else if (i == 5 ) high = (high * 7 ) / 4 ; } mOomMinFree[i] = (int )(low + ((high - low) * scale)); }
ProcessList.java 中定义了两个数组:
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
};
这个是默认的水位,按照 3.1 节,scale 对于手机设备值是为 1 的,也就是说 mOomMinFree 最终取值是按照 high。而 high 对于 64 位系统对于最后两个等级会进行放大。
# 9.3 进一步计算 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;
}
}
}
系统中还提供了两个 config,用以对默认的 minFree 进行一定的缩放:
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);
对于设定 minfree_abs 的 config 值时,mOomMinFree 会按照 minfree_abs 进行比例缩放;
对于设定 minfree_adj 的 config 值时,mOomMinFree 是按照 minfree_adj 进行比例增加;
Q1 控制 oom adj 水位策略
当 lmkd 触发,应该是系统中的内存超出了我们设定的界限,需要根据内存的使用情况判断 adj 的 level,进而 kill 相关的进程,而 adj level 和进程状态都是在 AMS 中修改。我们能做的就是控制 mOomMinFree 和 oom_adj 的 6 个 level。
最低 level 的 oom_adj 对应的 minfree 是内存最小的时候,这个时候内存严重紧张,应控制 oom_adj level,使其在此时不要 kill 重要进程。可以根据情况减小 mOomMinFreeHigh [0]、mOomMinFreeHigh [1] 等低等级的内存值,使其在内存很小情况下才触发 lmkd;
最高 level 的 oom_adj 对应 minfree 是内存部分紧张,只是想 lmkd 将不重要的进程 kill 掉,所以,可以放大 mOomMinFreeHigh [4]、mOomMinFreeHigh [5] 使其在内存快要紧张的时候,尽快把不重要的进程 kill;将重要的进程尽量放 mOomAdj 的低 level 中
# 五、分析结论
通过对 LMK 源码的分析,对 Android 低内存的管理机制有了更深的了解。LMK 的主要流程是 frameworks 的 ProcessList.java 调整 adj,通过 socket 通信将事件发送给 native 的守护进程 lmkd;lmkd 再根据具体的命令来执行相应操作,其主要功能是用来更新 oom_score_adj 值以及 lowmemorykiller 驱动的 parameters(包括 minfree 和 adj)。最后讲到了 lowmemorykiller 驱动,通过注册 shrinker,借助 linux 标准的内存回收机制,根据系统可用内存以及 parameters 配置参数(adj,minfree)来选取合适的 selected_oom_score_adj,再从所有进程中选择 adj 大于该目标值并占有 rss 内存最大的进程,将其杀掉从而释放出内存。
通过查阅资料了解到:Android 使用内核中的低内存终止守护程序(LMK)驱动程序来监控系统内存压力,该驱动程序是一种依赖于硬编码值的严格机制。从内核 4.12 开始,LMK 驱动程序已从上游内核中移除,改由用户空间 lmkd 来执行内存监控和进程终止任务。Android 低内存守护程序 lmkd 进程可监控运行中的 Android 系统的内存状态,并通过终止最不必要的进程来应对内存压力大的问题,使系统以可接受的性能水平运行。
Android 采用层次化系统架构,由底层向上分为 4 个主要功能层,分别是 Linux 内核层、系统运行时库层、应用程序框架层和应用程序层。对于 Linux 内核层来说,Android 以 Linux 操作系统内核为基础,借助 Linux 内核服务实现硬件设备驱动、进程和内存管理、网络协议栈、电源管理、无线通信等核心功能。Android4.0 版本之前基于 Linux2.6 系列内核,4.0 及之后的版本使用更新的 Linux3.X 内核,并且两个开源项目有了互通。Linux3.3 内核中正式包括以下 Android 代码,可以直接引导进 Android。Linux3.4 将会增添电源管理等更多功能,以增加与 Android 的硬件兼容性,使 Android 在更多设备上得到支持。Android 对 Linux 内核进行了增强,增加了一些面向移动计算的特有功能。例如,低内存管理器 LMK、匿名共享内存 Ashmem、轻量级的进程间通信 Binder 机制等。这些内核的增强使 Android 在继承 Linux 内核安全机制的同时,进一步提升了内存管理,进程间通信等方面的安全性。
内核驱动和用户软件之间还存在一层硬件抽象层(HAL),它是对硬件设备的具体实现加以抽象。鉴于许多硬件设备厂商不希望公开其设备驱动的源代码,如果能将 Android 的应用程序框架层与 Linux 系统内核的设备驱动隔离,使应用程序框架的开发尽量独立于具体的驱动程序,则 Android 将减少对 Linux 内核的依赖。所以 HAL 是对 Linux 内核驱动程序进行的封装,将硬件抽象化,屏蔽掉底层的实现细节。HAL 规定了一套应用层对硬件层读写和配置的统一接口,本质上就是将硬件的驱动分为用户空间和内核空间两个层面;Linux 内核驱动程序运行与内核空间,硬件抽象层运行与用户空间。
在系统运行库层,通过一些 C/C++ 库来为 Android 系统提供了主要的特性支持。如 SQLite 提供了数据库的支持,OpenGL|ES 提供了 3D 绘图的支持,Webkit 库提供了浏览器内核的支持等。同样在这一层还有 Android 运行时库,它主要提供了一些核心库,能够允许开发者使用 Java 语言来编写 Android 应用。另外 Android 运行时库中还包含了 Dalvik 虚拟机,它使得每一个 Android 应用都能运行在独立的进程当中,并且拥有一个自己的 Dalvik 虚拟机实例。相较于 Java 虚拟机,Dalvik 是专门为移动设备定制的,它针对手机内存、CPU 性能有限等情况作了优化处理。
应用程序框架层提供开发 Android 应用程序所需的一系列类库,构建应用程序时可能用到的 API 等,使开发人员可以进行快速的应用程序开发,方便重用组件,也可以通过继承实现个性化的扩展。而应用层就是 Android 平台上包括各类与用户直接交互的应用程序,或由 java 语言编写的运行与后台的服务程序。例如智能手机上实现的基本功能程序,像 SMS 短信,电话拨号,日历,浏览器等。