前言
基于andorid4.4.阅读和解释源代码
关于LMK 网上已经有很多介绍了,很多地方我就不重复了, 本文主要介绍个人对LMK以前对自己的理解和一些疑惑
1 LMK是什么? 2 adj 是什么? 3 curRawAdj,setRawAdj,curAdj,setAdj关系是什么? 4 如何Killer进程的? 5 什么时后会killer?
初识LMK
最初学android 一段时间后,为了写一个好的应用程序,我总是提前在网上搜索,看看有什么新东西,以避免与世界脱轨。我经常在阅读大量信息时看到它, android 进程 可见,服务, 后台,空闲。在就是activity 在后台,很容易被系统杀死。特别是应用service,动不动就被系统杀死了。 一些防service死亡方案百发齐放, 有提高service优先级,前台设置服务,多个app互相监控,拉起服务。这些都是app应用层的手段。最终,有可能被系统杀死。 什么东西像无情的杀手一样重复,一遍又一遍地杀死我的应用?当时因为知识有限或者android 当时对这方面的解释太少,找不到关键点,很少看源代码。很长一段时间,我感到沮丧。随着时间的推移,我心中的问题也被埋葬了。
甘于平凡,死于冷漠,灵魂不屈,遇火重生
慢慢接触andorid随着时间的推移,我对它的内部运行机制感到好奇。我还会在业余时间查看工作中的源代码。虽然看起来很难,但结合一些在线博客解释,我可以慢慢理解一些设计意图和工作流程。
再次遇到这个无情的杀手,这次一定会揭开它神秘的面纱
它就是LMK,全称 Low Memory Killer ,理解字面意思,低内存杀手。 (真是杀手(─??─)) 回到我的第一个问题
LMK是什么?
我们可以比喻android 系统是朝廷, 当朝廷需要银两战争或救灾时,将评估当前朝廷剩余银两的等级; oom_score_adj 可以理解官员的等级, 数字越大,代表等级越低,杀戮的负担越小。数字约小的代表等级约高。如果随意杀戮,可能会引起朝廷动荡,因此考虑是否值得杀戮需要平衡利弊。 当朝廷户部在白银1024两 在4096年之间,所有八品和八品以下的官员都会被列出来,选择一个品级最小、腐败最多的抄家。 当朝廷户部在白银1024下,大家都会包括在内,当然不包括皇帝本人(核心线程),最后选择最小腐败最多的抄家。
LMK杀戮过程
lowmemorykiller,内核文件中的注释直接引用在这里
/* drivers/misc/lowmemorykiller.c * * The lowmemorykiller driver lets user-space specify a set of memory thresholds * where processes with a range of oom_score_adj values will get killed. Specify * the minimum oom_score_adj values in * /sys/module/lowmemorykiller/parameters/adj and the number of free pages in * /sys/module/lowmemorykiller/parameters/minfree. Both files take a comma * separated list of numbers in ascending order. * * For example, write "0,8" to /sys/module/lowmemorykiller/parameters/adj and * "1024,4096" to /sys/module/lowmemorykiller/parameters/minfree to kill * processes with a oom_score_adj value of 8 or higher when the free memory * drops below 4096 pages and kill processes with a oom_score_adj value of 0 or * higher when the free memory drops below 1024 pages. * * The driver considers memory used for caches to be free, but if a large * percentage of the cached memory is locked this can be very inaccurate * and processes may not get killed until the normal oom killer is triggered. * * Copyright (C) 2007-2008 Google, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */
大概的意思 如果把“0,8" 写入 /sys/module/lowmemorykiller/parameters/adj ,“1024,4096” 写入 /sys/module/lowmemorykiller/parameters/minfree, 含义如下: 当系统内存小于1024时,从adj 0或0以上的过程可能会被杀死,其中选择adj最大的并持有内存最多的进程将其杀死。 当系统内存小于4096时,大于1024 ,则从adj 8或8以上的过程可能会被杀死,其中选择adj持有内存最多的最大过程将被杀死。
最终写入的数据组合将覆盖以下数组lowmem_adj 和lowmem_minfree
使用linux kernel简单的框架可以在网上查看 module_param_named 或者module_param_array_named 的相关说明
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 */
};
static int lowmem_minfree_size = 4;
在查看具体kill流程,在函数lowmem_shrink里
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;
//最低评分调整,默认按照最大的+1 等于 1001
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);
/*reduce CMA pages from free pages when allocate non-movable memory.*/
if (allocflags_to_migratetype(sc->gfp_mask) != MIGRATE_MOVABLE)
other_free = other_free - global_page_state(NR_FREE_CMA_PAGES);
if (lowmem_adj_size < array_size)
array_size = lowmem_adj_size;
if (lowmem_minfree_size < array_size)
array_size = lowmem_minfree_size;
for (i = 0; i < array_size; i++) {
minfree = lowmem_minfree[i];
/*low down minfree to half when allocate non-movable memory.*/
if (allocflags_to_migratetype(sc->gfp_mask) != MIGRATE_MOVABLE)
minfree = minfree / 2;
//当minfree小于系统内存则跳出循环,从而得到当前minfree min_score_adj的值
if (other_free < minfree && other_file < minfree) {
//由于lowmem_minfree lowmem_adj 数组是一一对应的,可以直接拿i获取对应的min_score_adj
min_score_adj = lowmem_adj[i];
break;
}
}
if (sc->nr_to_scan > 0)
lowmem_print(3, "lowmem_shrink %lu, %x, ofree %d %d, ma %hd\n",
sc->nr_to_scan, sc->gfp_mask, other_free,
other_file, min_score_adj);
rem = global_page_state(NR_ACTIVE_ANON) +
global_page_state(NR_ACTIVE_FILE) +
global_page_state(NR_INACTIVE_ANON) +
global_page_state(NR_INACTIVE_FILE);
//当nr_to_scan小于等于0 或者 min_score_adj是默认值1001则直接return 此方法。
//nr_to_scan这个在源码中 kernel_imx/mm/vmscan.c 代表要在列表中浏览的页面数。具体不是很了解,后续扩展细节。
if (sc->nr_to_scan <= 0 || min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
lowmem_print(5, "lowmem_shrink %lu, %x, return %d\n",
sc->nr_to_scan, sc->gfp_mask, rem);
return rem;
}
//选中的进程的评分调整, 第一次使用最低评分调整来初始化。
selected_oom_score_adj = min_score_adj;
// 内核一种同步机制 -- RCU同步机制 |不是很了解,后续扩展细节
rcu_read_lock();
//一个宏定义,用来遍历所有进程。
for_each_process(tsk) {
//进程结构体指针
struct task_struct *p;
//评分调整
short oom_score_adj;
//如果是内核线程则跳过
if (tsk->flags & PF_KTHREAD)
continue;
//判断进程是否存在有效的内存
p = find_lock_task_mm(tsk);
if (!p)
continue;
//不是很了解
if (test_tsk_thread_flag(p, TIF_MEMDIE) &&
time_before_eq(jiffies, lowmem_deathpending_timeout)) {
task_unlock(p);
rcu_read_unlock();
return 0;
}
//评分调整等于p的评分调整
oom_score_adj = p->signal->oom_score_adj;
//如果当前进程的评分小于最低评分则跳过,进行下一进程遍历
if (oom_score_adj < min_score_adj) {
task_unlock(p);
continue;
}
//获取进程的内存大小
tasksize = get_mm_rss(p->mm);
task_unlock(p);
//如果进程的内存大小小于等于0则跳过进行下一个进程遍历
if (tasksize <= 0)
continue;
//如果又选中过则与选中的进程比较
if (selected) {
//比较评分,如果当前小于选中的则跳过
if (oom_score_adj < selected_oom_score_adj)
continue;
//不过评分想等,则在比较内存大小,当前内存小于选中的则跳过
if (oom_score_adj == selected_oom_score_adj &&
tasksize <= selected_tasksize)
continue;
}
//赋值选中进程
selected = p;
//赋值选中的内存大小
selected_tasksize = tasksize;
//赋值选中的评分调整
selected_oom_score_adj = oom_score_adj;
lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n",
p->comm, p->pid, oom_score_adj, tasksize);
}
//遍历结束后如果欧选中的进程则开始杀死进程
if (selected) {
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));
lowmem_deathpending_timeout = jiffies + HZ;
send_sig(SIGKILL, selected, 0);
set_tsk_thread_flag(selected, TIF_MEMDIE);
rem -= selected_tasksize;
}
lowmem_print(4, "lowmem_shrink %lu, %x, return %d\n",
sc->nr_to_scan, sc->gfp_mask, rem);
rcu_read_unlock();
return rem;
}
这个函数主体步骤 1 通过lowmem_adj 和lowmem_minfree 和系统当前内存的状态获取最低评分标准 2 遍历基于最低评分标准大的进程找出评分最大且内存占用最多的进程。 3 找到进程后将其杀死。
adj计算与写入
这两个文件 /sys/module/lowmemorykiller/parameters/adj 作为优先级等级组 /sys/module/lowmemorykiller/parameters/minfree 作为对应的内存组 在AMS里这个类ProcessList.java 构造时写入的规则。 然后在AMS中通过applyOomAdjLocked这个方法来更新 进程的adj值。和这个方法相关的还有updateOomAdjLocked,computeOomAdjLocked。 大概的顺序是updateOomAdjLocked -> computeOomAdjLocked -> applyOomAdjLocked 个人理解触发这些方法的都是一些四大组件的生命周期变化,或者还有其他场景这里就不具体展开了。 主要了解 adj如何写入的。 在AMS内中的applyOomAdjLocked方法,会通过Process 调用setOomAdj来设置adj值。
private final boolean applyOomAdjLocked(ProcessRecord app, boolean wasKeeping,
ProcessRecord TOP_APP, boolean doingAll, boolean reportingProcessState, long now) {
//省略...
if (app.curAdj != app.setAdj) {
if (Process.setOomAdj(app.pid, 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;
} else {
success = false;
Slog.w(TAG, "Failed setting oom adj of " + app + " to " + app.curAdj);
}
}
我们可以往下再看frameworks/base/core/jni/android_util_Process.cpp
static const JNINativeMethod methods[] = {
{
"setOomAdj", "(II)Z", (void*)android_os_Process_setOomAdj},
在这个方法中直接把adj写入/proc/pid/oom_adj中。
jboolean android_os_Process_setOomAdj(JNIEnv* env, jobject clazz,
jint pid, jint adj)
{
#ifdef HAVE_OOM_ADJ
char text[64];
sprintf(text, "/proc/%d/oom_adj", pid);
int fd = open(text, O_WRONLY);
if (fd >= 0) {
sprintf(text, "%d", adj);
write(fd, text, strlen(text));
close(fd);
}
return true;
#endif
return false;
}
oom_adj 这个值会同步触发同目录的oom_score_adj 会有相印的规则转换 具体可以参考源码kernel_imx/fs/proc/base.c 这个是文件操作的结构体, 我们这里看write部分
static const struct file_operations proc_oom_adj_operations = {
.read = oom_adj_read,
.write = oom_adj_write,
.llseek = generic_file_llseek,
};
写入oom_adj
static ssize_t oom_adj_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
//省略...
//如果oom_adj是最大值15,则把oom_score_adj的最大值1000赋值给oom_adj
if (oom_adj == OOM_ADJUST_MAX)
oom_adj = OOM_SCORE_ADJ_MAX;
else //否则oom_adj * 1000 / -(-17)
oom_adj = (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;
//省略...
//最后把oom_adj 赋值给oom_score_adj
task->signal->oom_score_adj = oom_adj;
trace_oom_score_adj_update(task);
err_sighand:
unlock_task_sighand(task, &flags);
err_task_lock:
task_unlock(task);
put_task_struct(task);
out:
return err < 0 ? err : count;
}
oom_adj 和oom_score_adj相关常量定义kernel_imx/include/uapi/linux/oom.h
/* * /proc/<pid>/oom_score_adj set to OOM_SCORE_ADJ_MIN disables oom killing for * pid. */
#define OOM_SCORE_ADJ_MIN (-1000)
#define OOM_SCORE_ADJ_MAX 1000
/* * /proc/<pid>/oom_adj set to -17 protects from the oom killer for legacy * purposes. */
#define OOM_DISABLE (-17)
/* inclusive */
#define OOM_ADJUST_MIN (-16)
#define OOM_ADJUST_MAX 15
从代码看首先 oom_adj 区间范围是-17 ~ 15;oom_score_adj区间范围是-1000 ~ 1000 转换公式:
if (oom_adj == OOM_ADJUST_MAX)
oom_adj = OOM_SCORE_ADJ_MAX;
else
oom_adj = (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;
oom_adj基本是按照比例缩放到oom_score_adj的区间范围的,在lowmemorykiller里都是使用oom_score_adj这个参数来计算评分的,oom_adj 是一个旧的接口参赛,不过为了兼用,还是可以使用的,最终都会转换成oom_score_adj。
这里其实还有一个变量 oom_score,这个是干什么用的呢? 至少LMK没有使用过。 后续在要介绍Linux oom killer
curRawAdj,setRawAdj,curAdj,setAdj是什么关系?
frameworks/base/services/java/com/android/server/am/ProcessRecord.java
int curRawAdj; // Current OOM unlimited adjustment for this process
int setRawAdj; // Last set OOM unlimited adjustment for this process
int curAdj; // Current OOM adjustment for this process
int setAdj; // Last set OOM adjustment for this process
光看注释还不是很理解这四个变量是什么概念。 结合AMS的代码,在applyOomAdjLocked方法内
private final boolean applyOomAdjLocked(ProcessRecord app, boolean wasKeeping,
ProcessRecord TOP_APP, boolean doingAll, boolean reportingProcessState, long now) {
boolean success = true;
if (app.curRawAdj != app.setRawAdj) {
//省略...
app.setRawAdj = app.curRawAdj;
}
setRawAdj 最终通过curRawAdj赋值的。想到与最后一次记录rawadj
if (app.curAdj != app.setAdj) {
if (Process.setOomAdj(app.pid, 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;
} else {
success = false;
Slog.w(TAG, "Failed setting oom adj of " + app + " to " + app.curAdj);
}
}
curAdj 最终通过setAdj赋值的。想到与最后一次记录adj。 那么curRawAdj 和curAdj 是什么关系呢? 基本通过ProcessRecord 的方法modifyRawOomAdj 将curRawAdj转换一下赋值给curAdj
curAdj = app.modifyRawOomAdj(curRawAdj); modifyRawOomAdj方法位置: frameworks/base/services/java/com/android/server/am/ProcessRecord.java
int modifyRawOomAdj(int adj) {
if (hasAboveClient) {
// If this process has bound to any services with BIND_ABOVE_CLIENT,
// then we need to drop its adjustment to be lower than the service's
// in order to honor the request. We want to drop it by one adjustment
// level... but there is special meaning applied to various levels so
// we will skip some of them.
if (adj < ProcessList.FOREGROUND_APP_ADJ) {
// System process will not get dropped, ever
} else if (adj < ProcessList.VISIBLE_APP_ADJ) {
adj = ProcessList.VISIBLE_APP_ADJ;
} else if (adj < ProcessList.PERCEPTIBLE_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
} else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
adj = ProcessList.CACHED_APP_MIN_ADJ;
} else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
adj++;
}
}
return adj;
}
主要看hasAboveClient这个变量
boolean hasAboveClient; // Bound using BIND_ABOVE_CLIENT, so want to be lower
默认是false,那么相当于curRawAdj和curAdj是保持一致的,只有带BIND_ABOVE_CLIENT标签的服务连接才会变成true,从modifyRawOomAdj这个方法的值当hasAboveClient等于true的时后是期望adj数值变大, 从上面的LMK杀进程流程上看,adj数字越大代表越容易被杀死。
什么时后会killer?
这里先看看lowmem_shrink函数的注册
static struct shrinker lowmem_shrinker = {
.shrink = lowmem_shrink,
.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);
}
主要是register_shrinker这个方法注册,他在哪里是注册的呢? 请看kernel_imx/mm/vmscan.c文件,
/* * Add a shrinker callback to be called from the vm */
void register_shrinker(struct shrinker *shrinker)
{
atomic_long_set(&shrinker->nr_in_batch, 0);
down_write(&shrinker_rwsem);
list_add_tail(&shrinker->list, &shrinker_list);
up_write(&shrinker_rwsem);
}
EXPORT_SYMBOL(register_shrinker);
/* * Remove one */
void unregister_shrinker(struct shrinker *shrinker)
{
down_write(&shrinker_rwsem);
list_del(&shrinker->list);
up_write(&shrinker_rwsem);
}
EXPORT_SYMBOL(unregister_shrinker);
后面具体触发这里还不是很清楚,后续扩展。
因为在整理的过程中有很多深入的地方我都暂时跳过去了,或者根据个人理解大概的描述了一下,如果有什么讲解的不是很清楚或者不正确的,欢迎批评指正。