作者:李尚
Android5.0将设置进程优先级的入口封装成了一个独立的服务lmkd服务,AMS不再直接访问proc文件系统,而是通过lmkd服务来进行设置,从init.rc文件中看到服务的配置。
service lmkd /system/bin/lmkd
class core
critical
socket lmkd seqpacket 0660 system system
从配置中可以看出,该服务是通过socket与其他进行进程进行通信,其实就是AMS通过socket向lmkd服务发送请求,让lmkd去更新进程的优先级,lmkd收到请求后,会通过/proc文件系统去更新内核中的进程优先级。首先看一下5.0中这一块AMS有什么改变,其实大部分流程跟之前4.3源码类似,我们只看一下不同地方
ActivityManagerService
private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
ProcessRecord TOP_APP, boolean doingAll, long now) {
...
computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
...
applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
}
private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
long nowElapsed) {
boolean success = true;
if (app.curRawAdj != app.setRawAdj) {
app.setRawAdj = app.curRawAdj;
}
int changes = 0;
不同点1
if (app.curAdj != app.setAdj) {
ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": "
+ app.adjType);
app.setAdj = app.curAdj;
app.verifiedAdj = ProcessList.INVALID_ADJ;
}
从上面的不同点1可以看出,5.0之后是通过ProcessList类去设置oomAdj,其实这里就是通过socket与LMKD服务进行通信,向lmkd服务传递给LMK_PROCPRIO命令去更新进程优先级:
public static final void setOomAdj(int pid, int uid, int amt) {
if (amt == UNKNOWN_ADJ)
return;
long start = SystemClock.elapsedRealtime();
ByteBuffer buf = ByteBuffer.allocate(4 * 4);
buf.putInt(LMK_PROCPRIO);
buf.putInt(pid);
buf.putInt(uid);
buf.putInt(amt);
writeLmkd(buf);
long now = SystemClock.elapsedRealtime();
}
private static void writeLmkd(ByteBuffer buf) {
for (int i = 0; i < 3; i++) {
if (sLmkdSocket == null) {
if (openLmkdSocket() == false) {
...
try {
sLmkdOutputStream.write(buf.array(), 0, buf.position());
return;
...
}
其实就是openLmkdSocket打开本地socket端口,并将优先级信息发送过去,那么lmkd服务端如何处理的呢,init.rc里配置的服务是在开机时启动的,来看看lmkd服务的入口:main函数
lmkd.c函数
int main(int argc __unused, char **argv __unused) {
struct sched_param param = {
.sched_priority = 1,
};
mlockall(MCL_FUTURE);
sched_setscheduler(0, SCHED_FIFO, ¶m);
if (!init())
mainloop();
ALOGI("exiting");
return 0;
}
很简单,打开一个端口,并通过mainloop监听socket,如果有请求到来,就解析命令并执行,刚才传入的LMK_PROCPRIO命令对应的操作就是cmd_procprio,用来更新oomAdj,其更新新机制还是通过proc文件系统,不信?看下面代码:
static void cmd_procprio(int pid, int uid, int oomadj) {
struct proc *procp;
。。。
还是利用/proc文件系统进行更新
snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid);
snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj));
writefilestring(path, val);
。。。
}
简单的流程图如下,同4.3不同的地方
以上就分析完了用户空间的操作如何影响到进程的优先级,并且将新的优先级写到内核中。最后看一下LomemoryKiller在什么时候、如何根据优先级杀死进程的:
LomemoryKiller属于一个内核驱动模块,主要功能是:在系统内存不足的时候扫描进程队列,找到低优先级(也许说性价比低更合适)的进程并杀死,以达到释放内存的目的。对于驱动程序,入口是__init函数,先看一下这个驱动模块的入口:
static int __init lowmem_init(void)
{
register_shrinker(&lowmem_shrinker);
return 0;
}
可以看到在init的时候,LomemoryKiller将自己的lowmem_shrinker入口注册到系统的内存检测模块去,作用就是在内存不足的时候可以被回调,register_shrinker函数是一属于另一个内存管理模块的函数,如果一定要根下去的话,可以看一下它的定义,其实就是加到一个回调函数队列中去:
void register_shrinker(struct shrinker *shrinker)
{
shrinker->nr = 0;
down_write(&shrinker_rwsem);
list_add_tail(&shrinker->list, &shrinker_list);
up_write(&shrinker_rwsem);
}
最后,看一下,当内存不足触发回调的时候,LomemoryKiller是如何找到低优先级进程,并杀死的:入口函数就是init时候注册的lowmem_shrink函数(4.3源码,后面的都有微调但原理大概类似):
static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
struct task_struct *p;
。。。
关键点1 找到当前的内存对应的阈值
for(i = 0; i < array_size; i++) {
if (other_free < lowmem_minfree[i] &&
other_file < lowmem_minfree[i]) {
min_adj = lowmem_adj[i];
break;
}
}
。。。
关键点2 找到优先级低于这个阈值的进程,并杀死
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;
}
if(selected != NULL) {
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;
}
先看关键点1:其实就是确定当前低内存对应的阈值;关键点2 :找到比该阈值优先级低或者相等,并且内存占用较多的进程(tasksize = get_mm_rss(p->mm)其实就是获取内存占用)),将其杀死。如何杀死的呢?很直接,通过Linux的中的信号量,发送SIGKILL信号直接将进程杀死。到这就分析完了LomemoryKiller内核部分如何工作的。其实很简单,一句话:被动扫描,找到低优先级的进程,杀死。
通过本篇文章,希望大家能有以下几点认知:
至于更加细节的内存的缩减、优先级的计算也许将来会放到单独的文章中说明,本文的目的是:能让大家对LowmemoryKiller的概念以及运行机制有个简单了解。
**仅供参考,欢迎指正**
Android应用程序启动过程源代码分析
Android Framework架构浅析之【近期任务】
Android Low Memory Killer介绍
Android开发之InstanceState详解
对Android近期任务列表(Recent Applications)的简单分析
Android 操作系统的内存回收机制
Android LowMemoryKiller原理分析 精
Android进程生命周期与ADJ
Linux下/proc目录简介
Android系统中的进程管理:进程的创建 精
Google文档--进程和线程
相关阅读:Android后台杀死系列之三:LowMemoryKiller原理(4.3-6.0)上篇
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者李尚授权发布。