Android--LMK机制分析

Android--LMK机制分析

Android——LMK 机制源代码研究分析

# 一、目标

​ 本篇文章主要分析 Andoird 核心机制与服务中的 ——LMK 机制,低内存管理机制(根据需要杀死进程来释放需要的内存)。源码分析主要通过 http://androidxref.com/,Android 版本为 Marshmallow-Android 6.0.1_r10, 内核版本为 3.18,机制架构如下:

image-20220514110126445

源码位置如下:

源代码名称 路径位置
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 默认阈值图:

img

# (二)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 的工作原理。

img

用户在启动一个进程之后,通常伴随着启动一个 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 更新并保存。时序图如下:

img

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;

//curAdj是computeOomAdjLocked计算出的adj值,赋值给setAdj,
//并且调用ProcessList.setOomAdj方法
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;
}

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

//调用了进程ApplicationThread的setProcessState方法
//调用Process.setProcessGroup(int pid,int group)设置进程调度策略,native层代码原理就是利用linux的cgroup机制,将进程根据状态放入预先设定的cgroup分组中,这些分组包含了对cpu使用率、cpuset、cpu调频等子资源的配置,已满足特定状态进程对系统资源的需求。
if (app.repProcState != app.curProcState) {
app.repProcState = app.curProcState;
changes |= ProcessChangeItem.CHANGE_PROCESS_STATE;
if (app.thread != null) {
try {
if (false) {
//RuntimeException h = new RuntimeException("here");
Slog.i(TAG, "Sending new process state " + app.repProcState
+ " to " + app /*, h*/);
}
app.thread.setProcessState(app.repProcState);
} catch (RemoteException e) {
}
}
}
...

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; //MAX_EMPTY_TIME是系统控制空进程能够保存的最大时间
final int N = mLruProcesses.size(); //lru集合中进程的数量
...

mAdjSeq++; //记录执行该方法的次数
mNewNumServiceProcs = 0;
mNewNumAServiceProcs = 0;

//系统默认CUR_MAX_EMPTY_PROCESSES=16,CUR_MAX_CACHED_PROCESSES=32
final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit;

//将adj在900~906之间的进程分为numSlots部分
//900~906只有7个数字可用,但是adj位于该范围的进程数量往往远远不只
int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
- ProcessList.CACHED_APP_MIN_ADJ + 1) / 2;//=3
//N = mNumNonCachedProcs + mNumCachedHiddenProcs + numEmptyProcs
//lru集合的大小 = 非缓存进程+缓存进程+空进程
int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
if (numEmptyProcs > cachedProcessLimit) {
numEmptyProcs = cachedProcessLimit;//保证空进程的数量在阈值内
}
mEmptyRemainingCapacity = emptyProcessLimit - numEmptyProcs;//空进程剩余的容量
int emptyFactor = numEmptyProcs/numSlots;//空进程的计算因子
if (emptyFactor < 1) emptyFactor = 1;//保证最小为1
//缓存进程的计算因子
int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots;
if (cachedFactor < 1) cachedFactor = 1;//保证最小为1
int stepCached = 0;//代表每一个slot的深度,下同
int stepEmpty = 0;
int numCached = 0;//缓存进程的数量
int numEmpty = 0;//空进程的数量
int numTrimming = 0;//重要性低于home的后台进程数量
//以上的一些计算因子,都是动态变化的,会随着对应的进程数量变化,决定着每一个slot中进程的step

mNumNonCachedProcs = 0;//重设全局变量非缓存进程大小为0
mNumCachedHiddenProcs = 0;//重设全局变量缓存进程大小为0

int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;//在计算开始时,缓存进程的adj开始为900
int nextCachedAdj = curCachedAdj+1;//下一个为901
int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;//在计算开始时,空进程的adj开始900
int nextEmptyAdj = curEmptyAdj+2;//下一个为902
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;
//计算app进程的adj
computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
//当执行完computeOomAdjLocked之后,对于缓存进程和空进程的app,如果发现还未进行adj设置,需要修改成正确的adj值
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++;
//从这部分逻辑可以看出stepCached应该是表示一个深度
if (stepCached >= cachedFactor) {//cachedFactor用来表示每一个slot的最大深度
stepCached = 0;//在一个slot内,他们的adj值是一样的
curCachedAdj = nextCachedAdj;//下一个slot的adj值
nextCachedAdj += 2;
if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
//保证缓存进程和空进程的adj在900~906之间
nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
}
}
}
break;
default://处理空进程
app.curRawAdj = curEmptyAdj;
app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
...

// Cycle strategy:循环策略
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
//计算完毕之后,进行设置app的oom
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++;//重要性低于Home的进程
}
}
}

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);
//memFactor取值在0-3之间,越大代表内存越紧张
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();
//设置内存等级成功返回true
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;
//默认设置最高的回收等级
//这个循环为逆序遍历。因为LRU集合中越在后面的进程,优先级越高,代表用户使用的频率高
//该进程在LRU集合的位置就越靠后,也就意味着其占有的内存也较多,因此他就越需要进行内存回收。
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) {//procstate >= 13
//进程优先级大于Home进程,也可以认为adj>=600,也就是相对于用户重要程度低于Home进程
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) {//回收一定程度(即处理完一个slot之后),按需要降低内存回收等级
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 进程来说:

  • USS:SytemUI 进程实际占用的物理内存

  • PSS:SystemUI 进程实际占用的物理内存加上 SystemUI 的共享库占用的内存

  • RSS:SystemUI 进程实际占用的物理内存加上所有共享库占用的内存

  • VSS:通常不关注

一般来说内存占用大小有如下规律: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) {
//updateOomAdjLocked函数每次更新oom_adj时,都会分配一个序号
//此处就是根据序号判断是否已经处理过命令
if (mAdjSeq == app.adjSeq) {
// This adjustment has already been computed.
return app.curRawAdj;
}
//ProcessRecord对应的ActivityThread不存在了
//修改其中的一些变量,此时的oom_adj为CACHED_APP_MAX_ADJ,
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();
// ProcessRecord中只有初始化时为maxAdj赋值
//maxAdj取值为UNKNOWN_ADJ,即最大的1001
if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
//这部分代码就是修改app的curSchedGroup,并将oom_adj设置为maxAdj
...
}
//保存当前TOP Activity的状态
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;
//若进程包含正在前台显示的Activity
if (app == TOP_APP) {
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
//单独的一种schedGroup
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
app.adjType = "top-activity";
//当前处理的是包含前台Activity的进程时,才会将该值置为true
foregroundActivities = true;
procState = PROCESS_STATE_CUR_TOP;
} else if (app.instrumentationClass != null) {
//处理正在进行测试的进程
// Don't want to kill running instrumentation.
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;
//根据处理广播的Queue,决定调度策略
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) {
//处理Service正在运行的进程
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = app.execServicesFg ?
ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
procState = ActivityManager.PROCESS_STATE_SERVICE;
} else {
//其它进程,在后续过程中再进一步处理
// 先将adj临时赋值为cachedAdj,即参数传入的UNKNOW_ADJ
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) {
//rankTaskLayersIfNeeded函数会更新包含Activity的Task的rankLayer
//按照显示层次从上到下,rankLayer逐渐增加,对应的最大值就是VISIBLE_APP_LAYER_MAX
int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX;
//依次轮询进程中的Activity
for (int j = 0; j < activitiesSize; j++) {
final ActivityRecord r = app.activities.get(j);
...
//如果进程包含可见Activity,即该进程是个可见进程
if (r.visible) {
if (adj > ProcessList.VISIBLE_APP_ADJ) {
//之前提到的正在处理广播、服务或测试的进程,adj为FOREGROUND,是小于VISIBLE_APP_ADJ
//因此不会在此更新
adj = ProcessList.VISIBLE_APP_ADJ;
app.adjType = "visible";
}
if (procState > PROCESS_STATE_CUR_TOP) {
//与oom_adj类似,在条件满足时,更新procState
procState = PROCESS_STATE_CUR_TOP;
}
//正在处理广播、服务或测试的进程,如果它们的调度策略为BACKGROUND
//但又包含了可见Activity时,调度策略变更为DEFAULT
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) {
//更新ranklayer
minLayer = layer;
}
}
//发现可见Activity时,直接可以结束循环
break;
} else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) {
//如果进程包含处于PAUSING或PAUSED状态的Activity时
//将其oom_adj调整为“用户可察觉”的的等级,这个等级还是很高的
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;
//注意并不会break
} else if (r.state == ActivityState.STOPPING) {
//包含处于Stopping状态Activity的进程,其oom_adj也被置为PERCEPTIBLE_APP_ADJ
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
app.adjType = "stopping";
}
...
// 这种进程将被看作潜在的cached或empty进程
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 {
//只是含有cached-activity的进程,仅调整procState
if (procState > ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
app.adjType = "cch-act";
}
}
if (adj == ProcessList.VISIBLE_APP_ADJ) {
//不同可见进程的oom_adj有一定的差异,处在下层的oom_adj越大
//即存在时间越长的Activity所在进程,重要性越低
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) {
//进程包含前台服务或被强制在前台运行时
//oom_adj被调整为PERCEPTIBLE_APP_ADJ,只是procState略有不同
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;
}
}
//AMS的HeavyWeight进程单独处理
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;
}
}
//home进程特殊处理
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;
//处理正在进行backup工作的进程
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。上述代码的逻辑图如下所示:

image-20220514170953732

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
...
//依次处理进程中的每一个Service
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);
//Service被已Unbounded Service的方式启动过
if (s.startRequested) {
app.hasStartedServices = true;
//调整procState
if (procState > ActivityManager.PROCESS_STATE_SERVICE) {
procState = ActivityManager.PROCESS_STATE_SERVICE;
}
if (app.hasShownUi && app != mHomeProcess) {
// 仅有含有服务且显示过UI的进程,由于其占用内存可能较多,因此需要尽早回收
// 故此处不调整其oom_adj
if (adj > ProcessList.SERVICE_ADJ) {
app.adjType = "cch-started-ui-services";
}
} else {
if (now < (s.lastActivity + ActiveServices.MAX_SERVICE_INACTIVITY)) {
//MAX_SERVICE_INACTIVITY为activity启动service后,系统最多保留Service的时间
//此时进程的oom_adj就可以被调整为后台服务对应的SERVICE_ADJ
//adj大于500的进程均会受此判断的影响
if (adj > ProcessList.SERVICE_ADJ) {
adj = ProcessList.SERVICE_ADJ;
app.adjType = "started-services";
app.cached = false;
}
}
//处理Service存在超时的情况,可见超时时也不会调整oom_adj
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
...
//如果该Service还被客户端Bounded,即是Bounded Service时
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);
//客户端可以通过一个Connection以不同的参数绑定Service
//因此,一个Service可以对应多个Connection,一个Connection又对应多个ConnectionRecord
//这里依次处理每一个ConnectionRecord
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) {
// Binding to ourself is not interesting.
continue;
}
//当BIND_WAIVE_PRIORITY为1时,客户端就不会影响服务端
//if中的流程就可以略去;否则,客户端就会影响服务端
if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) {
ProcessRecord client = cr.binding.client;
//计算出客户端进程的oom_adj
//由此可看出Android oom_adj的计算多么麻烦
//要是客户端进程中,又有个服务进程被绑定,那么将再计算其客户端进程的oom_adj
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;
//BIND_ALLOW_OOM_MANAGEMENT置为1时,先按照通常的处理方式,调整服务端进程的adjType
if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
//与前面分析Unbounded Service基本一致,若进程显示过UI或Service超时
//会将clientAdj修改为当前进程的adj,即不需要考虑客户端进程了
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;
}
}
}
//根据情况,按照clientAdj调整当前进程的adj
if (adj > clientAdj) {
if (app.hasShownUi && app != mHomeProcess
&& clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
adjType = "cch-bound-ui-services";
} else {
//以下的流程表明,client和flag将同时影响Service进程的adj
if ((cr.flags&(Context.BIND_ABOVE_CLIENT
|Context.BIND_IMPORTANT)) != 0) {
//从这里再次可以看出,Service重要性小于等于Client
adj = clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ
? clientAdj : ProcessList.PERSISTENT_SERVICE_ADJ;
//BIND_NOT_VISIBLE表示不将服务端当作visible进程看待
//于是,即使客户端的adj小于PERCEPTIBLE_APP_ADJ,service也只能取到PERCEPTIBLE_APP_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) {
//进一步更具client调整当前进程的procState、schedGroup等
...
} else {
...
}
...
if (procState > clientProcState) {
procState = clientProcState;
}
//其它参数的赋值
...
}
if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
app.treatLikeActivity = true;
}
//取出ConnectionRecord所在的Activity
final ActivityRecord a = cr.activity;
//BIND_ADJUST_WITH_ACTIVITY值为1时,表示服务端可以根据客户端Activity的oom_adj作出相应的调整
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)) {
//BIND_ADJUST_WITH_ACTIVITY置为1,且绑定的activity可见或在前台时,
//Service进程的oom_adj可以变为FOREGROUND_APP_ADJ
adj = ProcessList.FOREGROUND_APP_ADJ;
//BIND_NOT_FOREGROUND为0时,才准许调整Service进程的调度优先级
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
...
//依次处理进程中的ContentProvider
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);
//依次处理ContentProvider的客户端
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) {
// Being our own client is not interesting.
continue;
}
//计算客户端的oom_adj
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;
}
//与Unbounded Service的处理基本类似
if (adj > clientAdj) {
if (app.hasShownUi && app != mHomeProcess
&& clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
app.adjType = "cch-ui-provider";
} else {
//根据clientAdj,调整当前进程的adj
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;
}
//进一步调整调度策略和procState
...
//特殊情况的处理
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;
}
}
}
}
//如果进程之前运行过ContentProvider,同时ContentProvider的存活时间没有超时
//那么进程的adj可以变为PREVIOUS_APP_ADJ
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
//根据进程信息,进一步调整procState
...
//对Service进程做一些特殊处理
if (adj == ProcessList.SERVICE_ADJ) {
if (doingAll) {
//每次updateOomAdj时,将mNewNumAServiceProcs置为0
//然后LRU list中,从后往前数,前1/3的service进程就是AService
//其余的就是bService
//mNumServiceProcs为上一次update时,service进程的数量
app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
//记录这一次update后,service进程的数量
//update完毕后,该值将赋给mNumServiceProcs
mNewNumServiceProcs++;
...
if (!app.serviceb) {
// 如果不是bService,但内存回收等级过高,也被视为bService
if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
&& app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) {
app.serviceHighRam = true;
app.serviceb = true;
...
} else {
//LRU中后1/3的Service,都是AService
mNewNumAServiceProcs++;
...
}
} else {
app.serviceHighRam = false;
}
}
//将bService的oom_adj调整为SERVICE_B_ADJ
if (app.serviceb) {
adj = ProcessList.SERVICE_B_ADJ;
}
}
//计算完毕
app.curRawAdj = adj;
...
//if基本没有用,maxAdj已经是最大的UNKNOW_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) {
// 计算memory的scale数值
float scaleMem = ((float)(mTotalMemMb-350))/(700-350);
// 计算显示屏的scale值
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);
}
//比较memory和显示屏scale值的大小,取较大值
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);
}
//判断是否为64位系统
final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
//根据mOomMinFreeLow、mOomMinFreeHigh和scale值填充mOomMinFree数组
for (int i=0; i<mOomAdj.length; i++) {
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
//如果是64位系统,第四和第五级的数值会稍大一点
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);
...
//如果需要写入则调用writelmkd将buf写入到lowmemoryKiller中
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));
}
// GB: 2048,3072,4096,6144,7168,8192
// HC: 8192,10240,12288,14336,16384,20480
}

# 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) {
//尝试打开lmkd socket端口
for (int i = 0; i < 3; i++) {
if (sLmkdSocket == null) {
if (openLmkdSocket() == false) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
}
continue;
}
}
try {
//将数据写到socket端口中
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 {
//创建本地socket句柄,尝试连接socket
sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
//连接本地名为lmkd的本地socket
sLmkdSocket.connect(
new LocalSocketAddress("lmkd",
LocalSocketAddress.Namespace.RESERVED));
//获取lmkd socket的输出流,用于向对端写入数据
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, &param);
if (!init()) //init 处理所有核心的初始化工作
mainloop();

ALOGI("exiting");
return 0;
}

//新版供对比
int main(int argc, char **argv) {
...
update_props(); //step 1,进程最初,需要获取所有的lmkd 的prop,为init 做准备

ctx = create_android_logger(KILLINFO_LOG_TAG);

if (!init()) { //step 2,init 处理所有核心的初始化工作
if (!use_inkernel_interface) { //step 3,如果不再使用旧的LMK 驱动程序
...
//step4, 给虚拟空间上锁,防止内存交换
if (mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) {
ALOGW("mlockall failed %s", strerror(errno));
}

//step 4,添加调度策略,即先进先出
struct sched_param param = {
.sched_priority = 1,
};
if (sched_setscheduler(0, SCHED_FIFO, &param)) {
ALOGW("set SCHED_FIFO failed %s", strerror(errno));
}
}

mainloop(); //step 5, 进入循环,等待polling
}

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) {
//epoll事件
struct epoll_event epev;
int i;
int ret;
page_k = sysconf(_SC_PAGESIZE);
if (page_k == -1)
page_k = PAGE_SIZE;
page_k /= 1024;
//创建epoll文件句柄
epollfd = epoll_create(MAX_EPOLL_EVENTS);
if (epollfd == -1) {
ALOGE("epoll_create failed (errno=%d)", errno);
return -1;
}
//获取lmkd socket的控制权
ctrl_lfd = android_get_control_socket("lmkd");
if (ctrl_lfd < 0) {
ALOGE("get lmkd control socket failed");
return -1;
}
//监听lmkd socket
ret = listen(ctrl_lfd, 1);
if (ret < 0) {
ALOGE("lmkd control socket listen failed (errno=%d)", errno);
return -1;
}
//设置epoll事件的触发方式
epev.events = EPOLLIN;
//设置epoll事件的处理函数
epev.data.ptr = (void *)ctrl_connect_handler;
//在epollfd中添加对lmkd socket文件句柄的监听
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++;
//判断INKERNEL_MINFREE_PATH是否有写权限,INKERNEL_MINFREE_PATH定义如下:
//#define INKERNEL_MINFRE_PATH
//“/sys/module/lowmemorykiller/parameters/minfree”
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
/*
* 1 ctrl listen socket, 3 ctrl data socket, 3 memory pressure levels,
* 1 lmk events + 1 fd to wait for process death
*/
#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() {
/* Try to use psi monitor first if kernel has it */
use_psi_monitors = property_get_bool("ro.lmk.use_psi", true) &&
init_psi_monitors();
/* Fall back to vmpressure */
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;

/* Do not register a handler if threshold_ms is not set */
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
/* let the others know it does support reporting kills */
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));

/*
* Read zoneinfo as the biggest file we read to create and size the initial
* read buffer and avoid memory re-allocations during memory pressure
*/
if (reread_file(&file_data) == NULL) {
ALOGE("Failed to read %s: %s", file_data.filename, strerror(errno));
}

/* check if kernel supports pidfd_open syscall */
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_psimp_event_common 处理,而本质都是通过节点 /proc/pressure/memory 获取内存压力是否达到 some/full 指定来确认是否触发 event;
  • 后期 epoll 触发主要的处理函数是 mp_event_psimp_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;
//在init方法中,我们已经经lmkd socket的listen事件添加到epollfd中
//并等待epollfd事件除法
nevents = epoll_wait(epollfd, events, maxevents, -1);
if (nevents == -1) {
if (errno == EINTR)
continue;
ALOGE("epoll_wait failed (errno=%d)", errno);
continue;
}
//处理listen事件到来
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) {
//这里创建了另一个epoll事件
struct sockaddr addr;
socklen_t alen;
struct epoll_event epev;
if (ctrl_dfd >= 0) {
ctrl_data_close();
ctrl_dfd_reopened = 1;
}
//接受lmkd socket的连接请求
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++;
//设置empoll监听事件
epev.events = EPOLLIN;
//设置epoll处理函数
epev.data.ptr = (void *)ctrl_data_handler;
//将accept后的socket套接字添加到epollfd中进行监听
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) {
//处理epoll事件,即文件句柄的读事件
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) {
//读取数据buffer
int ibuf[CTRL_PACKET_MAX / sizeof(int)];
int len;
int cmd = -1;
int nargs;
int targets;
//读取数据到ibuf中
len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX);
if (len <= 0)
return;
//获取数据长度
nargs = len / sizeof(int) - 1;
if (nargs < 0)
goto wronglen;
//解析处数据中的comand字段
cmd = ntohl(ibuf[0]);
//根据不同长度的command字段,调用不同的数据处理方法
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, /* Associate minfree with oom_adj_score */
LMK_PROCPRIO, /* Register a process and set its oom_adj_score */
LMK_PROCREMOVE, /* Unregister a process */

};

其中 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;
//将framework传过来的参数保存到lowmem_minfree和lowmem_adj数组中
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];
//根据lowmwm_minfree和lowmwm_adj中的数值,构造出数值字符串
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));
}
//将上面循环中构造出来的字符串数值写入到/sys/moudle/lowmwmoryykiller/parameters/adj
writefilestring(INKERNEL_MINFREE_PATH, minfreestr);
//将上面循环中构造出来的另一字符串数值写入到/sys/modlue/lowmemorykiller/parameters/adj
writefilestring(INKERNEL_ADJ_PATH, killpriostr);
}
}

cmd_target () 函数的目的就是将 framework 传过来的数值记录到数组中,即将 minfreestrkillpriostr 然后将这两个数组拼接成字符串输入,然后写入到内核对应的位置。

# 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];
//判断oomadj的值是否在规定值内
if (oomadj < OOM_DISABLE || oomadj > OOM_ADJUST_MAX) {
ALOGE("Invalid PROCPRIO oomadj argument %d", oomadj);
return;
}
//根据进程的pid输出对应的路径
snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid);
snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj));
//将oomadj的值写入到对应进程的oom_score_adj中
writefilestring(path, val);
//使用内核接口就直接返回
//这里use_inkernel_interface在初始化的时候设置为1,所以这里直接return
if (use_inkernel_interface)
return;
//判断是否有记录到对应的proc结构
procp = pid_lookup(pid);
if (!procp) {
//如果没有对应的proc结构,表明该进程是新创建的,需要新分配一个结构proc结构记录该进程
procp = malloc(sizeof(struct proc));
if (!procp) {
// Oh, the irony. May need to rebuild our state.
return;
}
procp->pid = pid;
procp->uid = uid;
procp->oomadj = oomadj;
proc_insert(procp);
} else {
//如果之前已经有proc记录,那么就更新对应的数值
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;
}
//如果不使用内核接口的话,就需要更新链表的信息,并删除proc结构占用的内存
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_countlast_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 miunion zoneinfo zi 表示系统当前的内存使用情况。 meminfo_parsezoneinfo_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_freeother_file 后,我们根据 lowmem_minfree 的值来确定 min_score_adjmin_score_adj 表示可以回收的最低的 oomadj 值(oomadj 越大,优先级越低,越容易被杀死),oomadj 小于 min_score_adj 的进程在这次回收过程中不会被杀死。

回想一下前面的 cmd_targetlowmem_minfreelowmem_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_datamemsw_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_upgradeupgrade_pressuredowngrade_pressure 的值是我们在 main 函数里根据系统属性设置的。

在内存压力比较大并且 enable_pressure_upgrade 打开的情况下,我们把内存压力向上提升一个等级(以期释放更多的内存);在内存压力小于 downgrade_pressure 的时候,内存是充足的,没有必要通过杀死应用来回收内存;如果内存压力中等(upgrade_pressure < mem_pressure < downgrade_pressure)但是 level 却是 critical,就给他降一级。

lmkd 在给 mp level 升级的时候需要打开 enable_pressure_upgrade(默认关闭),而降级却总是可行的,说明 lmkd 尽力在不杀死应用的情况下满足系统的内存需求。

到目前为止,我们得到了三组跟内存压力相关的参数:

  1. use_minfree_levels 的情况下, min_score_adjpages_to_free
  2. 内存压力 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_adjpages_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_freemin_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, /* 6MB */
2 * 1024, /* 8MB */
4 * 1024, /* 16MB */
16 * 1024, /* 64MB */
};

第一个数组 lowmem_adj 最多有 6 个元素(默认只定义了 4 个),它表示可用内存容量处于 “某层级” 时需要被处理的 adj 值;第二个数组则是对 “层级” 的描述。举个例子,lowmwm_minfree 的第一个元素是 3512,即 3512*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;
//获取当前内存阈值对应的adj值
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);
//如果当前min_score_adj为最大adj值加1,表明还剩余足够的内存,不需要进行内存释放
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();
//遍历所有的进程task
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;
//如果进程已经不包含任何memory则跳过
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比当前内存阈值的adj还小,表明当前进程不应该被杀,跳过
oom_score_adj = p->signal->oom_score_adj;
if (oom_score_adj < min_score_adj) {
task_unlock(p);
continue;
}
//如果当前进程的oom_score_adj比当前内存阈值的adj值大,进入进程的被杀候选
//首先获取当前进程的占用内存大小
tasksize = get_mm_rss(p->mm);
task_unlock(p);
if (tasksize <= 0)
continue;
//如果已经有进程被选为被杀进程
if (selected) {
//如果当前进程的oom_score_adj值比之前挑选的进程的oom_score_adj值小
//代表当前进程重要程度比选中要杀的进程高,则跳过
if (oom_score_adj < selected_oom_score_adj)
continue;
//如果oom_score_adj值一样大,但是进程占用内存比选中要杀的进程小也跳过
if (oom_score_adj == selected_oom_score_adj &&
tasksize <= selected_tasksize)
continue;
}
//以上判断都不符合,表明当前进程的oom_score_adj值比挑选要杀的进程的oom_score_adj大
//更新被挑选要杀的进程为当前遍历的进程,并记录相关信息后进入下一轮循环
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);
//发送SIGKILL信号到被选中进程
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. 整体流程

image-20220514164809062

# 四、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) { //startIsolatedProcess
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";
}
}
  • 执行 bindBackupAgent () 过程,设置 mBackupTarget 值;

  • 执行 clearPendingBackup () 或 unbindBackupAgent () 过程,置空 mBackupTarget 值;

# 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 客户端进程,还有以下两个条件能成为前台进程:

  • 当 Provider 的客户端进程 ADJ<=FOREGROUND_APP_ADJ 时,则 Provider 进程 ADJ 等于 FOREGROUND_APP_ADJ

  • 当 Provider 有外部 (非框架) 进程依赖,也就是调用了 getContentProviderExternal () 方法,则 ADJ 至少等于 FOREGROUND_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
for (int provi = app.pubProviders.size()-1; provi >= 0; provi--) {
ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
//根据client来调整provider进程的adj和procState
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;
}
...
}
//根据provider外部依赖情况来调整adj和schedGroup
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 为上限。

image-20220509220115658

# 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--) {
... //根据client情况来调整adj
}
}

# 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

image-20220509221346179

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final int N = mLruProcesses.size();
//numSlots等于3
int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
- ProcessList.CACHED_APP_MIN_ADJ + 1) / 2;
//mNumNonCachedProcs是指empty和cached之外的进程, mNumCachedHiddenProcs代表的是cached进程个数
int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
if (numEmptyProcs > cachedProcessLimit) {
numEmptyProcs = cachedProcessLimit;
}
//emptyFactor和cachedFactor分别代表每个slot里面包括的进程个数,大于或等于1
int emptyFactor = numEmptyProcs/numSlots;
int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots;
app.curRawAdj = curEmptyAdj;
...
//ADJ阈值
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 算法调整的绝大多数场景。

img

# 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) {
// Increase the high min-free levels for cached processes for 64-bit
if (i == 4) high = (high * 3) / 2;
else if (i == 5) high = (high * 7) / 4;
​ }
​ mOomMinFree[i] = (int)(low + ((high - low) * scale));
​ }

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 短信,电话拨号,日历,浏览器等。

Author

y1seco

Posted on

2022-05-14

Updated on

2022-05-15

Licensed under

Comments

:D 一言句子获取中...