Android在内存管理上与linux有些小的区别。其中一个就是引入了Low memory killer .
1,引入原因
Android是一个多任务系统,也就是说可以同时运行多个程序,这个大家应该很熟悉。一般来说,启动运行一个程序是有一定的时间开销的,因此为了加快运行速度,当你退出一个程序时,Android并不会立即杀掉它,这样下次再运行该程序时,可以很快的启动。随着系统中保留的程序越来越多,内存肯定会出现不足,low memory killer就是在系统内存低于某值时,清除相关的程序,保障系统保持拥有一定数量的空闲内存。
Android中,进程的生命周期都是由系统控制的,即使用户关掉了程序,进程依然是存在于内存之中。这样设计的目的是为了下次能快速启动。当然,随着系统运行时间的增长,内存会越来越少。Android Kernel 会定时执行一次检查,杀死一些进程,释放掉内存。
那么,如何来判断,那些进程是需要杀死的呢?答案就是我们的标题:Low memory killer机制。
Android 的Low memory killer是基于linux的OOM(out of memory) 规则改进而来的。 OOM通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为bad进程,杀死并释放内存。OOM只有当系统内存不足的时候才会启动检查,而Low memory killer 则是定时进行检查。
Low memory killer 主要是通过进程的oom_adj 来判定进程的重要程度。oom_adj的大小和进程的类型以及进程被调度的次序有关。
Low memory killer 的具体实现可参看:kernel/drivers/misc/lowmemorykiller.c
其原理很简单,在linux中,存在一个kswapd的内核线程,当linux回收存放分页的时候,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 *);
所以只要注册 Shrinker,变可以在内存分页回收时根据规则释放内存,下面我们来看看其实现。
首先定义shrinker结构体,lowmem_shrink为回调函数的指针,当有内存分页回收的时候,这个函数将会被调用。
static struct shrinker lowmem_shrinker = {
.shrink = lowmem_shrink,
.seeks = DEFAULT_SEEKS * 16
};
2. 基本原理和重要概念
Low memory killer根据两个原则,进程的重要性和释放这个进程可获取的空闲内存数量,来决定释放的进程。
(1)进程的重要性,由task_struct->signal_struct->oom_adj决定。
Android将程序分成以下几类,按照重要性依次降低的顺序:
名称 oom_adj 解释
FOREGROUD_APP 0 前台程序,可以理解为你正在使用的程序
VISIBLE_APP 1 用户可见的程序
SECONDARY_SERVER 2 后台服务,比如说QQ会在后台运行服务
HOME_APP 4 HOME,就是主界面
HIDDEN_APP 7 被隐藏的程序
CONTENT_PROVIDER 14 内容提供者,
EMPTY_APP 15 空程序,既不提供服务,也不提供内容
其中每个程序都会有一个oom_adj值,这个值越小,程序越重要,被杀的可能性越低。
(2)进程的内存,通过get_mm_rss获取,在相同的oom_adj下,内存大的,优先被杀。
(3)那内存低到什么情况下,low memory killer开始干活呢?Android提供了两个数组,一个lowmem_adj,一个lowmem_minfree。前者存放着oom_adj的阀值,后者存放着minfree的警戒值,以page为单位(4K)。
oom_adj 内存警戒值( 以4K为单位)
0 1536
1 2048
2 4096
7 5120
14 5632
15 6144
3.源码解析
module_init(lowmem_init);
module_exit(lowmem_exit);
模块加载和退出的函数,主要的功能就是register_shrinker和unregister_shrinker结构体lowmem_shrinker。主要是将函数lowmem_shrink注册到shrinker链表里,在mm_scan调用。
下面详细的介绍这个函数:
for (i = 0; i < array_size; i++) {
if (other_file < lowmem_minfree[i]) {
min_adj = lowmem_adj[i];
break;
}
}
other_file,系统的空闲内存数,根据上面的逻辑判断出,low memory killer需要对adj高于多少(min_adj)的进程进行分析是否释放。
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;
}
判断,系统当前的状态是否需要进行low memory killer。
for_each_process(p) {
struct mm_struct *mm;
struct signal_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);
}
对每个sig->oom_adj大于min_adj的进程,找到占用内存最大的进程存放在selected中。
if (selected) {
if (fatal_signal_pending(selected)) {
pr_warning("process %d is suffering a slow death\n",
selected->pid);
read_unlock(&tasklist_lock);
return rem;
}
lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",
selected->pid, selected->comm,
selected_oom_adj, selected_tasksize);
force_sig(SIGKILL, selected);
rem -= selected_tasksize;
}
发送SIGKILL信息,杀掉该进程。
关于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://my.oschina.net/wolfcs/blog/288259
http://www.cnblogs.com/angeldevil/archive/2013/05/21/3090872.html