一、概述
Linux系统当可用内存较低的时候oom killer机制会根据一定的规则去杀掉一些进程来释放内存,而Android系统的LowMemoryKiller机制就是以此功能为基础做了一些调整。Android系统中的APP在使用完成之后并不会马上被杀掉,而是驻留在内存中,当下一次在此进入此应用的时候可以省去进程创建的过程,加快启动速度。LowMemoryKiller机制会在内存资源紧张的时候,杀掉一些进程来回收内存。
二、整体架构
1.AMS部分的ProcessList
2.Native进程lmkd
3.内核中的LowMemoryKiller部分
Framework中的ProcessList和Native的lmkd进程通过Socket进行进程间通信,而lmkd和内核中的LowMemoryKiller通过writeFileString向文件节点写内容方法进行通信
Framework层通过一定的规则调整进程的adj的值和内存空间阀值,然后通过socket发送给lmkd进程
lmkd两种处理方式:
一种将阀值写入文件节点发送给内核的LowMemoryKiller,由内核进行杀进程处理
另一种是lmkd通过cgroup监控内存使用情况,自行计算杀掉进程。
三、lmkd的启动和初始化
lmkd是一个native进程,由init进程启动,定义在/system/core/lmkd/lmkd.rc中
service lmkd /system/bin/lmkd
class core
group root readproc
critical
socket lmkd seqpacket 0660 system system
writepid /dev/cpuset/system-background/tasks
在lmkd.rc中,启动了lmkd进程,并创建了一个名为lmkd的socket的描述符,用于socket进程间通信。lmkd启动后首先执行main方法。
int main(int argc __unused, char **argv __unused) {
struct sched_param param = {
.sched_priority = 1,
};
sched_setscheduler(0, SCHED_FIFO, ¶m);
if (!init())
mainloop();
}
main方法首先设置了当前进程的调度规则,然后执行了init方法和mainLoop方法。
static int init(void) {
struct epoll_event epev;
int i;
int ret;
//获取当前系统的页大小,单位kb
page_k = sysconf(_SC_PAGESIZE);
if (page_k == -1)
page_k = PAGE_SIZE;
page_k /= 1024;
// 创建一个epollfd描述符
epollfd = epoll_create(MAX_EPOLL_EVENTS);
if (epollfd == -1) {
ALOGE("epoll_create failed (errno=%d)", errno);
return -1;
}
// mark data connections as not connected
for (int i = 0; i < MAX_DATA_CONN; i++) {
data_sock[i].sock = -1;
}
// 获取init.rc中创建的lmkd socket描述符
ctrl_sock.sock = android_get_control_socket("lmkd");
if (ctrl_sock.sock < 0) {
ALOGE("get lmkd control socket failed");
return -1;
}
// 监听socket的连接,即ProcessList的Socket连接
ret = listen(ctrl_sock.sock, MAX_DATA_CONN);
if (ret < 0) {
ALOGE("lmkd control socket listen failed (errno=%d)", errno);
return -1;
}
// Epoll 设置监听socket中的可读事件,当有可读事件的时候回调ctrl_connect_hander方法
//处理socket连接过程
epev.events = EPOLLIN;
ctrl_sock.handler_info.handler = ctrl_connect_handler;
epev.data.ptr = (void *)&(ctrl_sock.handler_info);
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_sock.sock, &epev) == -1) {
ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno);
return -1;
}
maxevents++;
//检测内核是否支持lowMemoryKiller机制
has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
use_inkernel_interface = has_inkernel_module;
//如果内核不支持LowMemoryKiller,则调用init_mp_common初始化,在lmkd中实现进程查杀过程
if (use_inkernel_interface) {
ALOGI("Using in-kernel low memory killer interface");
} else {
if (!init_mp_common(VMPRESS_LEVEL_LOW) ||
!init_mp_common(VMPRESS_LEVEL_MEDIUM) ||
!init_mp_common(VMPRESS_LEVEL_CRITICAL)) {
ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");
return -1;
}
}
//初始化lmkd中的进程列表
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;
}
lmkd的init方法中做的工作
1.获取lmkd的socket描述符
2.创建epoll来监听socket的连接,如果有连接则回调ctrl_connect_handler方法来处理。
3.检测是否有minfree接口,即内核是否支持lowmemorykiller,如果内核不支持则调用init_mp_common初始化,在lmkd中实现进程查杀。
我们先分析内核实现的LowMemoryKiller进程查杀机制, 然后再分析lmkd实现的机制。两者最终的结果都是在内存紧张的时候杀死一些进程来释放内存, 但是实现机制去不太一样。
static void mainloop(void) {
struct event_handler_info* handler_info;
struct epoll_event *evt;
//循环等待epoll事件的上报
while (1) {
struct epoll_event events[maxevents];
int nevents;
int i;
nevents = epoll_wait(epollfd, events, maxevents, -1);
if (nevents == -1) {
if (errno == EINTR)
continue;
ALOGE("epoll_wait failed (errno=%d)", errno);
continue;
}
//获取到对应的epoll事件,分发给对应的handler处理
for (i = 0, evt = &events[0]; i < nevents; ++i, evt++) {
if (evt->events & EPOLLERR)
ALOGD("EPOLLERR on event #%d", i);
if (evt->events & EPOLLHUP) {
/* This case was handled in the first pass */
continue;
}
if (evt->data.ptr) {
handler_info = (struct event_handler_info*)evt->data.ptr;
handler_info->handler(handler_info->data, evt->events);
}
}
}
}
init执行初始化完成之后, 进入mainloop方法,循环等待epoll事件的上报,init的时候epoll监听的socket连接, 当有socket连接的时候就会调用ctrl_connect_handler方法。
static void ctrl_connect_handler(int data __unused, uint32_t events __unused) {
struct epoll_event epev;
int free_dscock_idx = get_free_dsock();
if (free_dscock_idx < 0) {
/*
* Number of data connections exceeded max supported. This should not
* happen but if it does we drop all existing connections and accept
* the new one. This prevents inactive connections from monopolizing
* data socket and if we drop ActivityManager connection it will
* immediately reconnect.
*/
for (int i = 0; i < MAX_DATA_CONN; i++) {
ctrl_data_close(i);
}
free_dscock_idx = 0;
}
//接受framework的socket连接
data_sock[free_dscock_idx].sock = accept(ctrl_sock.sock, NULL, NULL);
if (data_sock[free_dscock_idx].sock < 0) {
ALOGE("lmkd control socket accept failed; errno=%d", errno);
return;
}
ALOGI("lmkd data connection established");
//监听连接的socket通信,当socket有消息的时候会掉ctrl_data_handler方法。
data_sock[free_dscock_idx].handler_info.data = free_dscock_idx;
data_sock[free_dscock_idx].handler_info.handler = ctrl_data_handler;
epev.events = EPOLLIN;
epev.data.ptr = (void *)&(data_sock[free_dscock_idx].handler_info);
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, data_sock[free_dscock_idx].sock, &epev) == -1) {
ALOGE("epoll_ctl for data connection socket failed; errno=%d", errno);
ctrl_data_close(free_dscock_idx);
return;
}
maxevents++;
}
监听到socket连接, 我们知道此时连接lmkd的socket客户端就是framework,当有连接到来的时候accept方法返回连接的socketFD, 然后将连接的socketFD同样加入epoll中, 当socketFD中有可读消息,即framework给lmkd发送消息的时候,epoll唤醒然后会掉ctrl_data_handler方法来处理。
四、Framework和lmkd通信
Framework和lmkd进程通过socket来进行进程间通信,在lmkd初始化的时候,通过监听socket描述符lmkd来等待Framework发送的消息。
Framework向lmkd发送命令相关的方法有三个。
1.AMS.updateConfiguration
更新配置,手机屏幕的尺寸和内存大小不一样,对应的最小内存阀值和adj值也不一样, 最终调用ProcessList的updateOomLevel方法向lmkd发送调整命令
2.AMS.applyOomAdjLocked AMS根据一定的规则调整进程的adj值,最用通过ProcessList的setOomAdj方法发送给lmkd调整命令
3.AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked 进程死亡后,调用ProcessList的remove方法移除进程
上面的三种情况Framework最终是通过socket向lmkd发送了三种消息。
// LMK_TARGET <minfree> <minkillprio> ... (up to 6 pairs)
// LMK_PROCPRIO <pid> <uid> <prio>
// LMK_PROCREMOVE <pid>
//调整minfree和adj的值
static final byte LMK_TARGET = 0;
//设置对应进程的adj值
static final byte LMK_PROCPRIO = 1;
//移除对应进程
static final byte LMK_PROCREMOVE = 2;
lmkd接收命令处理逻辑
static void ctrl_command_handler(int dsock_idx) {
LMKD_CTRL_PACKET packet;
int len;
enum lmk_cmd cmd;
int nargs;
int targets;
//从socket中读取数据
len = ctrl_data_read(dsock_idx, (char *)packet, CTRL_PACKET_MAX_SIZE);
if (len <= 0)
return;
if (len < (int)sizeof(int)) {
ALOGE("Wrong control socket read length len=%d", len);
return;
}
//解析Socket的命令和参数
cmd = lmkd_pack_get_cmd(packet);
nargs = len / sizeof(int) - 1;
if (nargs < 0)
goto wronglen;
switch(cmd) {
case LMK_TARGET:
targets = nargs / 2;
if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj))
goto wronglen;
//调整minfree和adj阀值
cmd_target(targets, packet);
break;
case LMK_PROCPRIO:
if (nargs != 3)
goto wronglen;
//设置对应进程的adj值
cmd_procprio(packet);
break;
case LMK_PROCREMOVE:
if (nargs != 1)
goto wronglen;
//移除对应的进程
cmd_procremove(packet);
break;
default:
ALOGE("Received unknown command code %d", cmd);
return;
}
return;
lmkd通过epoll监听socket中是否有数据, 当接受的framework发送的socket命令之后,调用ctrl_cmmand_handler方法处理,显示解析socket中的命令和参数,根据对于的命令来调用不同的方法处理。
cmd_target 调整最小内存阀值和adj值
cmd_procprio 调整进程的adj值
cmd_procremove 移除对应的进程
对于进程查杀有两种实现方式,一种是内核的LMK,通过shrinker来触发低内存回收, 另一种是lmkd通过cgroup监控内存使用情况,自行计算杀掉进程。两种实现不太一样,需要逐个分析。
五、内核LMK的实现
static void cmd_target(int ntargets, LMKD_CTRL_PACKET packet) {
int i;
struct lmk_target target;
lowmem_targets_size = ntargets;
//使用kernel中的LMK
if (has_inkernel_module) {
char minfreestr[128];
char killpriostr[128];
minfreestr[0] = '\0';
killpriostr[0] = '\0';
//将从framework收到的内存阀值和adj值封装成字符串,以,分隔
//如 18432,23040,27648,32256,55296,80640
//0,100,200,300,900,906
for (i = 0; i < lowmem_targets_size; i++) {
char val[40];
if (i) {
strlcat(minfreestr, ",", sizeof(minfreestr));
strlcat(killpriostr, ",", sizeof(killpriostr));
}
snprintf(val, sizeof(val), "%d", use_inkernel_interface ? lowmem_minfree[i] : 0);
strlcat(minfreestr, val, sizeof(minfreestr));
snprintf(val, sizeof(val), "%d", use_inkernel_interface ? lowmem_adj[i] : 0);
strlcat(killpriostr, val, sizeof(killpriostr));
}
// 将字符串分别写入 /sys/module/lowmemorykiller/parameters/minfree
// 和/sys/module/lowmemorykiller/parameters/adj
writefilestring(INKERNEL_MINFREE_PATH, minfreestr);
writefilestring(INKERNEL_ADJ_PATH, killpriostr);
}
}
设置内存阀值和adj的值就是将从framework收到的数据封装成字符串,通过writefilestring写入到两个文件节点,以供内核LMK使用。
/sys/module/lowmemorykiller/parameters/minfree : 内存级别限额
/sys/module/lowmemorykiller/parameters/adj :内存级别限额对应的要杀掉的进程的adj值.
static void cmd_procprio(LMKD_CTRL_PACKET packet) {
struct proc *procp;
char path[80];
char val[20];
int soft_limit_mult;
struct lmk_procprio params;
//解析进程adj相关的参数
lmkd_pack_get_procprio(packet, ¶ms);
if (params.oomadj < OOM_SCORE_ADJ_MIN ||
params.oomadj > OOM_SCORE_ADJ_MAX) {
ALOGE("Invalid PROCPRIO oomadj argument %d", params.oomadj);
return;
}
//将进程优先级写入到/proc/进程id/oom_score_adj文件中
snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);
snprintf(val, sizeof(val), "%d", params.oomadj);
writefilestring(path, val);
if (use_inkernel_interface)
return;
}
由于使用内核LMK, 所以调整进程优先级直接将优先级写入对应进程的oom_adj_score文件即可。
static void cmd_procremove(LMKD_CTRL_PACKET packet) {
struct lmk_procremove params;
//内核LMK,移除进程什么都不需要做,全部有内核处理
if (use_inkernel_interface)
return;
}
移除进程的时候不需要做任何操作
六 内核LowMemoryKiller的实现原理
在linux中,有一个名为kswapd的内核线程,当linux回收存放分页的时候,kswapd线程将会遍历一张shrinker链表,并执行回调,或者某个app启动,发现可用内存不足时,则内核会阻塞请求分配内存的进程分配内存的过程,并在该进程中去执行lowmemorykiller来释放内存。虽然之前没有接触过,大体的理解就是向系统注册了这个shrinker回调函数之后,当系统空闲内存页面不足时会调用这个回调函数。 struct shrinker的定义在linux/kernel/include/linux/shrinker.h中:
内核LowMemoryKiller shrinker的注册过程如下:
static struct shrinker lowmem_shrinker = {
.scan_objects = lowmem_scan,
.count_objects = lowmem_count,
.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);
}
源链接:http://events.jianshu.io/p/4dbe9bbe0449