博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深挖android low memory killer
阅读量:6260 次
发布时间:2019-06-22

本文共 6217 字,大约阅读时间需要 20 分钟。

对于PC来说,内存是至关重要。如果某个程序发生了内存泄漏,那么一般情况下系统就会将其进程Kill掉。Linux中使用一种名称为OOM(Out Of Memory,内存不足)的机制来完成这个任务,该机制会在系统内存不足的情况下,选择一个进程并将其Kill掉。Android由于是嵌入式设备的操作系统,则使用了一个新的机制Low Memory Killer来完成同样的任务。下面首先来看看Low Memory Killer机制的原理以及它是如何选择被Kill的进程的。

 

  1.Low Memory Killer的原理和机制

 

  Low Memory Killer在用户空间中指定了一组内存临界值,当其中的某个值与进程描述中的oom_adj值在同一范围时,该进程将被Kill掉。通常,在“/sys/module/lowmemorykiller / parameters/adj”中指定oom_adj的最小值,在“/sys/module/lowmemorykiller/parameters/minfree”中储存空闲页面的数量,所有的值都用一个逗号将其隔开且以升序排列。比如:把“0,8”写入到/sys/module/lowmemorykiller/parameters/adj中,把“1024,4096”写入到/sys/module/lowmemory- killer/parameters/minfree中,就表示当一个进程的空闲存储空间下降到4096个页面时,oom_adj值为8或者更大的进程会被Kill掉。同理,当一个进程的空闲存储空间下降到1024个页面时,oom_adj值为0或者更大的进程会被Kill掉。我们发现在lowmemorykiller.c中就指定了这样的值,如下所示:

static int lowmem_adj[6] = {    0,    1,    6,    12,};static int lowmem_adj_size = 4;static size_t lowmem_minfree[6] = {    3*512, // 6MB    2*1024, // 8MB    4*1024, // 16MB    16*1024, // 64MB};static int lowmem_minfree_size = 4;

这就说明,当一个进程的空闲空间下降到3´512个页面时,oom_adj值为0或者更大的进程会被Kill掉;当一个进程的空闲空间下降到2´1024个页面时,oom_adj值为10或者更大的进程会被Kill掉,依此类推。其实更简明的理解就是满足以下条件的进程将被优先Kill掉:

  task_struct->signal_struct->oom_adj越大的越优先被Kill。

  占用物理内存最多的那个进程会被优先Kill。

  进程描述符中的signal_struct->oom_adj表示当内存短缺时进程被选择并Kill的优先级,取值范围是-17~15。如果是-17,则表示不会被选中,值越大越可能被选中。当某个进程被选中后,内核会发送SIGKILL信号将其Kill掉。

  实际上,Low Memory Killer驱动程序会认为被用于缓存的存储空间都要被释放,但是,如果很大一部分缓存存储空间处于被锁定的状态,那么这将是一个非常严重的错误,并且当正常的oom killer被触发之前,进程是不会被Kill掉的。

2.Low Memory Killer的具体实现

在了解了Low Memory Killer的原理之后,我们再来看如何实现这个驱动。Low Memory Killer驱动的实现位于drivers/misc/lowmemorykiller.c。

该驱动的实现非常简单,其初始化与退出操作也是我们到目前为止见过的最简单的,代码如下:

static int __init lowmem_init(void){    register_shrinker(&lowmem_shrinker);    return 0;}static void __exit lowmem_exit(void){    unregister_shrinker(&lowmem_shrinker);}module_init(lowmem_init);module_exit(lowmem_exit);
在初始化函数lowmem_init中通过register_shrinker注册了一个shrinker为lowmem_shrinker;退出时又调用了函数lowmem_exit,通过unregister_shrinker来卸载被注册的lowmem_shrinker。其中lowmem_shrinker的定义如下:

static struct shrinker lowmem_shrinker = {    .shrink = lowmem_shrink,    .seeks = DEFAULT_SEEKS * 16};
lowmem_shrink是这个驱动的核心实现,当内存不足时就会调用lowmem_shrink方法来Kill掉某些进程。下面来分析其具体实现,实现代码如下:

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask){    struct task_struct *p;    struct task_struct *selected = NULL;    int rem = 0;    int tasksize;    int i;    int min_adj = OOM_ADJUST_MAX + 1;    int selected_tasksize = 0;    int array_size = ARRAY_SIZE(lowmem_adj);    int other_free = global_page_state(NR_FREE_PAGES);    int other_file = global_page_state(NR_FILE_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++) {        if (other_free < lowmem_minfree[i] &&            other_file < lowmem_minfree[i]) {            min_adj = lowmem_adj[i];            break;        }    }    if(nr_to_scan > 0)        lowmem_print(3, "lowmem_shrink %d, %x, ofree %d %d, ma %d\n", nr_to_scan,                  gfp_mask, other_free, other_file, min_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);    if (nr_to_scan <= 0 || min_adj == OOM_ADJUST_MAX + 1) {        lowmem_print(5, "lowmem_shrink %d, %x, return %d\n", nr_to_scan, gfp_mask,                  rem);        return rem;    }    read_lock(&tasklist_lock);    for_each_process(p) {        if (p->oomkilladj < min_adj || !p->mm)            continue;        tasksize = get_mm_rss(p->mm);        if (tasksize <= 0)            continue;        if (selected) {            if (p->oomkilladj < selected->oomkilladj)                continue;            if (p->oomkilladj == selected->oomkilladj &&                tasksize <= selected_tasksize)                continue;        }        selected = p;        selected_tasksize = tasksize;        lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",                     p->pid, p->comm, p->oomkilladj, tasksize);    }    if(selected != NULL) {        lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",                     selected->pid, selected->comm,                     selected->oomkilladj, selected_tasksize);        force_sig(SIGKILL, selected);        rem -= selected_tasksize;    }    lowmem_print(4, "lowmem_shrink %d, %x, return %d\n", nr_to_scan, gfp_mask, rem);    read_unlock(&tasklist_lock);    return rem;}
可以看出,其中多处用到了global_page_state函数。有很多人找不到这个函数,其实它被定义在了linux/vmstat.h中,其参数使用zone_stat_item枚举,被定义在linux/mmzone.h中,具体代码如下:

enum zone_stat_item {    NR_FREE_PAGES,    NR_LRU_BASE,    NR_INACTIVE_ANON = NR_LRU_BASE,    NR_ACTIVE_ANON,    NR_INACTIVE_FILE,    NR_ACTIVE_FILE,#ifdef CONFIG_UNEVICTABLE_LRU    NR_UNEVICTABLE,    NR_MLOCK,#else    NR_UNEVICTABLE = NR_ACTIVE_FILE, /* 避免编译错误*/    NR_MLOCK = NR_ACTIVE_FILE,#endif    NR_ANON_PAGES,        /* 匿名映射页面*/    NR_FILE_MAPPED,        /*映射页面*/    NR_FILE_PAGES,    NR_FILE_DIRTY,    NR_WRITEBACK,    NR_SLAB_RECLAIMABLE,    NR_SLAB_UNRECLAIMABLE,    NR_PAGETABLE,    NR_UNSTABLE_NFS,    NR_BOUNCE,    NR_VMSCAN_WRITE,    NR_WRITEBACK_TEMP,    /* 使用临时缓冲区*/#ifdef CONFIG_NUMA    NUMA_HIT,            /* 在预定节点上分配*/    NUMA_MISS,            /* 在非预定节点上分配*/    NUMA_FOREIGN,    NUMA_INTERLEAVE_HIT,    NUMA_LOCAL,            /* 从本地页面分配*/    NUMA_OTHER,            /* 从其他节点分配 */#endif    NR_VM_ZONE_STAT_ITEMS };

 再回过头来看owmem_shrink函数,首先确定我们所定义的lowmem_adj和lowmem_minfree数组的大小(元素个数)是否一致,如果不一致则以最小的为基准。因为我们需要通过比较lowmem_minfree中的空闲储存空间的值,以确定最小min_adj值(当满足其条件时,通过其数组索引来寻找lowmem_adj中对应元素的值);之后检测min_adj的值是否是初始值“OOM_ADJUST_MAX + 1”,如果是,则表示没有满足条件的min_adj值,否则进入下一步;然后使用循环对每一个进程块进行判断,通过min_adj来寻找满足条件的具体进程(主要包括对oomkilladj和task_struct进行判断);最后,对找到的进程进行NULL判断,通过“force_sig(SIGKILL, selected)”发送一条SIGKILL信号到内核,Kill掉被选中的“selected”进程。

  关于Low Memory Killer的分析就到这里,在了解了其机制和原理之后,我们发现它的实现非常简单,与标准的Linux OOM机制类似,只是实现方式稍有不同。标准Linux的OOM Killer机制在mm/oom_kill.c中实现,且会被__alloc_pages_may_oom调用(在分配内存时,即mm/page_alloc.c中)。oom_kill.c最主要的一个函数是out_of_memory,它选择一个bad进程Kill,Kill的方法同样是通过发送SIGKILL信号。在out_of_memory中通过调用select_bad_process来选择一个进程Kill,选择的依据在badness函数中实现,基于多个标准来给每个进程评分,评分最高的被选中并Kill。一般而言,占用内存越多,oom_adj就越大,也就越有可能被选中。

转载地址:http://rcesa.baihongyu.com/

你可能感兴趣的文章
canvas核心技术-如何绘制图形
查看>>
netty源码分析之pipeline(二)
查看>>
面试:讲讲 Android 的事件分发机制
查看>>
计算机程序的思维逻辑 (95) - Java 8的日期和时间API
查看>>
计算机程序的思维逻辑 (8) - char的真正含义
查看>>
2019 年技术大趋势预测
查看>>
推荐一款基于vue的滚动条插件vuescroll
查看>>
安全圈有多大?也许就这么大!
查看>>
App基于手机壳颜色换肤?先尝试一下用 KMeans 来提取图像中的主色
查看>>
RecyclerView的滚动事件研究
查看>>
XXL-MQ v1.2.2 发布,分布式消息队列
查看>>
多线程:GCD
查看>>
深度解读 2018 JavaScript 趋势报告(含视频)
查看>>
以 RAIDs 分析作为架构驱动力
查看>>
Rust 2018 年度调查报告
查看>>
Tensorflow快餐教程(1) - 30行代码搞定手写识别
查看>>
聊聊flink Table的Set Operations
查看>>
3.3 卷积神经网络进阶-Inception-mobile_net
查看>>
JS学习系列 06 - 变量对象
查看>>
Swift开发应用时如何更方便地使用颜色?
查看>>