android的低内存管理器

来源:互联网 发布:csol for mac 编辑:程序博客网 时间:2024/04/28 22:36

安卓应用不用太在意内存回收,首先android上的应用是带有独立java虚拟机的,也就是每开一个应用就会打开一个独立的虚拟机,虚拟机有自己的一套规则回收内存;同时和java的垃圾回收机制类似,android系统有一个规则来主动回收内存。进行内存回收有个阀值,只有低于这个值系统才会按一个列表来关闭用户不需要的东西。

下面介绍android内核的低内存管理,由于上述的这个阈值存在,所以我们会看到android内存使用比例并不高。事实上高剩余内存并不影响速度,相反加快了下次启动应用的速度。这本来就是 android标榜的优势之一,如果人为去关闭进程,没有太大必要,用户体验也不好。比Linux的标准的OOM(Out Of Memory)机制更加灵活,它可以根据需要杀死进程以释放需要的内存 。源代码 位 于 drivers/staging/android/lowmemorykiller.c。

Linux使用OOM(Out Of Memory,内存不足)的机制来完成这个任务,该机制会在系统内存不足的情况下,选择一个进程并将其 Kill 掉。Android则使用了一个新的机制——Low Memory  Killer 来完成同样的任务。下面首先来看看 Low  MemoryKiller 机制的原理以及它是如何选择将被 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 掉,依此类推。

进程的oom_adj值是由上层决定的,Android将进程分为6个等级,分别对应不同的lowmem_adj,它们按优先级顺序由高到低依次是:
     1前台进程(foreground)

 目前正在屏幕上显示的进程和一些系统进程。举例来说,Dialer,Storage,Google Search等系统进程就是前台进程;再举例来说,当你运行一个程序,如浏览器,当浏览器界面在前台显示时,浏览器属于前台进程(foreground),但一旦你按home回到主界面,浏览器就变成了后台程序(background)。我们最不希望终止的进程就是前台进程。

2可见进程(visible)

 可见进程是一些不再前台,但用户依然可见的进程,举个例来说:widget、输入法等,都属于visible。这部分进程虽然不在前台,但与我们的使用也密切相关,我们也不希望它们被终止(还比如时钟、天气,新闻等widget用户肯定不希望被终止,那它们将无法同步,你也不希望输入法被终止,否则你每次输入时都需要重新启动输入法)。

3桌面进程(home app)

 即launcher(桌面启动器),保证在多任务切换之后,可以快速返回到home界面而不需重新加载launcher。

4次要服务(secondary server)

 目前正在运行的一些服务(主要服务,如拨号等,是不可能被进程管理终止的,故这里只谈次要服务),举例来说:谷歌企业套件,Gmail内部存储,联系人内部存储等。这部分服务虽然属于次要服务,但很一些系统功能依然息息相关,我们时常需要用到它们,所以也不太希望他们被终止。

5后台进程(hidden)

 即是后台进程(background),就是我们通常意义上理解的启动后被切换到后台的进程,如浏览器,阅读器等。当程序显示在屏幕上时,他所运行的进程即为前台进程(foreground),一旦我们按home返回主界面(注意是按home,不是按back),程序就驻留在后台,成为后台进程(background)。

后台进程的管理策略有多种:有较为积极的方式,一旦程序到达后台立即终止,这种方式会提高程序的运行速度,但无法加速程序的再次启动;也有较消极的方式,尽可能多的保留后台程序,虽然可能会影响到单个程序的运行速度,但在再次启动已启动的程序时,速度会有所提升。这里就需要用户根据自己的使用习惯找到一个平衡点。

6内容供应节点(content provider)

没有程序实体,仅提供内容供别的程序去用的,比如日历供应节点,邮件供应节点等。在终止进程时,这类程序应该优先杀死。

init.rc(android上层代码)中对adj与minfree默认值进行设置,在内核里分别对应lowmem_adj和lowmem_minfree

# Define the oom_adj values for the classes of processesthat can be

# killed by the kernel.  These are used inActivityManagerService.

   setprop ro.FOREGROUND_APP_ADJ 0

   setprop ro.VISIBLE_APP_ADJ 1

   setprop ro.SECONDARY_SERVER_ADJ 2

   setprop ro.HIDDEN_APP_MIN_ADJ 7

   setprop ro.CONTENT_PROVIDER_ADJ 14

   setprop ro.EMPTY_APP_ADJ 15

 

# Define the memory thresholds at which the above processclasses will

# be killed.  These numbers are in pages (4k).

   setprop ro.FOREGROUND_APP_MEM 1536

   setprop ro.VISIBLE_APP_MEM 2048

   setprop ro.SECONDARY_SERVER_MEM 4096

   setprop ro.HIDDEN_APP_MEM 5120

   setprop ro.CONTENT_PROVIDER_MEM 5632

   setprop ro.EMPTY_APP_MEM 6144

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

2.Low Memory Killer 的具体实现

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

该驱动的实现比较简单,在 初 始 化 函 数  lowmem_init   中 通 过  register_shrinker   注 册 了 一 个shrinker为lowmem_shrinker:

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_exit,通过 unregister_shrinker来卸载被注册的lowmem_shrinker。其中lowmem_shrinker的定义如下:

static struct shrinker lowmem_shrinker = {

.shrink = lowmem_shrink,

.seeks = DEFAULT_SEEKS * 16

};

lowmem_shrink 是这个驱动的核心实现,当内存不足时就会调用 lowmem_shrink 方法来 Kill掉某些进程shrink_slab -> do_shrinker_shrink->lowmem_shrink。kswapd线程将会遍历一张shrinker链表,下面来分析其具体实现,实现代码如下:

对每个满足sig->oom_adj大于min_adj的进程,找到占用内存最大的进程存放在selected中:

static int lowmem_shrink(struct shrinker *s, structshrink_control *sc)

{

struct task_struct*p;

struct task_struct*selected = NULL;

int rem = 0;

int tasksize;

int i;

int min_adj =OOM_ADJUST_MAX + 1;

intselected_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) -

                     global_page_state(NR_SHMEM);

 

/*

 * If we already have a death outstanding, then

 * bail out right away; indicating to vmscan

 * that we have nothing further to offer on

 * this pass.

 *

 */

if(lowmem_deathpending &&

    time_before_eq(jiffies,lowmem_deathpending_timeout))

     return 0;

 

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(sc->nr_to_scan > 0)

     lowmem_print(3,"lowmem_shrink %lu, %x, ofree %d %d, ma %d\n",

              sc->nr_to_scan, sc->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(sc->nr_to_scan <= 0 || min_adj == OOM_ADJUST_MAX + 1) {

     lowmem_print(5,"lowmem_shrink %lu, %x, return %d\n",

              sc->nr_to_scan, sc->gfp_mask, rem);

     return rem;

}

selected_oom_adj =min_adj;

 

read_lock(&tasklist_lock);

for_each_process(p){

     structmm_struct *mm;

     structsignal_struct *sig;

     int oom_adj;

 

     task_lock(p);

     mm = p->mm;

     sig =p->signal;

     if (!mm ||!sig) {

         task_unlock(p);

         continue;

     }

     oom_adj =sig->oom_adj;

     if (oom_adj< min_adj) {

         task_unlock(p);

         continue;

     }

    tasksize = get_mm_rss(mm);

     task_unlock(p);

     if (tasksize<= 0)

         continue;

     if (selected) {

         if (oom_adj< selected_oom_adj)

             continue;

         if (oom_adj== selected_oom_adj &&

            tasksize <= selected_tasksize)

             continue;

     }

     selected = p;

     selected_tasksize= tasksize;

     selected_oom_adj= oom_adj;

     lowmem_print(2,"select %d (%s), adj %d, size %d, to kill\n",

              p->pid, p->comm, oom_adj, tasksize);

}

if (selected) {

     lowmem_print(1,"send sigkill to %d (%s), adj %d, size %d\n",

              selected->pid, selected->comm,

              selected_oom_adj, selected_tasksize);

     lowmem_deathpending= selected;

     lowmem_deathpending_timeout= jiffies + HZ;

     force_sig(SIGKILL,selected);

     rem -=selected_tasksize;

}

lowmem_print(4,"lowmem_shrink %lu, %x, return %d\n",

          sc->nr_to_scan, sc->gfp_mask, rem);

read_unlock(&tasklist_lock);

return rem;

}

lowmem_shrink函数首先确定我们所定义的lowmem_adj和 lowmem_minfree 数组的大小(元素个数)是否一致,如果不一致则以最小的为基准。因为我们需要通过比较lowmem_minfree中的空闲储存空间的值,以确定最小min_adj值(当满足其条件时,通过其数组索引来寻找lowmem_adj中对应元素的值);然后使用循环对每一个进程块进行判断,通过min_adj来寻找满足条件的具体进程,并且找到满足条件的tasksize最大的进程;最后如果存在这样的进程,通过“force_sig(SIGKILL, selected)”发送一条SIGKILL信号到内核, Kill  掉被选中的 “selected”进程。

最后简单描述一下标准 Linux 的 OOM Killer 机制。在分 配内存时,即mm/page_alloc.c中内存紧张会调用__alloc_pages_may_oom。oom_kill.c 最主要的一个函数是 out_of_memory,它选择一个bad进程Kill, Kill的方法同样是通过发送SIGKILL信号。在 out_of_memory 中通过调用select_bad_process 来 选择一个进程 Kill,选择的依据在oom_badness函数中实现,2.6.36后内核引入多个标准来给每个进程评分,评分最高的被选中并 Kill。一般而言,占用内存越多,/proc/pid/oom_score_adj越大,非root进程等因素越有可能被选中,可见比android要简单。

0 0
原创粉丝点击