Android LMK机制

前言

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

后面具体触发这里还不是很清楚,后续扩展。

因为在整理的过程中有很多深入的地方我都暂时跳过去了,或者根据个人理解大概的描述了一下,如果有什么讲解的不是很清楚或者不正确的,欢迎批评指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月夜持剑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值