Jimmy Chen

A Programmer

(原创)基于Android 8.1之lowmemorykiller源码分析

  在使用Android的过程中,我们经常性的会按Home键返回到桌面,然后打开其他的应用进行使用,而此时前一个应用会驻留在内存中,当再次打开该应用时就可以直接显示使用。通过这种方法可以提升用户体验以及提高应用打开速度。但是系统内存是有限的,不可能一直将全部应用驻留在系统内存中,所以lowmemorykiller就诞生了。lowmemorykiller,如其名低内存杀手,作用是当内存处于低水平时,杀死系统中余留的暂时还不用的进程,以释放内存。下面我们直接分析lowmemorykiller的源码实现

lmkd启动

  lmkd是在init执行阶段作为守护进程启动的

  从上面的代码中可以了解到,lmkd在启动的过程中要创建一个名为lmkd的socket。查看lmkd编译的Android.bp文件可以知道,lmkd是由system/core/lmkd/lmkd.c编译而成。下面直接分析lmkd.c文件的源码

lmkd.c源码分析

main方法

  既然lmkd是一个可执行程序,那么可以直接从main方法开始分析。

  上面的代码主要集中在后面的两个方法,一个是init方法,另一个是mainloop方法。

init方法

  下面看看procadjslot_list的定义

  init方法就分析到这里,下面接着看mainloop方法。

mainloop方法

  上面epoll_wait在等到lmkd socket的listen事件到来,然后再调用event.data.ptr方法,在init方法中,我们将event.data.ptr指向ctrl_connect_handler方法,所以这里调用的是ctrl_connect_handler方法。下面分析ctrl_connect_handler完成的内容:

ctrl_connect_handler方法

  ctrl_connect_handler方法在处理lmkd socket的listen事件时,会像epoll创建另一个epoll事件,用于处理lmkd socket的accept事件,accept事件的处理方法为ctrl_data_handler,下面接着分析ctrl_data_handler方法

ctrl_data_handler方法

  ctrl_command_handler能够处理三种类型的command,command在代码中用enum列出

  上面三种command后续所带的参数使用如下

cmd_target方法

  所以cmd_target要做的内容很简单也很好理解,就是讲framework传过来的数值记录到数组中,然后将这两个数组组织成字符串输入,然后写入到内核对应的位置即可。接下来分析另一个command的处理方法cmd_procprio

cmd_procprio方法

  因为我们现在使用的是内核接口,所以只需将oomadj数值写入到/proc/[pid]/oom_score_adj中即可起到更新进程的oomadj的效果。接下来分析最后一个command处理方法cmd_procremove

cmd_procremove方法

  如果使用内核接口,这个方法最是简单了,什么都不用做。OK,系统侧lmkd对应源码到这里就分析完了。看着还是挺简单的,至于framework层我们这里只分析向底层发送者三种command部分的代码,具体的调用待后面有机会遇到再分析好了。

ProgressList.java lmkd部分源码分析

updateOomLevels方法分析

  updateOomLevels方法前面只是简单的计算出oomMinFree数组的值和oomAdj值,然后通过writeLmkd将数据发送给lmkd。下面看看writeLmkd方法

writeLmkd方法

  LMK_PROCREMOVE和LMK_PROCPRIO这两个command的处理代码比较简单,这里博主就不多做分析了。

内核层lowmemorykiller驱动分析

  上面所讲的代码只是讲述对lowmemorykiller中adj、minfree或者应用的adj进行设置,并没有涉及到具体的kill应用过程。而真正kill应用触发就是在内核层的lowmemorykiller驱动中。所以lowmemorykiller驱动的源码我们还是要继续坚持分析的。

  lowmemorykiller是内核的一个驱动,驱动注册的时候都会触发器init调用,所以我们可以以init方法调用作为突破口来分析这个驱动的实现和功能

init方法调用

  init方法中注册了一个shrinker到内核的shrinker链表。当内存不足时 kswapd 线程会遍历一张 shrinker 链表,并回调已注册的 shrinker 函数来回收内存 page,kswapd 还会周期性唤醒来执行内存操作。每个 zone 维护 active_list 和 inactive_list 链表,内核根据页面活动状态将 page 在这两个链表之间移动,最终通过 shrink_slab 和 shrink_zone 来回收内存页,有兴趣想进一步了解 linux 内存回收机制,可自行研究。

  接着我们查看lowmem_shrinker结构中包含哪些方法

lowmem_scan方法

  当系统内存不足时会回调lowmem_scan方法来kill应用以达到释放内存的效果。

  这段代码的主要思想就是首先获取当前内存剩余量,根据剩余量获取都对应的min free adj值;接着遍历当前系统中的所有进程,从中挑选出进程的oom adj最大者,如果存在进程oom adj值相同,则挑选出其中占用内存最大的那个进程;最后向这个进程发送SIGKILL信息,以达到杀死该进程释放内存的效果。这里博主有个思考,如果系统的进程非常多,如果内存不足,在进行lowmemorykiller时每次都需要遍历一遍,那效率岂不是很低,这里有没有优化的方法呢?留待给大家思考,有好的想法也可以留言交流。接下来就剩下最后一个方法了。

lowmem_count方法

  也很简单,这里只是简单统计各部分占用的内存大小,然后将其返回。

  好了,这一篇就到这里了。内容多了一点点,但是都是比较好理解的。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注