关于 LowMemoryKiller

核心源码(Android_10.0)

关键类 路径
kernel_3.18 http://androidxref.com/kernel_3.18/
lmkd.h system/core/lmkd/include/lmkd.h (Android 9.0)
lmkd.h system/memory/lmkd/include/lmkd.h (Android 10.0)
lmkd.c system/core/lmkd/lmkd.c (Android 9.0)
lmkd.cpp system/memory/lmkd/lmkd.cpp (Android 10.0)
lmkd.rc system/core/lmkd/lmkd.rc
lowmemorykiller.c kernel-3.18/drivers/staging/android/lowmemorykiller.c
Process.java frameworks/base/core/java/android/os/Process.java
ProcessList.java frameworks/base/services/core/java/com/android/server/am/ProcessList.java
OomAdjuster.java frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
ActivityManagerService.java frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
ActivityTaskManagerService.java frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

【申明】本篇博文研究的基础材料基于 LooperJing 的 LowMemoryKiller机制分析 ,在学习作者干货文章的过程中,本篇文章基于 Android 10 源码进行探讨,仅作学习交流,知识储备之用。

一、LowMemoryKiller

1.1 何为 LowMemoryKiller?

Android 系统的设计理念是希望应用进程能尽量长时间地存活,以提升用户体验。应用首次打开比较慢,这个过程有进程创建以及 Application 等信息的初始化,所以应用在启动之后,即便退到后台并非立刻杀死,而是存活一段时间,这样下次再使用则会非常快(应用的热启动)

但是这样的设计会带来一个问题:如果系统放任所有进程一直存活,所有的进程都把持着自己的内存空间,那么系统使用的内存会越来越大,导致系统内存不足。此时我们就需要进程回收机制帮助我们合理的把控进程的回收。

这就涉及到我们本篇文章的内容:LowMemoryKiller

1.2 LowMemoryKiller 原理

我们知道应用进程是在 ZygoteFork 出来的,由 ActivityManagerService 进行统一管理,AMS 中会根据进程的状态更新进程对应的 oom_adj 值,这个值会传递到 kernel,kernel 中存在一个 低内存回收机制,在内存达到一定阈值后,会触发回收那些 oom_adj 值较高的进程,从而释放出更多的内存空间。

用户在启动一个进程之后,通常伴随着启动一个 Activity 或者一个 Service 播放音乐,这个时候此进程的 adj 被 AMS 提高,LMK 就不会杀死这个进程,当这个进程要做的事情做完了,退出后台了,此进程的 adj 很快又被 AMS 降低。

当需要杀死一个进程释放内存时,一般先根据当前手机剩余内存的状态,在 minfree 节点中找到当前等级,再根据这个等级去 adj 节点中找到这个等级应该杀掉的进程的优先级,之后遍历所有进程并比较进程优先级 adj 与优先级阈值,并杀死优先级低于阈值的进程,达到释放内存的目的。

1.3 minfree 和 adj

不同厂商对于不同的产品可以做不同的配置,对于 LMK 杀进程的标准主要涉及到两个文件:minfreeadj

拿手里的 pixel 为例:

1
2
3
4
5
// 每个数字代表一个内存级别,比如:18432,23040,27648,32256,55296,80640
/sys/module/lowmemorykiller/parameters/minfree

// 对应上面的一组数,每个数组代表一个进程优先级级别,比如:0, 100, 200, 300, 900, 906
/sys/module/lowmemorykiller/parameters/adj

minfree & adj

minfree 中数值的单位是内存中的页面数量,一般情况下一个页面是 4KB,当内存低于 80640 的时候,系统会杀死 adj>=906 级别的进程,当内存低于 55296 的时候,系统会杀死 adj>=900 级别的进程。

既然可以通过这两个文件去杀指定进程区间的值,那么就涉及到每个进程各自的值,它们是怎么定义的呢?

这就要涉及到另外两个文件:oom_adjoom_score_adj

1
2
3
4
5
// 代表当前进程的优先级,这个优先级是 kernel 中的优先级
/proc/pid/oom_adj

// 这个是 AMS 上层的优先级,与 ProcessList 中的优先级对应
/proc/pid/oom_score_adj

拿手里的 pixel 为例,可以看到有很多 pid 不同的进程号,不同的进程号下存在 oom_adjoom_score_adj 两个文件。

pid

oom_adj & oom_score_adj

随着应用进程的状态切换(从前台切到后台),这两个值都会变化。

1.4 LowMemoryKiller 流程

Framework 层通过调整 adj 的值和 阈值数组,输送给 kernel 中的 lmk,为 lmk 提供杀进程的原材料,因为用户空间和内核空间相互隔离,就采用了 文件节点 进行通讯,用 socket 将 adj 的值与阈值数组传给 lmkd( Android 5.0 之后不再由 AMS 直接与 lmk 通信,而是引入了 lmkd 守护进程),lmkd 将这些值写到 内核节点 中。lmk 通过读取这些节点,实现进程的 kill



接下来,我们就会围绕此图展开对源码的分析。

二、 Framework 层

从上图可以看到,Framework 层的 AMS 中主要涉及三个核心的方法:

(1)AMS.updateConfiguration

更新窗口配置,这个过程中,分别向 /sys/module/lowmemorykiller/parameters 目录下的 minfree 和 adj 节点写入相应数值。

(2)AMS.updateOomAdjLocked

应用 adj,当需要杀掉目标进程则返回 false,否则返回 true。这个过程中,调用 setOomAdj() 方法,向 /proc/pid/oom_score_adj 写入 oom_adj 后直接返回。

(3)AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked

进程死亡后,调用 remove(),直接返回。

2.1 ActivityManagerService.updateConfiguration

这个方法是用来更新 minfreeadj 的值的:

1
2
3
4
5
6
7
8
9
10
11
12
13
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {

public ActivityTaskManagerService mActivityTaskManager;

@Override
public boolean updateConfiguration(Configuration values) {
return mActivityTaskManager.updateConfiguration(values);
}

}

2.1.1 ActivityTaskManagerService.updateConfiguration

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
// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

public class ActivityTaskManagerService extends IActivityTaskManager.Stub {

@Override
public boolean updateConfiguration(Configuration values) {
mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");

synchronized (mGlobalLock) {
if (values == null && mWindowManager != null) {
// sentinel: fetch the current configuration from the window manager
values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
}

if (mWindowManager != null) {
// 调用 updateOomLevelsForDisplay 方法
final Message msg = PooledLambda.obtainMessage(
ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal,
DEFAULT_DISPLAY);
mH.sendMessage(msg);
}

final long origId = Binder.clearCallingIdentity();
try {
if (values != null) {
Settings.System.clearConfiguration(values);
}
updateConfigurationLocked(values, null, false, false /* persistent */,
UserHandle.USER_NULL, false /* deferResume */,
mTmpUpdateConfigurationResult);
return mTmpUpdateConfigurationResult.changes != 0;
} finally {
Binder.restoreCallingIdentity(origId);
}
}

}

2.1.2 ActivityManagerInternal.updateOomLevelsForDisplay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {

@VisibleForTesting
public final class LocalService extends ActivityManagerInternal {
... ...

public void updateOomLevelsForDisplay(int displayId) {
synchronized(ActivityManagerService.this) {
if (mWindowManager != null) {
// 调用applyDisplaySize方法,基于屏幕尺寸,更新 LMK 的水平线,其实就是更新 minfree 文件的几个值
mProcessList.applyDisplaySize(mWindowManager);
}
}
}

... ...
}

}

2.1.3 ProcessList.applyDisplaySize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// frameworks/base/services/core/java/com/android/server/am/ProcessList.java

/**
* Activity manager code dealing with processes.
*/
public final class ProcessList {

void applyDisplaySize(WindowManagerService wm) {
if (!mHaveDisplaySize) {
Point p = new Point();
// TODO(multi-display): Compute based on sum of all connected displays' resolutions.
wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p);
if (p.x != 0 && p.y != 0) {
// 传入屏幕的参数进行更新处理
updateOomLevels(p.x, p.y, true);
mHaveDisplaySize = true;
}
}
}

}

2.1.4 ProcessList.updateOomLevels

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
// frameworks/base/services/core/java/com/android/server/am/ProcessList.java

/**
* Activity manager code dealing with processes.
*/
public final class ProcessList {

private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
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);

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

final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;

// 将 mOomMinFreeLow 和 mOomMinFreeHigh 经过运算得出的值存入 mOomMinFree 中,
// 这个值是根据当前屏幕的分辨率和内存大小来计算的。
for (int i = 0; i < mOomAdj.length; i++) {
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
if (is64bit) {
// 对于 64-bit 机器,minfree 的最大值会增大
if (i == 4) high = (high*3)/2;
else if (i == 5) high = (high*7)/4;
}
mOomMinFree[i] = (int)(low + ((high-low)*scale));
}
... ...

if (write) {
ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
// 携带了 LMK_TARGET 协议
buf.putInt(LMK_TARGET);
for (int i = 0; i < mOomAdj.length; i++) {
// 五个水位线
buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
// 与上面水位线对应的五个 adj 数值
buf.putInt(mOomAdj[i]);
}

// 将 AMS 已经计算好的值通过 socket 发送到 lmkd
writeLmkd(buf, null);
SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
mOomLevelsSet = true;
}
// GB: 2048,3072,4096,6144,7168,8192
// HC: 8192,10240,12288,14336,16384,20480
}

}

我们发现发送给 lmkd 的 buf 携带了一个命令协议:LMK_TARGET,这个协议干嘛用?其实这个协议就是让 kernel 层去执行对应的函数:cmd_target,去更新 minfree 和 adj 两个文件。

所以,AMS 是通过调用 applyDisplaySize 方法,基于 屏幕尺寸 以及机器的 CPU 位数,更新 LMK 的 水位线 的。

2.2 AMS.updateOomAdjLocked

前文我们说过,updateOomAdjLocked 其实就是应用 adj 值,或者说更新 adj 值,其实 adj 值的更新是相当频繁的,因为随着应用窗口的切换,四大组件的生命周期会做不停的调整,相应 adj 值就会随时更新。

在讲解源码之前,我们先了解下系统中对于 adj 值的定义有那些,所有 adj 值定义存放在 ProcessList.java 中。

oom_adj 划分为16级,取值范围[-1000~1001]。

ADJ 名称 取值 说明
NATIVE_ADJ -1000 native 进程
SYSTEM_ADJ -900 系统进程,仅指 system_server 进程
PERSISTENT_PROC_ADJ -800 系统 persistent 进程,比如 telephony
PERSISTENT_SERVICE_ADJ -700 关联着系统或 persistent 进程
FOREGROUND_APP_ADJ 0 前台进程
VISIBLE_APP_ADJ 100 可见进程
PERCEPTIBLE_APP_ADJ 200 可感知进程,比如后台音乐播放
BACKUP_APP_ADJ 300 备份进程
HEAVY_WEIGHT_APP_ADJ 400 重量级进程
SERVICE_ADJ 500 服务进程
HOME_APP_ADJ 600 Home 进程
PREVIOUS_APP_ADJ 700 上一个 App 的进程(往往通过按返回键)
SERVICE_B_ADJ 800 B List中的 Service(较老的、使用可能性更小)
CACHED_APP_MIN_ADJ 900 不可见进程的 adj 最小值
CACHED_APP_MAX_ADJ 999 不可见进程的 adj 最大值
UNKNOWN_ADJ 1001 一般指将要会缓存进程,无法获取确定值

从 Android 7.0 开始,ADJ 采用 100、200、300 等数值;在这之前的版本 AD J采用数字 1、2、3 等数值,这样的调整是为了可以更进一步地细化进程的优先级,比如在 VISIBLE_APP_ADJ(100) 与 PERCEPTIBLE_APP_ADJ(200) 之间,可以设定 ADJ = 101、ADJ = 102 级别的进程。

接下来,我们开始源码分析,看下 updateOomAdjLocked 方法。

2.2.1 ActivityManagerService.updateOomAdjLocked

1
2
3
4
5
6
7
8
9
10
11
12
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {

@GuardedBy("this")
final boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll,
String oomAdjReason) {
return mOomAdjuster.updateOomAdjLocked(app, oomAdjAll, oomAdjReason);
}

}

2.2.2 OomAdjuster.updateOomAdjLocked

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
// frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

/**
* All of the code required to compute proc states and oom_adj values.
*/
public final class OomAdjuster {

boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll,
String oomAdjReason) {
final ProcessRecord TOP_APP = mService.getTopAppLocked();
final boolean wasCached = app.cached;

mAdjSeq++;

// This is the desired cached adjusment we want to tell it to use.
// If our app is currently cached, we know it, and that is it. Otherwise,
// we don't know it yet, and it needs to now be cached we will then
// need to do a complete oom adj.
final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
// 执行 updateOomAdjLocked 方法
boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false,
SystemClock.uptimeMillis());
if (oomAdjAll
&& (wasCached != app.cached || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
// Changed to/from cached state, so apps after it in the LRU
// list may also be changed.
updateOomAdjLocked(oomAdjReason);
}
return success;
}

@GuardedBy("mService")
private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
ProcessRecord TOP_APP, boolean doingAll, long now) {
if (app.thread == null) {
return false;
}

computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false);
// 执行 applyOomAdjLocked 方法
return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
}

}

2.2.3 OomAdjuster.applyOomAdjLocked

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
// frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

/**
* All of the code required to compute proc states and oom_adj values.
*/
public final class OomAdjuster {

@GuardedBy("mService")
private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
long nowElapsed) {
... ...

if (app.curAdj != app.setAdj) {
// 之前的 adj 值与计算的 adj 值不同,则更新 adj
ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) {
String msg = "Set " + app.pid + " " + app.processName + " adj "
+ app.curAdj + ": " + app.adjType;
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
}
app.setAdj = app.curAdj;
app.verifiedAdj = ProcessList.INVALID_ADJ;
}

... ...
}

}

2.2.4 ProcessList.setOomAdj

跟踪 setOomAdj 方法,又回到了 ProcessList 中处理。

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
// frameworks/base/services/core/java/com/android/server/am/ProcessList.java

/**
* Activity manager code dealing with processes.
*/
public final class ProcessList {

public static final void setOomAdj(int pid, int uid, int amt) {
// This indicates that the process is not started yet and so no need to proceed further.
if (pid <= 0) {
return;
}
if (amt == UNKNOWN_ADJ)
return;

long start = SystemClock.elapsedRealtime();
ByteBuffer buf = ByteBuffer.allocate(4 * 4);
buf.putInt(LMK_PROCPRIO);
buf.putInt(pid);
buf.putInt(uid);
buf.putInt(amt);
// 将 AMS 已经计算好的 adj 值通过 socket 发送到 lmkd
writeLmkd(buf, null);
long now = SystemClock.elapsedRealtime();
if ((now-start) > 250) {
Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid + " = " + amt);
}
}

}

我们同样发现 buf 携带了一个命令协议:LMK_PROCPRIO,对应到 Kernel 层,执行 cmd_procprio 函数,把 AMS 发送过来的 adj 值更新到相应的文件中去,供 LMK 遍历内核中进程列表,杀死特定 adj 值的进程。

2.3 AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked

进程被杀之后,则会调用该进程的 ProcessList.remove 方法。

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
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {

// cleanUpApplicationRecordLocked 方法
final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
boolean restarting, boolean allowRestart, int index, boolean replacingPid) {
if (index >= 0) {
removeLruProcessLocked(app);
ProcessList.remove(app.pid); // 调用 remove 方法
}
... ...

}

// handleAppDiedLocked 方法
final void handleAppDiedLocked(ProcessRecord app,
boolean restarting, boolean allowRestart) {
int pid = app.pid;
boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1,
false /*replacingPid*/);
if (!kept && !restarting) {
removeLruProcessLocked(app);
if (pid > 0) {
ProcessList.remove(pid); // 调用 remove 方法
}
}
... ...

}

}

// frameworks/base/services/core/java/com/android/server/am/ProcessList.java

/**
* Activity manager code dealing with processes.
*/
public final class ProcessList {

public static final void remove(int pid) {
// This indicates that the process is not started yet and so no need to proceed further.
if (pid <= 0) {
return;
}
ByteBuffer buf = ByteBuffer.allocate(4 * 2);
buf.putInt(LMK_PROCREMOVE);
buf.putInt(pid);
writeLmkd(buf, null);
}

}

buf 携带了一个命令协议:LMK_PROCREMOVE,对应到 Kernel 层,执行 cmd_procremove 函数,删除 /proc/<pid> 下面的文件。

2.4 writeLmkd

我们发现不管是哪个方法,AMS 都是通过 writeLmkdlmkd 进行通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// frameworks/base/services/core/java/com/android/server/am/ProcessList.java

/**
* Activity manager code dealing with processes.
*/
public final class ProcessList {

private static boolean writeLmkd(ByteBuffer buf, ByteBuffer repl) {
if (!sLmkdConnection.isConnected()) {
// try to connect immediately and then keep retrying
sKillHandler.sendMessage(
sKillHandler.obtainMessage(KillHandler.LMKD_RECONNECT_MSG));

// 尝试三次
// wait for connection retrying 3 times (up to 3 seconds)
if (!sLmkdConnection.waitForConnection(3 * LMKD_RECONNECT_DELAY_MS)) {
return false;
}
}

return sLmkdConnection.exchange(buf, repl);
}

}

2.5 小结

Framework 层通过调整 adj 的值和 阈值数组,输送给 kernel 中的 lmk,为 lmk 提供杀进程的原材料,因为用户空间和内核空间是相互隔离的,所以采用了 文件节点 进行通讯,用 socket 将 adj 的值与阈值数组传给 lmkd,lmkd 将这些值写到 内核节点 中。lmk 通过读取这些节点,实现进程的 kill


三、lmkd 层

我们之前说过,lowMemoryKiller 分为 三层结构,lmkd 作为一个中间层,从 Framework 层获取材料传给 lmk 处理,那么这节我们聊聊 lmkd 的具体原理。

以我手里这台 pixel 为例,看下 lmkd 进程:

1
2
3
4
walleye:/ # ps -ef | grep lmkd                                                                                                                                                    
root 834 1 0 07:35:41 ? 00:00:00 lmkd
root 3383 3369 3 07:58:47 pts/0 00:00:00 grep lmkd
walleye:/ #

进程号为 834,我们到对应的进程目录下看一下:

lmkd

我们发现 lmkd 的父进程号为 1?这不是 init 进程么?所以 lmkd 的 父进程 正是我们的 init 进程

3.1 lmkd 数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// system/memory/lmkd/lmkd.cpp

// 这两个文件不用多说了,我们上面介绍过
#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree"
#define INKERNEL_ADJ_PATH "/sys/module/lowmemorykiller/parameters/adj"

/* OOM score values used by both kernel and framework */
#define OOM_SCORE_ADJ_MIN (-1000)
#define OOM_SCORE_ADJ_MAX 1000

// minfree 和 adj 文件中的值实质是来自 lowmem_adj 和 lowmem_minfree 两个数组,
// lowmem_minfree[] 和 lowmem_adj[] 数组大小个数都为 6
// 在 /system/memory/lmkd/include/lmkd.h 中定义:#define MAX_TARGETS 6
static int lowmem_adj[MAX_TARGETS];
static int lowmem_minfree[MAX_TARGETS];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// system/memory/lmkd/include/lmkd.h

/*
* Supported LMKD commands
*/
enum lmk_cmd {
LMK_TARGET = 0, /* Associate minfree with oom_adj_score */
LMK_PROCPRIO, /* Register a process and set its oom_adj_score */
LMK_PROCREMOVE, /* Unregister a process */
LMK_PROCPURGE, /* Purge all registered processes */
LMK_GETKILLCNT, /* Get number of kills */
LMK_SUBSCRIBE, /* Subscribe for asynchronous events */
LMK_PROCKILL, /* Unsolicited msg to subscribed clients on proc kills */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// system/memory/lmkd/lmkd.cpp

// procadjslot_list 是一个双向的链表,数组的下标 index 就是进程的优先级
// 系统中同一个时刻,有很多进程的优先级都是相同的,那么根据指定的优先级就能从数组中获取一个链表,
// 这个链表上的所有 proc 的优先级都是相同的,根据这个链表进一步选择杀掉哪些进程。
// 由于进程的优先级可能是一个负数,所以加上了一个-OOM_SCORE_ADJ_MIN(1000)。
#define ADJTOSLOT(adj) ((adj) + -OOM_SCORE_ADJ_MIN)
#define ADJTOSLOT_COUNT (ADJTOSLOT(OOM_SCORE_ADJ_MAX) + 1)
static struct adjslot_list procadjslot_list[ADJTOSLOT_COUNT];

struct adjslot_list {
struct adjslot_list *next;
struct adjslot_list *prev;
};

struct proc {
struct adjslot_list asl;
int pid;
int pidfd;
uid_t uid;
int oomadj;
pid_t reg_pid; /* PID of the process that registered this record */
struct proc *pidhash_next;
};

在 lmkd 中进程的数据结构是 procadjslot_list 是双向链表,而 AMS 中进程的数据结构是 ProcessRocord

3.2 lmkd.main

了解完了 lmkd 的 数据结构 后,我们再来看看 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
// system/memory/lmkd/lmkd.cpp

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

if (!init()) {
if (!use_inkernel_interface) {
if (mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) {
ALOGW("mlockall failed %s", strerror(errno));
}

// 指定进程的调度策略, FIFO 方式的实时调度策略
if (sched_setscheduler(0, SCHED_FIFO, &param)) {
ALOGW("set SCHED_FIFO failed %s", strerror(errno));
}
}

mainloop(); // 进入主循环,等待 AMS 发送的请求
}
statslog_destroy();

android_log_destroy(&ctx);

ALOGI("exiting");
return 0;
}

3.3 lmkd.init

我们先分析 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
// system/memory/lmkd/lmkd.cpp

static int init(void) {
... ...

// 拿到 socket 的 fd
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_connect_handler 中主要完成 soclet 的 accpet 以及数据 read 工作,
// 当监听到 socket 连接事件后会调用 ctrl_connect_handler 方法,下面会重点分析
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++;

has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
use_inkernel_interface = has_inkernel_module;

// use_inkernel_interface 代表文件是否可读,默认为 1
if (use_inkernel_interface) {
ALOGI("Using in-kernel low memory killer interface");
... ...

} else {
... ...

}
// 初始化链表
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;
}

3.4 lmkd.mainloop

接下来在跟踪 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
26
27
28
// system/memory/lmkd/lmkd.cpp

static void mainloop(void) {
struct event_handler_info* handler_info;
struct polling_params poll_params;
struct timespec curr_tm;
struct epoll_event *evt;
long delay = -1;

poll_params.poll_handler = NULL;
poll_params.update = POLLING_DO_NOT_CHANGE;

while (1) {
struct epoll_event events[maxevents];
int nevents;
int i;

if (poll_params.poll_handler) {
... ...

} else {
// 调用 epoll_wait 阻塞,等待 socket 事件的到来
nevents = epoll_wait(epollfd, events, maxevents, -1);
}
... ...

}
}

3.5 ctrl_connect_handler

前面我们在 init 函数中提到了 ctrl_connect_handler 中主要完成 soclet 的 accpet 以及数据 read 工作,当监听到 socket 连接事件后会调用 ctrl_connect_handler 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// system/memory/lmkd/lmkd.cpp

static void ctrl_connect_handler(int data __unused, uint32_t events __unused,
struct polling_params *poll_params __unused) {
struct epoll_event epev;
int free_dscock_idx = get_free_dsock();
... ...

/* use data to store data connection idx */
data_sock[free_dscock_idx].handler_info.data = free_dscock_idx;
data_sock[free_dscock_idx].handler_info.handler = ctrl_data_handler; // 执行 ctrl_data_handler 函数
data_sock[free_dscock_idx].async_event_mask = 0;
epev.events = EPOLLIN;
... ...

maxevents++;
}

3.6 ctrl_data_handler

1
2
3
4
5
6
7
8
// system/memory/lmkd/lmkd.cpp

static void ctrl_data_handler(int data, uint32_t events,
struct polling_params *poll_params __unused) {
if (events & EPOLLIN) {
ctrl_command_handler(data);
}
}

3.7 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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// system/memory/lmkd/lmkd.cpp

static void ctrl_command_handler(int dsock_idx) {
LMKD_CTRL_PACKET packet;
struct ucred cred;
int len;
enum lmk_cmd cmd;
int nargs;
int targets;
int kill_cnt;

// 读取 socket 管道信息
len = ctrl_data_read(dsock_idx, (char *)packet, CTRL_PACKET_MAX_SIZE, &cred);
if (len <= 0)
return;

if (len < (int)sizeof(int)) {
ALOGE("Wrong control socket read length len=%d", len);
return;
}

// 获取 buffer 中的命令协议
cmd = lmkd_pack_get_cmd(packet);
nargs = len / sizeof(int) - 1;
if (nargs < 0)
goto wronglen;

switch(cmd) {
case LMK_TARGET: // 处理 LMK_TARGET 事件
targets = nargs / 2;
if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj))
goto wronglen;
cmd_target(targets, packet); // 调用 cmd_target 函数
break;
case LMK_PROCPRIO: // 处理 LMK_PROCPRIO 事件
if (nargs < 3 || nargs > 4)
goto wronglen;
cmd_procprio(packet, nargs, &cred); // 调用 cmd_procprio 函数
break;
case LMK_PROCREMOVE: // 处理 LMK_PROCREMOVE 事件
if (nargs != 1)
goto wronglen;
cmd_procremove(packet, &cred); // 调用 cmd_procremove 函数
break;
... ...

case LMK_PROCKILL:
/* This command code is NOT expected at all */
ALOGE("Received unexpected command code %d", cmd);
break;
default:
ALOGE("Received unknown command code %d", cmd);
return;
}

return;

wronglen:
ALOGE("Wrong control socket read length cmd=%d len=%d", cmd, len);
}

一套流程下来,在 init 中注册了 ctrl_connect_handler 的回调函数,然后通过 ctrl_connect_handler -> ctrl_data_handler -> ctrl_command_handler 函数的调用,完成了对上层 command 命令的处理工作。

接下来,我们针对每个命令协议执行的方法进行讨论。

3.7.1 LMK_TARGET – 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// system/memory/lmkd/lmkd.cpp

static void cmd_target(int ntargets, LMKD_CTRL_PACKET packet) {
int i;
struct lmk_target target;
... ...

if (ntargets > (int)ARRAY_SIZE(lowmem_adj))
return;
... ...

// 读取参数
for (i = 0; i < ntargets; i++) {
lmkd_pack_get_target(packet, i, &target);
lowmem_minfree[i] = target.minfree;
lowmem_adj[i] = target.oom_adj_score;

pstr += snprintf(pstr, pend - pstr, "%d:%d,", target.minfree,
target.oom_adj_score);
if (pstr >= pend) {
/* if no more space in the buffer then terminate the loop */
pstr = pend;
break;
}
}

lowmem_targets_size = ntargets;
... ...

// 是否使用 kernel 空间的处理逻辑,默认为 1
if (has_inkernel_module) {
char minfreestr[128];
char killpriostr[128];

minfreestr[0] = '\0';
killpriostr[0] = '\0';

for (i = 0; i < lowmem_targets_size; i++) {
char val[40];

if (i) {
strlcat(minfreestr, ",", sizeof(minfreestr));
strlcat(killpriostr, ",", sizeof(killpriostr));
}

snprintf(val, sizeof(val), "%d", use_inkernel_interface ? lowmem_minfree[i] : 0);
strlcat(minfreestr, val, sizeof(minfreestr));
snprintf(val, sizeof(val), "%d", use_inkernel_interface ? lowmem_adj[i] : 0);
strlcat(killpriostr, val, sizeof(killpriostr));
}

// 将生成好的 string 写入到文件节点 minfree 以及 adj
writefilestring(INKERNEL_MINFREE_PATH, minfreestr);
writefilestring(INKERNEL_ADJ_PATH, killpriostr);
}
}

3.7.2 LMK_PROCPRIO – 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
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
// system/memory/lmkd/lmkd.cpp

static void cmd_procprio(LMKD_CTRL_PACKET packet, int field_count, struct ucred *cred) {
struct proc *procp;
char path[LINE_MAX];
char val[20];
int soft_limit_mult;
struct lmk_procprio params;
bool is_system_server;
struct passwd *pwdrec;
int tgid;

lmkd_pack_get_procprio(packet, field_count, &params);

if (params.oomadj < OOM_SCORE_ADJ_MIN ||
params.oomadj > OOM_SCORE_ADJ_MAX) {
ALOGE("Invalid PROCPRIO oomadj argument %d", params.oomadj);
return;
}
... ...

// 更新 oom_score_adj 的值
snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);
snprintf(val, sizeof(val), "%d", params.oomadj);

// 写到文件中
if (!writefilestring(path, val, false)) {
ALOGW("Failed to open %s; errno=%d: process %d might have been killed",
path, errno, params.pid);
/* If this file does not exist the process is dead. */
return;
}

if (use_inkernel_interface) {
stats_store_taskname(params.pid, proc_get_name(params.pid, path, sizeof(path)));
return;
}
... ...

// 从 hashtable 找到对应的进程
procp = pid_lookup(params.pid);
if (!procp) {
int pidfd = -1;

if (pidfd_supported) {
pidfd = TEMP_FAILURE_RETRY(sys_pidfd_open(params.pid, 0));
if (pidfd < 0) {
ALOGE("pidfd_open for pid %d failed; errno=%d", params.pid, errno);
return;
}
}

// 如果没有找到,分配一个结点,调用 proc_insert 插入 hashtable 中
procp = static_cast<struct proc*>(calloc(1, sizeof(struct proc)));
if (!procp) {
// Oh, the irony. May need to rebuild our state.
return;
}

procp->pid = params.pid;
procp->pidfd = pidfd;
procp->uid = params.uid;
procp->reg_pid = cred->pid;
procp->oomadj = params.oomadj;
proc_insert(procp);
} else {
... ...

// 如果已经存在,将原来优先级的 proc 移除,然后将新的优先级的 proc 添加到双向链表中
proc_unslot(procp);
procp->oomadj = params.oomadj;
proc_slot(procp);
}
}

3.7.3 LMK_PROCREMOVE – 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
26
27
28
29
30
31
32
33
34
35
36
37
// system/memory/lmkd/lmkd.cpp

static void cmd_procremove(LMKD_CTRL_PACKET packet, struct ucred *cred) {
struct lmk_procremove params;
struct proc *procp;

lmkd_pack_get_procremove(packet, &params);

if (use_inkernel_interface) {
/*
* Perform an extra check before the pid is removed, after which it
* will be impossible for poll_kernel to get the taskname. poll_kernel()
* is potentially a long-running blocking function; however this method
* handles AMS requests but does not block AMS.
*/
poll_kernel(kpoll_fd);

stats_remove_taskname(params.pid);
return;
}

procp = pid_lookup(params.pid);
if (!procp) {
return;
}

if (!claim_record(procp, cred->pid)) {
char buf[LINE_MAX];
/* Only registrant of the record can remove it */
ALOGE("%s (%d, %d) attempts to unregister a process registered by another client",
proc_get_name(cred->pid, buf, sizeof(buf)), cred->uid, cred->pid);
return;
}

// 执行 pid_remove, 更新 hashtable 和双向链表
pid_remove(params.pid);
}

3.8 lmkd 杀进程

不知道你有没有发现,三种命令都对 use_inkernel_interface 进行了判断,为什么?

因为如果 use_inkernel_interface 等于1,那么就执行 kernel 空间的逻辑,lmkd 中数据结构也不用更新,也不用 lmkd 中杀进程的逻辑,全部都交给 lmk 完成

如果不等于1,那么 lmkd 就需要自己维护进程的这些数据结构了。如果由 lmkd 杀进程,执行的方法为:find_and_kill_process

3.8.1 find_and_kill_process

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
// system/memory/lmkd/lmkd.cpp

/*
* Find one process to kill at or above the given oom_adj level.
* Returns size of the killed process.
*/
static int find_and_kill_process(int min_score_adj, int kill_reason, const char *kill_desc,
union meminfo *mi, struct timespec *tm) {
int i;
int killed_size = 0;
bool lmk_state_change_start = false;

for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) {
struct proc *procp;

while (true) {
... ...

// 执行 kill_one_process 方法
killed_size = kill_one_process(procp, min_score_adj, kill_reason, kill_desc, mi, tm);
... ...

}
}
... ...

return killed_size;
}

3.8.2 kill_one_process

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
// system/memory/lmkd/lmkd.cpp

/* Kill one process specified by procp. Returns the size of the process killed */
static int kill_one_process(struct proc* procp, int min_oom_score, int kill_reason,
const char *kill_desc, union meminfo *mi, struct timespec *tm) {
... ...

/* CAP_KILL required */
if (pidfd < 0) {
start_wait_for_proc_kill(pid);
r = kill(pid, SIGKILL);
} else {
start_wait_for_proc_kill(pidfd);
r = sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
}
... ...

out:
/*
* WARNING: After pid_remove() procp is freed and can't be used!
* Therefore placed at the end of the function.
*/
pid_remove(pid);
return result;
}

3.9 有趣的问题

lmkd 会把自己给 kill 掉么?

我们在本节开头知道了 lmkd 的进程号为:834,那我们来看下它的 adj 值。

我们发现它的 oom_score_adj 值为 -1000,这是一个级别最高的进程,是绝对不会被 kill 的进程。

ADJ 名称 取值 说明
NATIVE_ADJ -1000 native 进程

四、Kernel 层

lmk driver 位于 kernel-x.xx/drivers/staging/android/lowmemorykiller.c

lmk 中是通过 Linux 的 shrinker 机制 实现的,它是 linux 内存回收机制的一种,由内核线程 kswapd 负责监控,在 lowmemorykiller 初始化的时候注册 register_shrinker

4.1 shrinker 注册与返注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c

static struct shrinker lowmem_shrinker = {
.scan_objects = lowmem_scan,
.count_objects = lowmem_count,
.seeks = DEFAULT_SEEKS * 16
};

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

static void __exit lowmem_exit(void)
{
unregister_shrinker(&lowmem_shrinker); // 返注册
}

shrinker 注册完成之后,就回调 lowmem_scan 函数。

4.2 lowmem_scan

当触发 lmkd,则先杀 oom_score_adj 最大的进程,当 oom_adj 相等时,则选择 rss 最大的进程。

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
129
130
131
132
133
134
135
136
137
// http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c

static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
{
//tsk进程结构体对象
struct task_struct *tsk;
// selected 用来保存需要被杀的那个进程
struct task_struct *selected = NULL;
unsigned long rem = 0;
int tasksize;
int i;
// OOM_SCORE_ADJ_MAX = 1000
short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
int minfree = 0;
// 被杀进程的内存占用大小
int selected_tasksize = 0;
// 被杀进程的 oom_score_adj 值
short selected_oom_score_adj;
int array_size = ARRAY_SIZE(lowmem_adj);
// global_page_state 可以获取当前系统可用的(剩余)内存大小
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;
// 遍历 lowmem_minfree 数组找出相应的最小 adj 值,
// 目的就是根据剩余内存的大小,确定当前剩余内存的级别的 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);

// 系统的空闲内存数,根据上面的逻辑判断出,low memory killer 需要对 adj 高于多少(min_adj)
// 的进程进行分析是否释放,发现 min_score_adj 值为 OOM_SCORE_ADJ_MAX + 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 同步机制
rcu_read_lock();
// 遍历所有进程
for_each_process(tsk) {
struct task_struct *p;
short oom_score_adj;

// 内核线程 kthread
if (tsk->flags & PF_KTHREAD)
continue;

p = find_lock_task_mm(tsk);
if (!p)
continue;

if (test_tsk_thread_flag(p, TIF_MEMDIE) &&
time_before_eq(jiffies, lowmem_deathpending_timeout)) {
task_unlock(p);
rcu_read_unlock();
return 0;
}
oom_score_adj = p->signal->oom_score_adj;
// 如果当前找到的进程的 oom_score_adj 比当前需要杀的最小优先级还低,则不杀
if (oom_score_adj < min_score_adj) {
task_unlock(p);
continue;
}
// 获取进程的占用内存大小(rss值),也就是进程独占内存 + 共享库大小
tasksize = get_mm_rss(p->mm);
task_unlock(p);
if (tasksize <= 0)
continue;
// 第一次循环,selected 一定为 null
if (selected) {
// 如果这个进程的 oom_score_adj 小于我们已经选中的那个进程的 oom_score_adj,
// 则继续寻找下一个进程。
if (oom_score_adj < selected_oom_score_adj)
continue;
// 或者这个进程的 oom_score_adj 等于我们已经选中的那个进程的 oom_score_adj,
// 但其所占用的内存大小 tasksize 小于等于我们已经选中的那个进程所占用内存大小,
// 则继续寻找下一个进程。
if (oom_score_adj == selected_oom_score_adj &&
tasksize <= selected_tasksize)
continue;
}
// 已经找到了需要寻找的进程,更新它的 tasksize 与 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);
}
// selected 非 null,说明已经找到了
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
lowmem_deathpending_timeout = jiffies + HZ;
// 设置进程的标记是 TIF_MEMDIE
set_tsk_thread_flag(selected, TIF_MEMDIE);
// 发送 SIGKILL 信号,杀死这个进程
send_sig(SIGKILL, selected, 0);
// 更新一下 rem 值,杀死了一个进程所释放的内存加上去
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 源码就是 lmk 机制的核心实现。