1.概述
Android底层还是基于Linux,在Linux中低内存是会有oom killer去杀掉一些进程去释放内存,而Android中的lowmemorykiller就是在此基础上做了一些调整来的。因为手机上的内存毕竟比较有限,而Android中APP在不使用之后并不是马上被杀掉,虽然上层ActivityManagerService中也有很多关于进程的调度以及杀进程的手段,但是毕竟还需要考虑手机剩余内存的实际情况,
lowmemorykiller的作用就是当内存比较紧张的时候去及时杀掉一些ActivityManagerService还没来得及杀掉但是对用户来说不那么重要的进程,回收一些内存,保证手机的正常运行。
lowmemkiller中会涉及到几个重要的概念:
/sys/module/lowmemorykiller/parameters/minfree
:里面是以”,”分割的一组数,每个数字代表一个内存级别
/sys/module/lowmemorykiller/parameters/adj
:对应上面的一组数,每个数组代表一个进程优先级级别
举个例子:
/sys/module/lowmemorykiller/parameters/minfree
:18432,23040,27648,32256,55296,80640
/sys/module/lowmemorykiller/parameters/adj
:0,100,200,300,900,906
代表的意思:两组数一一对应,当手机内存低于80640时,就去杀掉优先级906以及以上级别的进程,当内存低于55296时,就去杀掉优先级900以及以上的进程。
对每个进程来说:
/proc/pid/oom_adj:代表当前进程的优先级,这个优先级是kernel中的优先级,这个优先级与上层的优先级之间有一个换算,文章最后会提一下。
/proc/pid/oom_score_adj:上层优先级,跟ProcessList中的优先级对应
2.init进程lmkd
代码位置:platform/system/core/lmkd/
ProcessList中定义有进程的优先级,越重要的进程的优先级越低,前台APP的优先级为0,系统APP的优先级一般都是负值,所以一般进程管理以及杀进程都是针对与上层的APP来说的,而这些进程的优先级调整都在AMS里面,AMS根据进程中的组件的状态去不断的计算每个进程的优先级,计算之后,会及时更新到对应进程的文件节点中,而这个对文件节点的更新并不是它完成的,而是lmkd,他们之间通过socket通信。
lmkd在手机中是一个常驻进程,用来处理上层ActivityManager在进行updateOomAdj之后,通过socket与lmkd进行通信,更新进程的优先级,如果必要则杀掉进程释放内存。lmkd是在init进程启动的时候启动的,在lmkd中有定义lmkd.rc:
代码语言:javascript复制service lmkd /system/bin/lmkd
class core
group root readproc
critical
socket lmkd seqpacket 0660 system system
writepid /dev/cpuset/system-background/tasks
上层AMS跟lmkd通信主要分为三种command,每种command代表一种数据控制方式,在ProcessList以及lmkd中都有定义:
代码语言:javascript复制LMK_TARGET:更新/sys/module/lowmemorykiller/parameters/中的minfree以及adj
LMK_PROCPRIO:更新指定进程的优先级,也就是oom_score_adj
LMK_PROCREMOVE:移除进程
在开始介绍lmkd的处理逻辑之前,lmkd.c中有几个重要的变量与数据结构提前说明一下:
代码语言:javascript复制// 内存级别限额
#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree"
// 不同级别内存对应要杀的的优先级
#define INKERNEL_ADJ_PATH "/sys/module/lowmemorykiller/parameters/adj"
// 装载上面两组数字的数组
static int lowmem_adj[MAX_TARGETS];
static int lowmem_minfree[MAX_TARGETS];
// 三种command
enum lmk_cmd {
LMK_TARGET,
LMK_PROCPRIO,
LMK_PROCREMOVE,
};
// 优先级的最小值
#define OOM_SCORE_ADJ_MIN (-1000)
// 优先级最大值
#define OOM_SCORE_ADJ_MAX 1000
// 双向链表结构体
struct adjslot_list {
struct adjslot_list *next;
struct adjslot_list *prev;
};
// 进程在lmkd中的数据结构体
struct proc {
struct adjslot_list asl;
int pid;
uid_t uid;
int oomadj;
struct proc *pidhash_next;
};
// 存放进程proc的hashtable,index是通过pid的计算得出
static struct proc *pidhash[PIDHASH_SZ];
// 根据pid计算index的hash算法
#define pid_hashfn(x) ((((x) >> 8) ^ (x)) & (PIDHASH_SZ - 1))
// 进程优先级到数组的index之间的转换
// 因为进程的优先级可以是负值,但是数组的index不能为负值
// 不过因为这个转换只是简单加了1000,为了方便,后面的描述中就认为是优先级直接做了index
#define ADJTOSLOT(adj) (adj -OOM_SCORE_ADJ_MIN)
// table,类似hashtable,不过计算index的方式不是hash,而是oom_score_adj经过转换后直接作为index
// 数组的每个元素都是双向循环链表
// 进程的优先级作为数组的index
// 即以进程的优先级为index,从-1000到 1000 1大小的数组,根据优先级,同优先级的进程index相同
// 每个元素是一个双向链表,这个链表上的所有proc的优先级都相同
// 这样根据优先级杀进程的时候就会非常方便,要杀指定优先级的进程可以根据优先级获取到一个进程链表,逐个去杀。
static struct adjslot_list procadjslot_list[ADJTOSLOT(OOM_SCORE_ADJ_MAX) 1];
2.1 lmkd进程启动入口
代码语言:javascript复制int main(int argc __unused, char **argv __unused) {
struct sched_param param = {
.sched_priority = 1,
};
// 将此进程未来使用到的所有内存都锁在物理内存中,防止内存被交换
mlockall(MCL_FUTURE);
// 设置此线程的调度策略为SCHED_FIFO,first-in-first-out,param中主要设置sched_priority
// 由于SCHED_FIFO是一种实时调度策略,在这个策略下优先级从1(low) -> 99(high)
// 实时线程通常会比普通线程有更高的优先级
sched_setscheduler(0, SCHED_FIFO, ¶m);
// 初始化epoll以及与ActivityManager的socket连接,等待cmd和data
if (!init())
// 进入死循环epoll_wait等待fd事件
mainloop();
ALOGI("exiting");
return 0;
}
前面已经提到,这个进程存在的主要作用是跟AMS进行通信,更新oomAdj,在必要的时候杀掉进程。所以在main函数中主要就是创建了epoll以及初始化socket并连接ActivityManager,然后阻塞等待上层传递cmd以及数据过来。
2.2 init初始化
代码语言:javascript复制static int init(void) {
...
// 拿到lmkd的socket fd
ctrl_lfd = android_get_control_socket("lmkd");
if (ctrl_lfd < 0) {
ALOGE("get lmkd control socket failed");
return -1;
}
// server listen
ret = listen(ctrl_lfd, 1);
if (ret < 0) {
ALOGE("lmkd control socket listen failed (errno=%d)", errno);
return -1;
}
epev.events = EPOLLIN;
// ctrl_connect_handler里面完成了soclet的accpet以及read数据,并对数据进行相应的处理
epev.data.ptr = (void *)ctrl_connect_handler;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1) {
ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno);
return -1;
}
maxevents ;
// 使用kernel空间的处理
use_inkernel_interface = !access(INKERNEL_MINFREE_PATH, W_OK);
if (use_inkernel_interface) {
ALOGI("Using in-kernel low memory killer interface");
} else {
ret = init_mp(MEMPRESSURE_WATCH_LEVEL, (void *)&mp_event);
if (ret)
ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");
}
// 双向链表初始化
for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i ) {
procadjslot_list[i].next = &procadjslot_list[i];
procadjslot_list[i].prev = &procadjslot_list[i];
}
return 0;
}
在初始化的时候,有一个很重要的判断:use_inkernel_interface,这个是根据是否有/sys/module/lowmemorykiller/parameters/minfree
的写权限来判断的,没有的情况下就使用kernel空间的逻辑
目前遇到的都是use_inkernel_interface
如果use_inkernel_interface的值为false:
2.3 进入loop循环mainloop
代码语言:javascript复制// 进入死循环,然后调用epoll_wait阻塞等待事件的到来
static void mainloop(void) {
while (1) {
struct epoll_event events[maxevents];
int nevents;
int i;
ctrl_dfd_reopened = 0;
nevents = epoll_wait(epollfd, events, maxevents, -1);
if (nevents == -1) {
if (errno == EINTR)
continue;
ALOGE("epoll_wait failed (errno=%d)", errno);
continue;
}
for (i = 0; i < nevents; i) {
if (events[i].events & EPOLLERR)
ALOGD("EPOLLERR on event #%d", i);
if (events[i].data.ptr)
(*(void (*)(uint32_t))events[i].data.ptr)(events[i].events);
}
}
}
2.4 处理socket传递过来的数据ctrl_command_handler
前面在ctrl_connect_handler这个方法中处理了accept,并开始了ctrl_data_handler中读取数据并进行处理:ctrl_command_handler。对于ActivityManager传递来的Command以及data的主要处理逻辑就在ctrl_command_handler中。
代码语言:javascript复制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]);
// 一共三种command,在前面静态变量的定义处已经介绍过
switch(cmd) {
// 更新内存级别以及对应级别的进程adj
case LMK_TARGET:
targets = nargs / 2;
if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj))
goto wronglen;
cmd_target(targets, &ibuf[1]);
break;
// 根据pid更新adj
case LMK_PROCPRIO:
if (nargs != 3)
goto wronglen;
cmd_procprio(ntohl(ibuf[1]), ntohl(ibuf[2]), ntohl(ibuf[3]));
break;
// 根据pid移除proc
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);
}
上层代码的调用时机这里就不细化了,往前追的话基本都是在ActivityManagerService中的udpateOomAdj中,也就是说上层根据四大组件的状态对进程的优先级进行调整之后,会及时的反应到lmkd中,在内存不足的时候触发杀进程,会从低优先级开始杀进程。command一共有三种,在上层的代码是在ProcessList中。
2.4.1 LMK_TARGET
代码语言:javascript复制// 上层逻辑是在ProcessList.updateOomLevels中
ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length 1));
buf.putInt(LMK_TARGET);
for (int i=0; i<mOomAdj.length; i ) {
buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
buf.putInt(mOomAdj[i]);
}
writeLmkd(buf)
// lmkd处理逻辑
static void cmd_target(int ntargets, int *params) {
int i;
if (ntargets > (int)ARRAY_SIZE(lowmem_adj))
return;
// 这个for循环对应上面的for循环,将数据读出装进数组中
for (i = 0; i < ntargets; i ) {
lowmem_minfree[i] = ntohl(*params );
lowmem_adj[i] = ntohl(*params );
}
lowmem_targets_size = ntargets;
// 使用kernel空间的处理逻辑
if (use_inkernel_interface) {
char minfreestr[128];
char killpriostr[128];
minfreestr[0] = '