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.c
,lmkd
会创建名为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 | 设置进程adj | PL.setOomAdj() |
LMK_TARGET | 更新oom_adj | PL.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事件分发机制详细攻略