LMK简介
Linux的内存的使用原则就是不要浪费内存,所以在程序退出时在一段时间内还停留在内存中,这也是我们下一次打开程序时发现快一些的原因。但是这样带来的坏处就是如果驻留在内存中的程序多了,容易导致OOM(out of memory)的可能。Linux中使用内存监控机制来避免OOM发生。
OOM Killer
Linux原本存在一个内存监控机制OOM Killer,一旦发现内存使用进入一个临界值就会自动按照一定的策略来清理。它的核心思想是,
按照优先级,从低到高来杀死进程,回收内存资源。
一方面要考虑杀死进程给系统带来的损坏要尽量小,另一方面要释放尽量多的内存。
具体的做法是OOM Killer会根据一些参考因素,例如进程消耗内存,运行时间,OOM权重等指标计算出一个oom_score分数,这个分数越低,进程被杀死的概率越小,被杀死的时间越晚。
LMK
在Android中存在另一个内存监控机制Low memory killer(LMK)。它实现一个不同级别的killer,根据进程的oom_adj 来杀死进程,释放内存。oom_adj的大小和进程的类型以及进程被调度的次序有关,这个值越小,程序越重要,被杀的可能性越低。其源码位于,kernel/drivers/staging/android/LowMemoryKiller.c。该文件中定义了两个数组,用来调整killer行为。
static short lowmem_adj[6] = {
0,
1,
6,
12,
};
static int lowmem_adj_size = 4;
/*static*/ int lowmem_minfree[6] = {
3 * 512, /* 6MB */
2 * 1024, /* 8MB */
4 * 1024, /* 16MB */
16 * 1024, /* 64MB */
};
上面定义的两个数组时一一对应的,其中lowmem_adj表示的是被处理某一个级别的adj的值,lowmem_minfree则表示该级别对应的内存阈值。比如说adj=0的级别,它对应的内存阈值是6M,也就是在可用内存小于6M时,会清除adj大于等于0的所有进程。所以可以看出adj越小,它被杀死的可能越小。
LMK参数
Kernel代码中设定了lowmem_adj和lowmem_minfree的默认值,可以通过设置下面的文件来修改这两组值。需要注意的是最多只能设置6个级别,并且minfree的单位是page。
/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree (以页为单位,一般是4KB大小)
例如可以在init.rc中修改其默认值,
write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15
write /proc/sys/vm/overcommit_memory 1
write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144
但是在Android运行时,AMS还会通过updateOomLevels()对LMK的参数进行调整。
LMK驱动
LMK使用了kernel中的shrinker机制,在驱动加载时,向系统注册了一个shrinker。当系统空闲内存页面不足时就会调用该函数。LMK的shrinker实现如下,
kernel\drivers\staging\android\lowmemorykiller.c
static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
{
struct task_struct *tsk;
struct task_struct *selected = NULL;
int rem = 0;
int tasksize;
int i;
short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
int minfree = 0;
int selected_tasksize = 0;
short selected_oom_score_adj;
int array_size = ARRAY_SIZE(lowmem_adj);
int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
int other_file = global_page_state(NR_FILE_PAGES) -
global_page_state(NR_SHMEM);
if (lowmem_adj_size < array_size)
array_size = lowmem_adj_size;
if (lowmem_minfree_size < array_size)
array_size = lowmem_minfree_size;
for (i = 0; i < array_size; i++) {
//依次遍历策略阀值数组,从小到大,根据当前memory free情况,取触发adj值
minfree = lowmem_minfree[i];
if (other_free < minfree && other_file < minfree) {
min_score_adj = lowmem_adj[i];
break;
}
}
//这里得到的min_score_adj 就是此时内存状态下 将会kill掉的最小score_adj
......
for_each_process(tsk) {
......
tasksize = get_mm_rss(p->mm);
......
if (selected) {
if (oom_score_adj < selected_oom_score_adj)
continue;
if (oom_score_adj == selected_oom_score_adj &&
tasksize <= selected_tasksize)
continue;
}//可以看到 遍历一圈process 只为找到一个 oom_score_adj tasksize 最大的process
selected = p;
selected_tasksize = tasksize;
selected_oom_score_adj = oom_score_adj;
}
if (selected) {
lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \
" to free %ldkB on behalf of '%s' (%d) because\n" \
" cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \
" Free memory is %ldkB above reserved\n",
selected->comm, selected->pid,
selected_oom_score_adj,
selected_tasksize * (long)(PAGE_SIZE / 1024),
current->comm, current->pid,
other_file * (long)(PAGE_SIZE / 1024),
minfree * (long)(PAGE_SIZE / 1024),
min_score_adj,
other_free * (long)(PAGE_SIZE / 1024));
trace_lowmem_kill(selected, other_file, minfree, min_score_adj, other_free);
lowmem_deathpending_timeout = jiffies + HZ;
send_sig(SIGKILL, selected, 0); //发送kill signal 去kill selected的process
set_tsk_thread_flag(selected, TIF_MEMDIE);
rem -= selected_tasksize;
}
}
lowmem_shrink()中,首先根据当前的内存状态找到一个合适的ADJ值,再根据该ADJ找到tasksize最大的进程将其杀死。在判断内存状态时需要注意一下,与minfree做比较的是free和cache,也就是说只有当free和cache都小于minfree时才满足ADJ的条件。
LMK设定
LMK的策略是通过驱动来执行的,但其策略的参数是在应用层设定。参数的默认值可以通过上文讲到的配置文件来修改,在Android中参数是由AMS设置的。AMS中定义了ADJ和minfree相关的数组资源。
frameworks/base/services/core/java/com/android/server/am/ProcessList.java
// These are the various interesting memory levels that we will give to
// the OOM killer. Note that the OOM killer only supports 6 slots, so we
// can't give it a different value for every possible kind of process.
private final int[] mOomAdj = new int[] {
FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
};
// These are the low-end OOM level limits. This is appropriate for an
// HVGA or smaller phone with less than 512MB. Values are in KB.
private final int[] mOomMinFreeLow = new int[] {
12288, 18432, 24576,
36864, 43008, 49152
};
// These are the high-end OOM level limits. This is appropriate for a
// 1280x800 or larger screen with around 1GB RAM. Values are in KB.
private final int[] mOomMinFreeHigh = new int[] {
73728, 92160, 110592,
129024, 147456, 184320
};
// The actual OOM killer memory levels we are using.
private final int[] mOomMinFree = new int[mOomAdj.length];
策略参数的更新是由updateOomLevels()完成的。最终计算出来的minfree会与mOomMinFreeLow,mOomMinFreeHigh,minfree_adj,minfree_abs等多个参数相关。
frameworks/base/services/core/java/com/android/server/am/ProcessList.java
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
……
int minfree_adj = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
int minfree_abs = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
……
for (int i=0; i
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
if (is64bit) {
// Increase the high min-free levels for cached processes for 64-bit
if (i == 4) high = (high*3)/2;
else if (i == 5) high = (high*7)/4;
}
mOomMinFree[i] = (int)(low + ((high-low)*scale));
}
if (minfree_abs >= 0) {
for (int i=0; i
mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
}
}
if (minfree_adj != 0) {
for (int i=0; i
mOomMinFree[i] += (int)((float)minfree_adj * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
if (mOomMinFree[i] < 0) {
mOomMinFree[i] = 0;
}
}
}
……
}
AMS回收机制
Android用用在各种activity生命周期切换时,会触发AMS中的回收机制。在AMS的回收过程中,还会去维护一个ADJ变量,作为LMK行为的参考依据。AMS回收机制的入口为trimApplications(),它在很多地方都有调用。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void trimApplications() {
synchronized (this) {
int i;
// First remove any unused application processes whose package
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
if (app.activities.size() == 0
&& app.curReceiver == null && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
+ app.processName + " ("
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
app.kill("empty", false);
} else {
try {
app.thread.scheduleExit();
} catch (Exception e) {
// Ignore exceptions.
}
}
cleanUpApplicationRecordLocked(app, false, true, -1);
mRemovedProcesses.remove(i);
if (app.persistent) {
addAppLocked(app.info, false, null /* ABI override */);
}
}
}
// Now update the oom adj for all processes.
updateOomAdjLocked();
}
}
mRemovedProcesses 列表中主要包含了 crash 的进程、5 秒内没有响应并被用户选在强制关闭的进程、以及应用开发这调用 killBackgroundProcess 想要杀死的进程。调用 Process.killProcess 将所有此类进程全部杀死。updateOomAdjLocked()计算更新所有process的oomadj。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void updateOomAdjLocked() {
......
// First update the OOM adjustment for each of the
// application processes based on their current state.
int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
int nextCachedAdj = curCachedAdj+1;
int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
int nextEmptyAdj = curEmptyAdj+2;
for (int i=N-1; i>=0; i--) {
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm && app.thread != null) {
app.procStateChanged = false;
computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
......
applyOomAdjLocked(app, TOP_APP, true, now);
......
}
computeOomAdjLocked()计算当前process的ADJ。ADJ值为-17~15,越小优先级越高。AMS中根据进程的类型定义了ADJ的值,他们的意义分别如下:
ADJ
Description
CACHED_APP_MAX_ADJ = 15
当前只运行了不可见的Activity组件的进程
CACHED_APP_MIN_ADJ = 9
SERVICE_B_ADJ = 8
B list of Service。和A list相比,他们对用户的黏合度要小些
PREVIOUS_APP_ADJ = 7
用户前一次交互的进程。按照用户的使用习惯,人们经常会在几个常用的进程间切换,所以这类进程得到再次运行的概率比较大
HOME_APP_ADJ = 6
Launcher进程,他对用户的重要性不言而喻
SERVICE_ADJ = 5
当前运行了application service的进程
EAVY_WEIGHT_APP_ADJ = 4
重量级应用程序进程
BACKUP_APP_ADJ = 3
用于承载backup相关操作的进程
PERCEPTIBLE_APP_ADJ = 2
这类进程用户感觉到但看不见,如后台运行的音乐播放器
VISIBLE_APP_ADJ = 1
前台可见的Activity进程,如果轻易杀死这类进程将严重影响用户体验
FOREGROUND_APP_ADJ = 0
当前正在前台运行的进程,也就是用户正在交互的那个程序
PERSISTENT_SERVICE_ADJ = -11
与系统进程或Persistent进程绑定的进程,说明该进程很
PERSISTENT_PROC_ADJ = -12
Persistent性质的进程,如telephony
SYSTEM_ADJ = -16
系统进程
这些是系统提供的adj,我们还可以改变自己进程的adj值,有以下两种方式:
写/proc/pid/oom_adj 值,在init.rc文件中就经常看到下面的语句。它设置进程的adj 值为-16,属于系统进程永远不会被杀死。
on early-init
write /proc/1/oom_adj -16
设置persistent属性。在AndroidManifest.xml文件中设置这个属性为true,即可将其adj的值设置为-12,处于这个级别的进程基本上也不会被杀死,比如电话。
继续updateOomAdjLocked()流程。通过computeOomAdjLocked()得到ADJ值后,applyOomAdjLocked()将其经过一定的修整,设置到对应的process。大体流程如下,
必须是非 persistent 进程,即非系统进程;
必须是空进程,即进程中没有任何 activity 存在。如果杀死存在 Activity 的进程,有可能关闭用户正在使用的程序,或者使应用程序恢复的时延变大,从而影响用户体验;
必须无 broadcast receiver。运行 broadcast receiver 一般都在等待一个事件的发生,用户并不希望此类程序被系统强制关闭;
进程中 service 的数量必须为 0。存在 service 的进程很有可能在为一个或者多个程序提供某种服务,如 GPS 定位服务。杀死此类进程将使其他进程无法正常服务。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
private final boolean applyOomAdjLocked(ProcessRecord app,
ProcessRecord TOP_APP, boolean doingAll, long now) {
......
if (app.curAdj != app.setAdj) {
ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(
TAG, "Set " + app.pid + " " + app.processName +
" adj " + app.curAdj + ": " + app.adjType);
app.setAdj = app.curAdj;
}
......
}
setOomAdj()通过lmkd将ADJ值写入到proc文件系统对应的节点上,其路径为"/proc//oom_score_adj"。
frameworks/base/services/core/java/com/android/server/am/ProcessList.java
/**
* Set the out-of-memory badness adjustment for a process.
*
* @param pid The process identifier to set.
* @param uid The uid of the app
* @param amt Adjustment value -- lmkd allows -16 to +15.
*
* {@hide}
*/
public static final void setOomAdj(int pid, int uid, int amt) {
if (amt == UNKNOWN_ADJ)
return;
long start = SystemClock.elapsedRealtime();
ByteBuffer buf = ByteBuffer.allocate(4 * 4);
buf.putInt(LMK_PROCPRIO);
buf.putInt(pid);
buf.putInt(uid);
buf.putInt(amt);
writeLmkd(buf);
long now = SystemClock.elapsedRealtime();
if ((now-start) > 250) {
Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid
+ " = " + amt);
}
}