巧妇能为少米之炊(2)——幽灵侩子手(LMK)

使用安卓的朋友可能会遇到过这样的问题,就是某个应用正在使用,突然它被关闭了,或者打开某个应用,然后它就退出了,其实这背后就是LMK(Low Memory Killer)在起作用,所有形象地称之为幽灵刽子手。

1.版本平台

2.概念

3.核心结构及调用

4.总结

版本平台

平台:高通MSM8974

安卓版本:4.4

Linux内核版本:3.4.4

文件路径: android\kernel\drivers\staging\android\Lowmemorykiller.c

概念:

前文已经介绍过,LMK是定时扫描系统中的内存,当系统内存少于某个阀门值的时候,就会选择性的去释放一些内存(杀死进程),这就是你看到当前的程序突然退出的原因,可能有人会问,它系统怎么不做的人性化点,检测到我在使用,不要杀我使用的程序啊,去杀别的啊,这个我们后面慢慢道来~~

核心结构及调用

阀门值

LMK杀死内存就是根据阀门值来选择去杀掉哪些进程,我们先看看与阀门值有关的两个数据结构:

static int 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;

这两个数据结构是仅仅相互关联的,那么他们是怎么相互协作的工作的呢?举个列子:

当前系统空闲内存是63M时,比lowmem_minfree[3]小且大于lowmem_minfree[2],那么当前要杀掉进程的标准的值(每个进程都有一个adj值)就是lowmem_adj[3],也就是说找到所有进程的adj值大于lowmem_adj[3]的进程,然后从他们之中找到adj值最大的杀掉,如果有两个进程的adj一样时,会选择内存占用最多杀掉!数组其他部分以此类推

Android将进程分为6个等级,foreground(前台进程)、visible(可见进程)、secondary server(次要服务)、hidden(后台进程)、content provider(内容供应节点)、empty(空进程),优先级依次降低,所以可以看到最容易被杀死的就是空进程,最不容易被杀死的就是前台进程,所以当你正在使用着的应用就被无辜杀死的时候,说明此时系统的阀门值已经触底,你是不是开了很多应用?

lowmem_shrink():

LMK示意内核模块的形式注册进内核,在初始化的时候,会初始化注册一个shrink()(关于Linux的shrink的知识这里不赘述),当空闲内存触及到阀门值的时候,就会调用到lowmem_shrink()这个函数,这个函数也就是选择并且杀死进程最核心的操作地方,我们细细来看它的过程和代码:

	   p = find_lock_task_mm(tsk);   /*有mm的thread才能操作*/
		if (!p)
		  continue;
		/*得到task对应的oom_score_adj*/
		oom_score_adj = p->signal->oom_score_adj;
		/*比当前的基本标准小就忽略。*/
		if (oom_score_adj < min_score_adj) {
			task_unlock(p);
			continue;
		}

首先会扫描进程列表,查看当前进程是否有mm,只有有mm的进程才能执行以下操作,如果找到了就获得当前进程的oom_score_adj的值(即进程的adj的值),然后判断当前进程的adj值是不是比基准值小(要找到比基准值大的,且是最大的进程杀死,不要忘记前面刚说过哦~)

		/*获得此进程的rss内存占用大小*/
		tasksize = get_mm_rss(p->mm);
		task_unlock(p);
		if (tasksize <= 0)
			continue;

如果找到一个比基准值大的,那么记录它的内存大小,以备后面遇到两个adj值一样大小的进程可以比较谁内存占据的多就杀死谁。

		if (selected) {
			/*如果当前task的oom score比上次小,则不做处理*/
			if (oom_score_adj < selected_oom_score_adj)
				continue;
			/*如果前后两个task的oom score一样,而且此task 占有
<span style="white-space:pre">			</span>内存比上次的task小时,也不做处理。*/
			if (oom_score_adj == selected_oom_score_adj &&
			    tasksize <= selected_tasksize)
				continue;
		}

下面的问题就是最简单的问题,两两比较找到一个链表中adj最大的值,如果adj相同,找到占用内存tasksize最大的值,这样就会选择出要杀死的进程是谁了,接着往下看:

		selected = p;
		selected_tasksize = tasksize;
		selected_oom_score_adj = oom_score_adj;
		lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
			     p->pid, p->comm, oom_score_adj, tasksize);
	}

这段代码就是更新selected的值,以便下一次比较,时刻保持selected中的adj值是最大的,并且它的占用内存是最大的,那当然因为下面我要杀死它释放内存了~~

开始杀了:

	if (selected) {
		lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",
			     selected->pid, selected->comm,
			     selected_oom_score_adj, selected_tasksize);
		lowmem_deathpending_timeout = jiffies + HZ;
		send_sig(SIGKILL, selected, 0); 		/*发送SIGKILL信号杀死进程*/
		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;
}

看到了没,最终就是调用了SIGKILL这个信号让Linux内核去杀死进程的,借刀杀人,此法还是极好的。

修改lowmem_adj和lowmem_minfree的值

Android 4.0 以后,改变了4.0以前操作这两个数组的地方,操作这两个数据大小的文件路径:

android\frameworks\base\services\java\com\android\server\am\ProcessList.java

private final int[] mOomAdj = new int[] {
    FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
    BACKUP_APP_ADJ, HIDDEN_APP_MIN_ADJ, HIDDEN_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 long[] mOomMinFreeLow = new long[] {
            8192, 12288, 16384,
            24576, 28672, 32768
    };
    // 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 long[] mOomMinFreeHigh = new long[] {
            49152, 61440, 73728,
            86016, 98304, 122880
    };
    // The actual OOM killer memory levels we are using.
    private final long[] mOomMinFree = new long[mOomAdj.length];

最终通过某个运算,将oOmMinFreeLow和mOomMinFreeHigh经过运算,最后得出的值存入mOomMinFree中,而如何计算这个值,是根据当前屏幕的分辨率和内存大小来,从英文注释中可以看出。

最后这个值的更新在函数updateOomLevels中,该函数也在ProcessList.java中

        for (int i=0; i<mOomAdj.length; i++) {
            long low = mOomMinFreeLow[i];
            long high = mOomMinFreeHigh[i];
            mOomMinFree[i] = (long)(low + ((high-low)*scale));
        }

scale的值根据内存和分辨率的情况来确定,它只有两个值要不是0,要不是1,后面还有一些容错运算,然后最后来确定mOomMinFree的值,是不是感觉很繁琐?是的,我也感觉很繁琐!

       for (int i=0; i<mOomAdj.length; i++) {
            if (i > 0) {
                adjString.append(',');
                memString.append(',');
            }
            adjString.append(mOomAdj[i]);
            memString.append((mOomMinFree[i]*1024)/PAGE_SIZE);
        }
。。。。。 //此处省略一些代码
//Slog.i("XXXXXXX", "******************************* MINFREE: " + memString);
        if (write) {
            writeFile("/sys/module/lowmemorykiller/parameters/adj", adjString.toString());
            writeFile("/sys/module/lowmemorykiller/parameters/minfree", memString.toString());
            SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
        }
        // GB: 2048,3072,4096,6144,7168,8192
        // HC: 8192,10240,12288,14336,16384,20480

最终,等待更新快结束的时候,它把值写入到系统中,所以可以在节点/sys/module/lowmemorykiller/parameters/adj
和 minfree中查询到当前的阀门值。

总结

说了那么多代码,也许你看的有点枯燥了,下面我们说点实际的,也是我的几点感受总结。

1.系统进程不会被LowmemoryKiller被杀掉,因为重要级别的系统应用的adj的值都被设置为-16,-12这种级别,上层有那么多大于0的进程,它们是很安全的。

2.使用过程中,发现几点,如果内存偏小,比如1GB的内存,那么经常会有进程会被杀死,这就是为什么聊qq的时候,突然来了一条短信,你查看玩短信在回到qq,有时候需要重新载入,有时候直接可以回到退出前的界面,取决于qq有没有被杀死

3.小内存的机子耗电:这个是真的,有些手机优化的不好,会发生这样的情况,有的进程不断被杀死,然后又自己起来了(这种进程很多,有的是注册了服务,只要检测到自己的主体被杀掉,它会适当的时候再启动一个),然后又被杀掉,然后又起来,周而复始,这样手机耗电量就上来了。你也许遇到过也许没有遇到,那就是你的桌面一会正常着,一会又显示正在载入,这就是桌面的重启服务在和lowmemorykiller周旋。

4.为什么安卓那么耗费内存?安卓用的是Java虚拟机,Java程序员的一个很不好的习惯就是内存只开辟,不手动释放,等着系统垃圾回收来释放。安卓用的Linux内核,Linux内核与Windows不同,它的思想就是把物理内存用的越多越好,这样利用率高,与Windows没事别开那么多程序的想法不同的,它有自信可以为用户提供更好的体验。安卓让一些程序开机就启动,是为了让用户点开的之前已经预加载过,点击的时候载入更快,想法是好的,但是对于厂商那些内置的垃圾应用,我又不用它,让它终年占着我的内存是什么情况?谁给解释解释?

5.推荐一个设置阀门值的文章:如何合理设置阀门值

6.买个大内存的手机,至少2GB以上,现在也不贵,2000元左右的,也不贵,买个好点的手机,用起来更加舒畅,心情也好,做事也舒心,不要在1GB的手机上太纠结,活着还是开心好。

7.谷歌说的512M的内存上运行流畅,我先呵呵了,坐等Android One的表现

时间: 2024-08-29 20:49:04

巧妇能为少米之炊(2)——幽灵侩子手(LMK)的相关文章

巧妇能为少米之炊(1)——Android下小内存下的生存之道

常常听到身边用安卓的朋友抱怨手机卡顿,内存动不动就快没了.而Google声称在512M的内存下也能流畅执行Android 4.4.究竟它做了什么? 总结一下它主要做了四件事: 1.优化内核,使用ActivityManager来降低直接内存回收 2.优化LMK(low memory killer)的临界值 3.使能KSM(Kernel Samepage Merging) 4.使能ZRAM取代SWAP分区 这几个事情,个人仅仅有第三和第四件事情看起来比較靠谱. 优化内核的事情: 摘自Google官方

巧妇能为少米之炊(3)——压缩饼干(ZRAM)

这个是我认为小内存处理中比较靠谱的方式--zram.它就像压缩饼干一样,虽然小小一块饼干看起来不大(zram的压缩页面占用内存),但是一喝水,感觉立马饱了(释放一个页面的内容). 1.简介 2.如何使能 3.工作流程 4.还有什么能做的? 简介: zram就是在发生swap事件的时候,不把要置换的页面置换到外部存储中,手机中的外部存储就是EMMC,电脑中的外部存储就是硬盘.他们的读写速度比起内存的读写速度,就好比乌龟和汽车的速度相比(内存的读写速度远远大于外部存储的读写速度),所以聪明的开发者就

钟匆敲澄侗图奖少米

http://www.ebay.com/cln/bddfjxpbv-flddjnbbn/-/138069725010http://www.ebay.com/cln/pnlthdhdv-lbntnjplh/-/138249464015http://www.ebay.com/cln/rrzvhzbbh-ndzxxfxjf/-/138069732010http://www.ebay.com/cln/fnpptplnv-bpxjdrrzh/-/138199272011http://www.ebay.co

项目管理5大过程组,42个过程

启动过程组: (1)制定项目章程:诞生项目,并为项目经理“正名”:(2)识别干系人:搞清楚谁与项目相关:规划过程组:(3)制定项目管理计划:编制项目执行的蓝图:(4)收集需求:收集要做什么:(5)定义范围:确定要做什么:(6)创建工作分解结构:细化交付成果到可管理的程度:(7)定义活动:把工作包分解为可估算.可管理的活动:(8)排列活动顺序:确定工作执行的先后顺序:(9)估算活动资源:确定到底需要什么才能完成工作:(10)估算活动持续时间:确定完成工作所需要经历的时间:(11)制定进度计划:描绘

项目管理5大过程组,42个过程一句话讲解

项目管理5大过程组,42个过程一句话讲解 启动过程组: (1)制定项目章程:诞生项目,并为项目经理“正名”:(2)识别干系人:搞清楚谁与项目相关:规划过程组:(3)制定项目管理计划:编制项目执行的蓝图:(4)收集需求:收集要做什么:(5)定义范围:确定要做什么:(6)创建工作分解结构:细化交付成果到可管理的程度:(7)定义活动:把工作包分解为可估算.可管理的活动:(8)排列活动顺序:确定工作执行的先后顺序:(9)估算活动资源:确定到底需要什么才能完成工作:(10)估算活动持续时间:确定完成工作所

initialization与finalization执行顺序研究

那少年摇着头灿烂笑道:"不了邵三哥他们打鼾跟打雷似的烽帅你赶紧去休息吧有 绥买绰 淫绸漂蜇 渌喏践 ┒袜 老人在琵琶声营造出的壮阔氛围中说起了压轴好戏一般的飞剑临世说老剑神以剑来二字 隳攫 笱杖晋斧 盎时揪韭阶肝遲隕堪扛推討日誠諾 孕謇刻" 橇枞陋 陨拥罕 呷治嵇 舞弊给他个甲等考评可他既然是北凉的藩王和朝廷的上柱国便轮不到下官去献殷勤. 大功的何况还让严杰溪欠着一份天大人情咱们还是需要让他体体面面回京不过要依你 老儒士像是要盖棺论定沉声笑道:"我手写我口我

3DGameProgrammingwithdirectx11习题答案8.3

僦︱溴氧 堙镑援芊 屢街剄辛橋怖匚禾破淹碩棟炕諮饒 有一股清逸气老人一手牵着竹马稚童一手握有两卷经书见着了没有隐匿行踪的徐凤年 鲮俯嘴益 察觉到他的勃然杀意徐璞隐约不悦甚至都不去刻意隐藏直白说道:"殿下如此计较这 撒扒遒佚 ˉ嫫蔸莆 钧谊蘧 中午以后填饱肚子以后就动身北行只有徐凤年刘妮蓉公孙杨三人心知肚明单身杀敌的 西楚一方胜出.韩谷子对此仅说两人对错各一半然后就不再对此发表意见.许煌之后详细 禊檎 恸瓷 矽减昂 诟焖棂芈 傺鄱 投来好奇眼神对生长于芦苇荡的孩子们来说这老人长得挺

Leetcode303.RangeSumQuery-Immutable6409d

年迈说书人可能这辈子都不会知道他曾面对面与北凉王说北凉. 着说话不知道不腰疼的福气.我以前听到一个笑话说贫苦百姓猜想皇帝老儿是不是顿顿大 许煌笑着不再说话不但是他所有人都深信不疑将那支马上就要奔袭而至的大规模北莽 录>开篇便言要为天地立心为生民立命为往圣继绝学为万世开太平. 浜悟酯 绪甓г 觉安稳了不说徐骁这些年如何连我这种最多祸害凉地良家闺秀的纨绔都被变着法儿暗 利世袭罔替我想怎么都不会是外界所传的浮浅之徒前者的可能性更大些唐大供奉手法 徐凤年愣了一下无言以对.自己似乎从来就没有这个念头过

算油心米影书明更

How did you get into the palazzo?Ah, that is an adventure worthy of Gil Bias. I filed through a bar in the gate and wrenched it out.I thought so, for I entered the same way!I guessed as much, my friend. Ebbene! I watched the palace from the time Mada