Android lowmemkiller配置(Android7.1实践)

一、概述

1.1 Android

Andorid的Low Memory Killer是在标准的linux kernel的OOM基础上修改而来的一种内存管理机制。当系统内存不足时,杀死不必要的进程释放其内存。不必要的进程的选择根据有2个:oom_adj和占用的内存的大小。

oom_adj代表进程的优先级,数值越高,优先级越低,越容易被杀死;对应每个oom_adj都可以有一个空闲内存的阀值。Android Kernel每隔一段时间会检测当前空闲内存是否低于某个阀值。假如是,则杀死oom_adj最大的不必要的进程,如果有多个,就根据oom_score_adj去杀死进程,直到内存恢复低于阀值的状态。

LowMemoryKiller的值的设定,主要保存在2个文件之中,分别是:

/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree

例如:

# cat /sys/module/lowmemorykiller/parameters/adj
0,100,200,300,900,906
# cat /sys/module/lowmemorykiller/parameters/minfree
9216,11520,13824,16128,18432,23040

Android按照进程的不同类别,分为了6个等级
在这里插入图片描述
分别对应的空闲内存阈值,在frameworks\base\services\core\java\com\android\server\am\ProcessList.java中的函数updateOomLevels计算得来。ADJ在ProcessList.java中定义,不用做修改。

在Android系统里面,当app的状态发生变化,如:创建,收到广播,唤醒,放入后台等, ActivityManagerService的updateOomAdjLocked() 会computeOomAdjLocked(),然后,通过applyOomAdjLocked()把app的oom_adj值写入到Linux Kernel。

那么AN是如何计算每个app最终的oom_adj值的呢?

oom_adj的范围是[-16, 15],AN根据app进程的特性进行了分类,不同的类别,对应不同的数值。

  • 9 ~ 15: 是缓存到后台的app。
  • 8: service B 列表, 长时间未使用的service进程。
  • 7: 前一个app。
  • 6: Home app。
  • 5: 包含service的app 进程。
  • 4: 高权重的应用--隐藏的属性,AN P以上版本才能配置:android:cantSaveState=“true”
  • 3: backup app--正在被备份的app。
  • 2: 用户可感知的后台进程,如:后台背景音乐播放器。
  • 1: 前台app启动的一些可见的组件,如: 弹出的Email activity。
  • 0: 前台app.
  • -11: persistent service -- android:persistent:=true
  • -12: 常驻内存的系统app--如:系统自带的拨号app。
  • -16: 系统进程--如:system_server进程。
  • -17: 不受oom_adj的管理,该进程不会被杀死,也native process的默认值。
1.2 lowmemorykiller Driver部分

lowmemorykiller driver 位于 drivers/staging/android/lowmemorykiller.c

LMK通过注册shrinker来实现,shrinker是Linux kernel标准的回收page的机制,由内核线程kswapd负责监控。参见mm/vmscan.c中的kswapd。 当需要分配内存,发现可用内存不足时,则内核会阻塞请求分配内存的进程,进入slow path的内存申请逻辑进行回收(包括ZRAM的内存压缩),参见mm/page_alloc.c中的__alloc_pages_slowpath。 LMK核心思想:

LowMemoryKiller注册了shrinker--Linux Kernel的一个内存管理工具,当kernel需要回收内存时,会回调LowMemoryKiller的lowmem_shrink(),它先检查kernel 剩下多少内存,根据剩下的内存数量来匹配数组 lowmem_minfree[], 找到数组索引值,然后,再使用该索引值,从 lowmem_adj[]这个数组里面就得到目标oom_adj值,最终,在大于等于该目标oom_adj的进程中,杀死拥有最大oom_adj值的进程--send_sig(SIGKILL, selected, 0) 。算法其实很简单,就是两个一维数组的映射。

lowmemorykiller.c 中的lowmem_shrink注册到vm

扫描二维码关注公众号,回复: 9621263 查看本文章
static struct shrinker lowmem_shrinker = {
	.shrink = lowmem_shrink,
	.seeks = DEFAULT_SEEKS * 16
};

static int __init lowmem_init(void)
{
	register_shrinker(&lowmem_shrinker);
	return 0;
}
/*
 * 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);
}

然后,当Linux内存管理模块线程kswapd被调度时,就会通过 kswapd_shrink_node > ……>scan_objects 来触发lowmem_shrink内存扫描及执行lowmem killer。lowmem_shrink根据当前系统free内存和每个进程的oom_score_adj来决定当前哪个进程将会被killed。

内存回收时机

内存中有三个水位min <low < high,当内存达到low水位时,kswapd开始回收内存,直到内存达到high水位时停止kswapd;

如果kswapd回收速度小于内存消耗速度,内存水位下降到min水位,则direct reclaim开始回收内存,并会阻塞应用程序。 内存换出到swap分区的过程:

kswapd()-->balance_pgdat()-->shrink_zone()-->shrink_inactive_list()

Watermark的设置

每个zone有单独的水位,可以在/proc/sys/vm/min_free_kbytes中设置min水位,这个参数本身决定了系统中每个zone的watermark[min]的值大小。 然后内核根据min的大小并参考每个zone的内存大小分别算出每个zone的low水位和high水位值 通过命令查看zone的watermark:

XXXXX:/ # cat /proc/zoneinfo
  Node 0, zone      DMA
  per-node stats
  ………….
     pages free     285389
        min      1392
        low      2054 
        high     2716
   node_scanned  0
  …………..

相关代码见/mm/page_alloc.c: __setup_per_zone_wmarks

二、配置修改

关于lowmemkiller可配置项有
android\frameworks\base\core\res\res\values\config.xml

<!-- config_lowMemoryKillerMinFreeKbytesAbsolute和config_lowMemoryKillerMinFreeKbytesAdjust修改minfree的默认值 -->
<!-- config_lowMemoryKillerMinFreeKbytesAbsolute按照和MAX_ADJ的minfree比例,等比例计算个等级的空闲内存阈值。-->
<integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">-1</integer>
<!-- config_lowMemoryKillerMinFreeKbytesAdjust按照和MAX_ADJ的minfree比例,等比例计算各等级的空闲内存阈值的加减量,然后默认值加或减加减量 -->
<integer name="config_lowMemoryKillerMinFreeKbytesAdjust">0</integer>

<!-- 修改/proc/sys/vm/extra_free_kbytes的值,这个参数告诉VM在后台回收程序开始工作的门限值和直接回收(内存分配进程做的)的门限值之间保持额外的free memory。需要低延迟的内存分配和在内存分配具有突发性的情况很有用,例如一个实时应用,接受和发送最大的信息可能达到200MB的网络数据(导致内核内存分配),这就需要200MB的额外的可用内存来避免直接回收内存相关的延迟。-->
<!-- config_extraFreeKbytesAbsolute替换默认值 -->
<integer name="config_extraFreeKbytesAbsolute">-1</integer>
<!-- config_extraFreeKbytesAdjust,在默认值的基础上加或减配置值 -->
<integer name="config_extraFreeKbytesAdjust">0</integer>

计算代码如下:

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);
if (minfree_abs >= 0) {
    for (int i=0; i<mOomAdj.length; i++) {
        mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]
                / mOomMinFree[mOomAdj.length - 1]);
    }
}

if (minfree_adj != 0) {
    for (int i=0; i<mOomAdj.length; i++) {
        mOomMinFree[i] += (int)((float)minfree_adj * mOomMinFree[i]
                / mOomMinFree[mOomAdj.length - 1]);
        if (mOomMinFree[i] < 0) {
            mOomMinFree[i] = 0;
        }
    }
}
... 
int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust);
        int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute);
if (reserve_abs >= 0) {
    reserve = reserve_abs;
}

if (reserve_adj != 0) {
    reserve += reserve_adj;
    if (reserve < 0) {
        reserve = 0;
    }
}

在updateOomLevels中,oom_adj和minfree通过socket传给lmkd服务,服务设置给驱动。

if (write) {
    ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
    buf.putInt(LMK_TARGET);
    for (int i=0; i<mOomAdj.length; i++) {
    	// 注意:设置到kernel的minfree是内存页数,每个内存页大小为PAGE_SIZE(4KB)
        buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
        buf.putInt(mOomAdj[i]);
    }
    writeLmkd(buf); // 写给lmkd服务
    SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve)); // 触发init.rc中on事件
}

reserve_adj 在init.rc中通过如下方式设置

on property:sys.sysctl.extra_free_kbytes=*
    write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}

注意: 设置到kernel的minfree是内存页数,每个内存页大小为PAGE_SIZE(4KB)

三、实践

开发的设备内存比较小,总共1G,可用动态分配只有770,952KB。
monkey煲机,发现很容易死机,log如下

[ 1875.198095] match process is mediaserver ,not kill it!
[ 1875.204129] match process is android.icetech ,not kill it!
[ 1875.210333] BUG: scheduling while atomic: flushcache.sh/9931/0x40000028
[ 1875.217600] Modules linked in: itech_rfid itech_led itech_wiegand bcmdhd mali(O) nand(O)
[ 1875.226864] CPU: 1 PID: 9931 Comm: flushcache.sh Tainted: G           O 3.10.65 #18
[ 1875.235638] [<c0017fc4>] (unwind_backtrace+0x0/0xec) from [<c0014238>] (show_stack+0x20/0x24)
[ 1875.245261] [<c0014238>] (show_stack+0x20/0x24) from [<c06aa4c4>] (dump_stack+0x20/0x28)
[ 1875.254376] [<c06aa4c4>] (dump_stack+0x20/0x28) from [<c06a7818>] (__schedule_bug+0x54/0x6c)
[ 1875.263890] [<c06a7818>] (__schedule_bug+0x54/0x6c) from [<c06ae4a4>] (__schedule+0x80/0x754)
[ 1875.273513] [<c06ae4a4>] (__schedule+0x80/0x754) from [<c0054208>] (__cond_resched+0x20/0x2c)
[ 1875.283111] [<c0054208>] (__cond_resched+0x20/0x2c) from [<c06aec1c>] (_cond_resched+0x40/0x50)
[ 1875.292900] [<c06aec1c>] (_cond_resched+0x40/0x50) from [<c00e5cec>] (shrink_slab+0x29c/0x398)
[ 1875.302609] [<c00e5cec>] (shrink_slab+0x29c/0x398) from [<c0162e34>] (drop_caches_sysctl_handler+0x84/0xa0)
[ 1875.313536] [<c0162e34>] (drop_caches_sysctl_handler+0x84/0xa0) from [<c016e9e0>] (proc_sys_call_handler+0x94/0xb0)
[ 1875.325265] [<c016e9e0>] (proc_sys_call_handler+0x94/0xb0) from [<c016ea20>] (proc_sys_write+0x24/0x2c)
[ 1875.335874] [<c016ea20>] (proc_sys_write+0x24/0x2c) from [<c0117200>] (vfs_write+0xe8/0x184)
[ 1875.345388] [<c0117200>] (vfs_write+0xe8/0x184) from [<c01175a0>] (SyS_write+0x50/0x78)
[ 1875.354403] [<c01175a0>] (SyS_write+0x50/0x78) from [<c000f9e0>] (ret_fast_syscall+0x0/0x30)
[ 1935.389865] INFO: rcu_preempt self-detected stall on CPU { 0}  (t=6000 jiffies g=68818 c=68817 q=1590)
[ 1935.390012] CPU: 0 PID: 9931 Comm: flushcache.sh Tainted: G        W  O 3.10.65 #18
[ 1935.390012] [<c0017fc4>] (unwind_backtrace+0x0/0xec) from [<c0014238>] (show_stack+0x20/0x24)
[ 1935.390012] [<c0014238>] (show_stack+0x20/0x24) from [<c06aa4c4>] (dump_stack+0x20/0x28)
[ 1935.390012] [<c06aa4c4>] (dump_stack+0x20/0x28) from [<c0015be4>] (smp_send_all_cpu_backtrace+0x64/0xd4)
[ 1935.390012] [<c0015be4>] (smp_send_all_cpu_backtrace+0x64/0xd4) from [<c0010da8>] (arch_trigger_all_cpu_backtrace+0x18/0x1c)
[ 1935.390012] [<c0010da8>] (arch_trigger_all_cpu_backtrace+0x18/0x1c) from [<c00a2e1c>] (rcu_check_callbacks+0x280/0x71c)
[ 1935.390012] [<c00a2e1c>] (rcu_check_callbacks+0x280/0x71c) from [<c00346a4>] (update_process_times+0x4c/0x78)
[ 1935.390012] [<c00346a4>] (update_process_times+0x4c/0x78) from [<c0074684>] (tick_sched_handle+0x58/0x64)
[ 1935.390012] [<c0074684>] (tick_sched_handle+0x58/0x64) from [<c0074930>] (tick_sched_timer+0x54/0x84)
[ 1935.390012] [<c0074930>] (tick_sched_timer+0x54/0x84) from [<c004a678>] (__run_hrtimer+0x1b8/0x2d0)
[ 1935.390012] [<c004a678>] (__run_hrtimer+0x1b8/0x2d0) from [<c004b314>] (hrtimer_interrupt+0x148/0x2b4)
[ 1935.390012] [<c004b314>] (hrtimer_interrupt+0x148/0x2b4) from [<c049099c>] (arch_timer_handler_phys+0x38/0x40)

从log看是触发了内除回收,结合出发前剩余内存信息,此时可用内存46364KB(此处log未体现),按照lowmemkiller的机制,此时的可用内存应该小于最小可用内存的阈值,影响了前台进程运行,因此触发内存压缩,回收内存。但是设备并没有配置内存压缩分区,内存不能再分配,因此崩溃。

查看不同oom_adj对应的minfree

xxxx:/ # cat /sys/module/lowmemorykiller/parameters/minfree
18432,23040,27648,32256,36864,46080

对应的字节数是(内存页数*4,即KB)

73728,92160,110592,1299024,147456,184320

剩余内存的确小于了前台进程对应的可用内存阈值73728,触发了内存压缩。设备配置内存本来比较小,没有必要把阈值配置的很高,由于使用的是默认值,没有做修改,也没有打开low_mem,从代码看low_mem打开会牺牲一些性能(实测如此),因此没有打开,那么现在需要修改不同oom_adj对应的可用内存。
通过配置config_lowMemoryKillerMinFreeKbytesAdjust,把各阈值减少为

xxxx:/ # cat /sys/module/lowmemorykiller/parameters/minfree
9216,11520,13824,16128,18432,23040

对应KB值为

36864,46080,55296,64512,73728,92160

通过log,5s前的可用内存是87176KB,现在是46364KB,内存跳跃较大(此处应该有异常),查看
/proc/sys/vm/extra_free_kbytes

xxxx:/ # cat /proc/sys/vm/extra_free_kbytes
7200

这个值不是内存页数,单位是KB,因此只有7M,通过多次可用内存分析,内存需求较大是在20M左右,为系统稳定不卡顿运行,该值设置为25M,修改config_extraFreeKbytesAbsolute为25600即可。

通过修改后,做相同的压测,系统稳定性提高很多,已煲机21小时,还在煲机中。=_=

发布了93 篇原创文章 · 获赞 12 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/mcsbary/article/details/104609109