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就越大,也就越有可能被选中。
4. 资源配置
阈值表可以通过/sys/module/lowmemorykiller/parameters/adj和/sys/module/lowmemorykiller/parameters/minfree进行配置,例如在init.rc中:
# Write value must be consistent with the above properties.
write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15
write /proc/sys/vm/overcommit_memory 1
write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144
class_start default
进程oom_adj同样可以进行设置,通过write /proc/<PID>/oom_adj ,在init.rc中,init进程的pid为1,omm_adj被配置为-16,永远不会被杀死。
# Set init its forked children's oom_adj.
write /proc/1/oom_adj -16
Low memory killer的基本原理我们应该弄清了,正如我前面所说的,进程omm_adj的大小跟进程的类型以及进程被调度的次序有关。进程的类型,可以在ActivityManagerService中清楚的看到:
static final int EMPTY_APP_ADJ;
static final int HIDDEN_APP_MAX_ADJ;
static final int HIDDEN_APP_MIN_ADJ;
static final int HOME_APP_ADJ;
static final int BACKUP_APP_ADJ;
static final int SECONDARY_SERVER_ADJ;
static final int HEAVY_WEIGHT_APP_ADJ;
static final int PERCEPTIBLE_APP_ADJ;
static final int VISIBLE_APP_ADJ;
static final int FOREGROUND_APP_ADJ;
static final int CORE_SERVER_ADJ = -12;
static final int SYSTEM_ADJ = -16;
ActivityManagerService定义各种进程的oom_adj,CORE_SERVER_ADJ代表一些核心的服务的omm_adj,数值为-12,由前面的分析可知道,这类进程永远也不会被杀死。
其他未赋值的都在static块中进行了初始化,是通过system/rootdir/init.rc进行配置的:
在init.rc中:
# Define the oom_adj values for the classes of processes that can be
# killed by the kernel. These are used in ActivityManagerService.
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 process classes 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
由此我们知道EMPTY_APP 最容易被杀死,其实是CONTENT_PROVIDER ,FOREGROUND的进程很难被杀死。
现在我们再来说影响oom_adj的第二个因素,进程的调度次序。这涉及到了ActivityManagerService的复杂调度,我们下次再来看吧。
附言:
Android将进程分为6个等级,它们按优先级顺序由高到低依次是:
1. FOREGROUND_APP:
This is the process running the current foreground app. We'd really rather not kill it!
用户正在使用的程序. 这个设的太高,用户看到得就会是一个正在使用的程序莫名其妙的消失了,然后自动回到桌面..(因为它被系统kill了..) 所以最好别动它.
2. VISIBLE_APP:
This is a process only hosting activities that are visible to the user, so we'd prefer they don't disappear.
跟FOREGROUND_APP类似,用户正在使用/看得到. 它们的区别就是VISIBLE_APP可能不是用户focus的程序,但是用户看得到,或者没有覆盖到整个屏幕,只有屏幕的一部分. 所以可以适当的比FOREGROUND_APP高一点.
3. SECONDARY_SERVER:
This is a process holding a secondary server -- killing it will not have much of an impact as far as the user is concerned.
所有应用的service. 系统级的service比如PhoneService不属于这类,它们是绝不会被Android结束掉的. 所以这个可以适当的设高一点点~ 注意, HOME(SenseUI)也包括在这里 因此还是别设的太高. 要不每次返回桌面都得等它重新load,特别是widget多的.
4. HIDDEN_APP:
This is a process only hosting activities that are not visible, so it can be killed without any disruption.
本来属于1或者2的程序, 在用户按了"back"或者"home"后,程序本身看不到了,但是其实还在运行的程序,它们就属于HIDDEN_APP了. 干掉没什么影响.. 不过要了解并不是所有属于这一类的就应该马上结束掉,像push mail,locale,闹钟,等都属于这一类. 因此还是别设的过高. 真正"应该"一点返回键就退出的程序(真正没用的程序)在下面.
5. CONTENT_PROVIDER:
This is a process with a content provider that does not have any clients attached to it. If it did have any clients, its adjustment would be the one for the highest-priority of those processes.
5,6的区别具体不太了解..这个也是用处不大,但是还是比EMPTY_APP稍微有点用.. 所以高点没关系~,
6. EMPTY_APP:
This is a process without anything currently running in it. Definitely the first to go! This value is initalized in the constructor, careful when refering to this static variable externally.
完全没用的一个,杀了它只有好处没坏处,第一个干它!
The configuration is set in the file: "/sys/module/lowmemorykiller/parameters/minfree"
查看现在的设置可以:
1. # cat /sys/module/lowmemorykiller/parameters/minfree
显示出的应该是6个数字,以逗号隔开,例如:
1536,2048,4096,5120,5632,6144
注意这些数字的单位是page. 1 page = 4 kilobyte.
上面的六个数字对应的就是(MB): 6,8,16,20,22,24
这些数字也就是对应的内存阀值,一旦低于该值,Android便开始按顺序关闭进程. 因此Android开始结束优先级最低的EMPTY_APP当可用内存小于24MB(6144*4/1024).
有一点没搞明白,它的可用内存不知道是从哪得到.. 明显不是free显示的可用内存,而且貌似compcache跟swap也不影响.
要想重新设置该值:
1. # echo "1536,2048,4096,5120,15360,23040" > /sys/module/lowmemorykiller/parameters/minfree;
这样当可用内存低于90MB的时候便开始结束EMPTY_APP. 而当可用内存低于60MB的时候才开始结束CONTENT_PROVIDER组. 其余四个没动.
注意:
通过以上方法改变的数值并非永久.在下次重启后就又恢复到之前的设置. 若想让设置在每次开机执行,将;
echo "1536,3072,4096,21000,23000,25000" > /sys/module/lowmemorykiller/parameters/minfree
加入到任意一个开机启动的配置文件. 一般在/system/init.d下的文件都是开机执行的(有的ROM也可能不在这里..) 只需用记事本打开任意一个文件,再把这行加入其中就好.
个人经验补充一点:
一般前三个不用动,或者只是稍微改高一点就好,毕竟它们比较重要,不要太早结束. 值得关注的是后三个. 它们也可以适当的设大一点. 当然,任何东西都有一个度,像上面例子中EMPTY_APP搞到25000(97MB)并不至于大到离谱,hero我没有过 我在Magic 32A甚至还试过120MB..不过120MB那是有点过头了..游戏玩着玩着就消失回到桌面了(可用内存低于120MB被系统结束任务了).. 因此还是不要太高.
我目前的设置(开了32MB的CC加32MB的backing swap @60 swappiness):
1536,2048,4096,8192,11520,19200
也许大家还不明白这样做的好处. 这样的好处就是让我们随时有足够的内存来执行我们要运行的程序,而那些真正没用的进程又不会多余的占用着宝贵的内存. 更重要的是这一切都是不需要您的参与或任何第三方软件的协助,完全由Android在后台自动执行. 试想,又有谁会比Android更熟悉的掌握每个进程呢 比起那些内存管理程序傻傻的一锅端的方法聪明多了吧~ 让我们从现在开始把那些内存管理程序删掉吧。