android的oomkiller_Android Low Memory Killer

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

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值