Android 进阶—— Framework 核心之 Low Memory Killer机制和进程优先级小结

Posted CrazyMo_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 进阶—— Framework 核心之 Low Memory Killer机制和进程优先级小结相关的知识,希望对你有一定的参考价值。

引言

无论是在设备硬件条件低,内存相对稀缺的年代,还是在现在内存相对充裕,设备的内存都尤其珍贵,特别是对于一些低端设备来更甚,于是乎Linux操作系统设计了OOM Kill 机制,android 系统也不例外。

一、Linux OOM Kill

1、按需分配物理页面

通常一个进程会申请一块很大的内存,,但只是用到其中的一小部分。为了避免内存的浪费,在分配页面时,Linux 采用的是按需分配物理页面的方式。当某个进程调用malloc()申请了一块小内存,此时内核会分配一个虚拟页面,但这个页面不会映射到实际的物理页面,因此当程序首次访问这个虚拟页面时,会触发一个缺页异常 (page fault);此时内核才会分配一个物理页面,让虚拟页面映射到这个物理页面同时更新进程的页表 (page table)。 这种按需分配物理页面的方式可以大大节省物理内存的使用,但有时会导致 Memory Overcommit。

2、Memory Overcommit

Memory Overcommit所有进程使用的虚拟内存超过了系统的物理内存和交换空间的总和。Linux 默认是允许 Memory Overcommit :

在 Linux 中,可以通过内核参数vm.overcommit_memory去控制是否允许 overcommit:

  • 默认值是 0,在这种情况下,只允许轻微的 overcommit,而比较明显的 overcommit 将不被允许。
  • 如果设置为 1,表示总是允许 overcommit。
  • 如果设置为 2,则表示总是禁止 overcommit。也就是说,如果某个申请内存的操作将导致 overcommit,那么这个操作将不会得逞。

在大多数情况下Memory Overcommit 也是安全的,因为很多进程只是申请了很多内存,但实际使用到的内存并不多。但万一很多进程都使用了申请来的大部分内存,就可能导致物理内存和交换空间不够用了,这时内核的 OOM Killer 就会出马,它会选择杀掉一个或多个进程,这样就能腾出一些内存给其它进程使用。为了让内核判断是否Memory Overcommit,Linux 设定了一个阈值 CommitLimit(若所有进程申请的总内存超过了 CommitLimit 阈值就属于Overcommit了)。可以在通过查阅/proc/meminfo文件获取 CommitLimit 的大小

yanfa204_ubuntu14-nas-18115:jdk8-181009:~$ cat /proc/meminfo | grep CommitLimit
CommitLimit: 65957864 kB

内核CommitLimit计算公式为:

//vm.overcommit_ratio为内核参数
CommitLimit = [swap size] + [RAM size] * vm.overcommit_ratio / 100

3、OOM Kill

在Linux中内存是以页面为最小单位分配的,当物理内存和交换空间不够用时,OOM Killer 就会按照一定的算法选择杀死符合条件的进程, Linux 的每个进程都有一个 oom_score (位于/proc//oom_score),这个值越大,就越有可能被 OOM Killer 选中oom_score 的值是由很多因素共同决定的:

  • 如果进程消耗的内存越大,它的 oom_score 通常也会越大。
  • 如果进程运行了很长时间,并且消耗很多 CPU 时间,那么通常它的 oom_score 会偏小。
  • 如果进程以 superuser 的身份运行,那么它的 oom_score 也会偏小。

Linux 每个进程都有一个 oom_adj (位于/proc//oom_adj),这个值的范围是 [-17, +15],进程的 oom_adj 会影响 oom_score 的计算,即我们可以通过调小进程的 oom_adj 从而降低进程的 oom_score从而降低其被OOM Killer杀死的概率。因此想要尽量避免某个进程被 OOM Killer 杀死,就可以调低它的 oom_adj 的值。

//降低 mysql 进程的oom_adj值
$ sudo echo -10 > /proc/$(pidof mysqld)/oom_adj

4、交换空间

通常来说操作系统都会开启交换空间,因为交换空间有以下一些作用:

  • 允许系统将一些长期没有用到的物理页面换出到交换空间,这样就能节省物理内存的使用。
  • 当物理内存不够使用时,系统可以利用交换空间作为缓冲,防止一些进程因为内存不够而被 OOM Killer 杀死。

vm.swppiness可以用来配置交换空间,取值范围是 [0, 100],在 Linux 3.5 之后,它有这些作用:

  • 设置为 0 表示禁止交换空间的使用,只有当系统 OOM 时才允许使用交换空间。
  • 设置为 1 不会禁止交换空间的使用,但系统会尽量不去使用交换空间。
  • 设置为 100 表示系统会很喜欢使用交换空间。

交换空间是位于磁盘之上的,对操作系统来说,访问磁盘的速度远远慢于访问物理内存。所以我们希望,当物理内存足够使用时,系统能尽量不去使用交换空间,这样能降低页面换入换出的频率,因为频繁的页面换入换出操作会严重影响系统的性能。为了达到这种效果,我们可以把vm.swappiness设置为 1:

sudo echo 1 >  /proc/sys/vm/swappiness

二、Android Low Memory Killer

Linux OOM Killer通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为bad进程,杀死并释放内存且OOM Killer只有当系统内存不足的时候才会启动检查,而Low Memory iller 则是定时进行检查。

1、Low Memory Killer 概述

Android的Low Memory Killer基于Linux的OOM机制,通过在用户空间中指定了一组内存临界值,定时按照配置的策略检查判断进程oom_adj,当其中的某个值与进程描述中的oom_adj值在同一范围时,该进程将被Kill掉Low Memory Killer通过检查oom_adj占用的内存大小来杀死不必要的进程释放其内存。oom_adj 代表进程的优先级,数值越高,优先级越低,越容易被杀死

oom_adj的大小和进程的类型以及进程被调度的次序有关。

Android Kernel每隔一段时间会检测当前空闲内存是否低于某个阀值,假如是则杀死oom_adj最大的不必要的进程;如果有多个,就根据 oom_score_adj 去杀死进程,直到内存恢复低于阀值的状态。

2、lmkd 守护进程

Low Memory Killer 的守护进程是随着系统的启动而启动的lmkd进程。实现源码要在/system/core/lmkd/lmkd.clmkd会创建名为lmkd的socket,节点位于/dev/socket/lmkd,该socket用于跟上层framework交互。

service lmkd /system/bin/lmkd
    class core
    critical
    socket lmkd seqpacket 0660 system system
    writepid /dev/cpuset/system-background/tasks 

2.1、lmkd参数

  • oom_adj:代表进程的优先级, 数值越大,优先级越低,越容易被杀. 取值范围[-16, 15]
  • oom_score_adj: 取值范围[-1000, 1000]
  • oom_score:linux oom才会使用。

2.2、lmkd 会接收 Framework 的命令,进行相应的操作

功能命令对应方法
LMK_PROCPRIO设置进程adjPL.setOomAdj()
LMK_TARGET更新oom_adjPL.updateOomLevels()
LMK_PROCREMOVE移除进程PL.remove()

2.3、lmkd socket 命令处理

static void ctrl_command_handler(void) 
    int ibuf[CTRL_PACKET_MAX / sizeof(int)];
    int len;
    int cmd = -1;
    int nargs;
    int targets;
    len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX);
    if (len <= 0)
        return;
    nargs = len / sizeof(int) - 1;
    if (nargs < 0)
        goto wronglen;
    //将网络字节顺序转换为主机字节顺序
    cmd = ntohl(ibuf[0]);
    switch(cmd) 
    case LMK_TARGET:
        targets = nargs / 2;
        if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj))
            goto wronglen;
        cmd_target(targets, &ibuf[1]);
        break;
    case LMK_PROCPRIO:
        if (nargs != 3)
            goto wronglen;
        //设置进程adj
        cmd_procprio(ntohl(ibuf[1]), ntohl(ibuf[2]), ntohl(ibuf[3]));
        break;
    case LMK_PROCREMOVE:
        if (nargs != 1)
            goto wronglen;
        cmd_procremove(ntohl(ibuf[1]));
        break;
    default:
        ALOGE("Received unknown command code %d", cmd);
        return;
    
    return;
wronglen:
    ALOGE("Wrong control socket read length cmd=%d len=%d", cmd, len);

2.4、设置进程 adj

static void cmd_procprio(int pid, int uid, int oomadj) 
    struct proc *procp;
    char path[80];
    char val[20];
    ...
    snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid);
    snprintf(val, sizeof(val), "%d", oomadj);
    // 向节点/proc/<pid>/oom_score_adj写入oomadj
    writefilestring(path, val);

    // 当使用kernel方式则直接返回
    if (use_inkernel_interface)
        return;
    procp = pid_lookup(pid);
    if (!procp) 
            procp = malloc(sizeof(struct proc));
            if (!procp) 
                // Oh, the irony.  May need to rebuild our state.
                return;
            
            procp->pid = pid;
            procp->uid = uid;
            procp->oomadj = oomadj;
            proc_insert(procp);
     else 
        proc_unslot(procp);
        procp->oomadj = oomadj;
        proc_slot(procp);
    

向节点/proc/oom_score_adj写入oom_adj。

2.5、计算lowmem_oom_adj_to_oom_score_adj

  • 当oom_adj = 15, 则 oom_score_adj = 1000;
  • 当oom_adj < 15, 则 oom_score_adj = oom_adj * 1000/17;
static int lowmem_oom_adj_to_oom_score_adj(int oom_adj)

    if (oom_adj == OOM_ADJUST_MAX)
        return OOM_SCORE_ADJ_MAX;
    else
        return (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;

3、oom_odj 和oom_score_odj

Low Memory Killer Driver在用户空间通过两个文件保存LowMemoryKiller 的阈值的设定:

  • /sys/module/lowmemorykiller/parameters/minfree——保存着对应的内存阀值,minfree中数值代表的是内存中的页面数量(一个页面是4KB)。

  • /sys/module/lowmemorykiller/parameters/adj——代表系统将要杀掉进程对应的oom_score_adj值。

通过这两个文件指定了一组内存临界值及与之一一对应的一组oom_adj值的映射关系,可以通过修改**/sys/module/lowmemorykiller/parameters/minfree/sys/module/lowmemorykiller/parameters/adj来改变内存临界值及与之对应的oom_adj值**来定义Low Memory Killer 策略。

2,8写入节点/sys/module/lowmemorykiller/parameters/adj,再将2048,8192写入节点/sys/module/lowmemorykiller/parameters/minfree。那么相对于是告诉当系统可用内存低于8192个pages时可以杀掉oom_adj>=8的进程;当系统可用内存低于2048个pages时杀oom_adj>=2的进程。

由Low Memory Killer去指定的,对应的实现代码在**/kernel/drivers/staging/android/lowmemorykiller.c**里

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;

也可以通过init.rc自定义,比如init.rc中定义了init进程的oom_adj为-16,不可能会被杀死(init的PID是1):

on early-init
# Set init and its forked children’s oom_adj.
​ write /proc/1/oom_adj -16

以上的含义表示当一个进程的空闲空间下降到3 x 512个页面时,oom_adj值为0或者更大的进程会被Kill掉;当一个进程的空闲空间下降到2 x 1024个页面时,oom_adj值为10或者更大的进程会被Kill掉,依此类推。进程描述符中的signal_struct->oom_adj表示当内存短缺时进程被选择并Kill的优先级,取值范围是-17~15。如果是-17,则表示不会被选中,值越大越可能被选中。当某个进程被选中后,内核会发送SIGKILL信号将其Kill掉。实际上Low Memory Killer驱动程序会认为被用于缓存的存储空间都要被释放,但是当正常的OOM Killer被触发之前,进程是不会被Kill掉的。

4、 LowMemoryKiller Kernel driver

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;

首先确定我们所定义的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”进程。

5、LowMemoryKiller 和 shrinker

shrinker是linux kernel标准的回收内存page的机制,由内核线程kswapd负责监控。当内存不足时kswapd线程会遍历一张shrinker链表,并回调已注册的shrinker函数来回收内存page,kswapd还会周期性唤醒来执行内存操作,shrinker 链表定义如下:

struct shrinker
    int (*shrink)(int nr_to_scan, gfp_t gfp_mask);
    int seeks;
    struct list_head list;
    long nr;
;
#define DEFAULT_SEEKS 2
extern void register_shrinker(struct shrinker *);
extern void unregiter_shrinker(struct shrinker *);

Linux里存在一个名为kswapd的内核线程,当Linux回收存放分页的时候,kswapd线程将会遍历一张shrinker链表并执行回调。

通过register_shrinker与unregister_shrinker向shrinker链表中添加或移除回调。当注册shrinker后就可以在回收内存分页时按自己定义的规则释放内存,其中lowmem_shrink为回调函数的指针。当有内存分页回收的时候,这个函数将会被回调。Android Low Memory Killer的实现在/kernel/drivers/staging/android/lowmemorykiller.c中,通过以下代码在模块初始化时注册shrinker,

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask);
 
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);


module_init(lowmem_init);
module_exit(lowmem_exit);

模块加载和退出的函数,主要的功能就是register_shrinker和unregister_shrinker结构体lowmem_shrinker。主要是将函数lowmem_shrink注册到shrinker链表里,在mm_scan调用。

4.1、lowmem_shrink 触发 shrink 操作

  • 选择oom_score_adj最大的进程中,并且rss内存最大的进程作为选中要杀的进程。

  • 杀进程方式:send_sig(SIGKILL, selected, 0)向选中的目标进程发送signal 9来杀掉目标进程。

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 selected_oom_adj;
        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;
        
        selected_oom_adj = min_adj;

        read_lock(&tasklist_lock);
        for_each_process(p) 
                struct mm_struct *mm;
                int oom_adj;

                task_lock(p);
                mm = p->mm;
                if (!mm) 
                        task_unlock(p);
                        continue;
                
                oom_adj = mm->oom_adj;
                if (oom_adj < min_adj) 
                        task_unlock(p);
                        continue;
                
                tasksize = get_mm_rss(mm);
                task_unlock(p);
                if (tasksize <= 0)
                 

以上是关于Android 进阶—— Framework 核心之 Low Memory Killer机制和进程优先级小结的主要内容,如果未能解决你的问题,请参考以下文章

Android 进阶——系统启动之Framework 核心ActivitityManagerService服务启动

Android 进阶——Framework核心 之Binder Java成员类详解

Android 进阶——Framework核心 之Binder Java成员类详解

Android 进阶——Framework 核心之Touch事件分发机制详细攻略

Android 进阶——Framework 核心之Touch事件分发机制详细攻略

Android 进阶——Framework 核心之Touch事件分发机制详细攻略