从源码角度看 LowMemoryKiller

简介

Android系统中,进程的运行状态常常是各个应用开发者关注点的重中之重,进程是否能够存活基本也就意味着应用能否从用户手中获益。之前我写过一篇文章专门讲过进程保活与进程优先级的计算方式,有兴趣的话大家可以再看看。

进程一直存活固然很重要,一来应用可以随时随地的在用户手机上做业务操作,二来用户再次启动时速度也会更快些。然而,作为移动设备,应用进程如果只启动而不能被Android系统被动的杀除,那么越来越多的内存被尚在运行的无用进程霸占,系统只会越用越卡。对此,Android基于OOM机制引入了lowmemmorykiller,可以在系统内存紧缺时杀死不必要的低优先级进程,进程的优先级越低就越容易被杀死。

对于lowmemorykiller, 我总结为三层:AMS, lmkd 与 lowmemorykiller。其中AMS负责更新各应用的进程优先级与阈值数组,lmkd负责接收AMS传输过来的数据然后写入到sys与proc节点,lowmemorykiller则在内存低于阈值时才会被触发并负责杀死低优先级的进程。

这篇文章中,我会先列出一张总结流程图,便于大家掌握lowmemorykiller总体涉及也方便以后查看。随后我再会按照三层从上往下的分析lowmemmorykiller的代码。

查看大图

上层:ActivityManagerService更新adj

AMS.updateConfiguration

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

  • public void updateConfiguration(Configuration values) {
  • synchronized (this) {
  • ...
  • if (mWindowManager != null) {
  • mProcessList.applyDisplaySize(mWindowManager);
  • }
  • ...
  • }
  • }

updateConfiguration是AMS对外提供的binder接口,调用后可以更新窗口配置

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

  • void applyDisplaySize(WindowManagerService wm) {
  • if (!mHaveDisplaySize) {
  • Point p = new Point();
  • // 获取窗口显示的宽高
  • wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p);
  • if (p.x != 0 && p.y != 0) {
  • updateOomLevels(p.x, p.y, true);
  • mHaveDisplaySize = true;
  • }
  • }
  • }
  • private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
  • // mTotalMemMb是指当前设备内存大小,以MB为单位
  • // 计算内存比例
  • float scaleMem = ((float)(mTotalMemMb-350))/(700-350);
  • // 计算屏幕大小
  • int minSize = 480*800; // 384000
  • int maxSize = 1280*800; // 1024000 230400 870400 .264
  • float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
  • if (false) {
  • Slog.i("XXXXXX", "scaleMem=" + scaleMem);
  • Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth
  • + " dh=" + displayHeight);
  • }
  • // 选择较大的比例,最小为0最大为1
  • float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
  • if (scale < 0) scale = 0;
  • else if (scale > 1) scale = 1;
  • int minfree_adj = Resources.getSystem().getInteger(
  • com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
  • int minfree_abs = Resources.getSystem().getInteger(
  • com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
  • if (false) {
  • Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs);
  • }
  • final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
  • for (int i=0; i<mOomAdj.length; i++) {
  • int low = mOomMinFreeLow[i];
  • int high = mOomMinFreeHigh[i];
  • if (is64bit) {
  • // Increase the high min-free levels for cached processes for 64-bit
  • // 64位的机器high值会适当的提高,最终空进程、缓存进程被杀的内存阈值也会被提高
  • if (i == 4) high = (high*3)/2;
  • else if (i == 5) high = (high*7)/4;
  • }
  • mOomMinFree[i] = (int)(low + ((high-low)*scale));
  • }
  • ...
  • if (write) {
  • // 通过socket将计算完毕的信息传输给lwkd守护进程
  • ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
  • buf.putInt(LMK_TARGET);
  • for (int i=0; i<mOomAdj.length; i++) {
  • // 最终写入到minfree文件的数组单位为page的个数
  • buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
  • // 写入到adj文件的数组为固定的adj数组
  • buf.putInt(mOomAdj[i]);
  • }
  • writeLmkd(buf);
  • SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
  • }
  • // GB: 2048,3072,4096,6144,7168,8192
  • // HC: 8192,10240,12288,14336,16384,20480
  • }
名称
FOREGROUND_APP\_ADJ 0
VISIBLE_APP\_ADJ 1
PERCEPTIBLE_APP\_ADJ 2
BACKUP_APP\_ADJ 3
CACHED_APP\_MIN\_ADJ 9
CACHED_APP\_MAX\_ADJ 15

这张表为mOomAdj数组,最终会被写入到sys节点的adj文件

AMS.applyOomAdjLocked

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

  • private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
  • long nowElapsed) {
  • ...
  • if (app.curAdj != app.setAdj) {
  • ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
  • }
  • ...

当计算后的adj和之前的不一样,就会通过setOomAdj更新lmk的adj数值

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

  • 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();
  • if ((now-start) > 250) {
  • Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid
  • + " = " + amt);
  • }
  • }

setOomAdj会将AMS已经计算好的adj值通过socket发送到lmkd

AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

  • private final void handleAppDiedLocked(ProcessRecord app,
  • boolean restarting, boolean allowRestart) {
  • int pid = app.pid;
  • boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
  • if (!kept && !restarting) {
  • removeLruProcessLocked(app);
  • if (pid > 0) {
  • ProcessList.remove(pid);
  • }
  • }
  • ...

死亡回调后,如果进程不再重启,那么会先移除lruProcess的记录,随后会调用该进程的ProcessList.remove方法

  • private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
  • boolean restarting, boolean allowRestart, int index) {
  • ...
  • if (restart && !app.isolated) {
  • // We have components that still need to be running in the
  • // process, so re-launch it.
  • if (index < 0) {
  • ProcessList.remove(app.pid);
  • }
  • addProcessNameLocked(app);
  • startProcessLocked(app, "restart", app.processName);
  • return true;
  • }
  • return false;
  • }

通过trim会调用应用的cleanUp机制,如果应用会重启那么会调用该进程的ProcessList.remove方法

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

  • public static final void remove(int pid) {
  • ByteBuffer buf = ByteBuffer.allocate(4 * 2);
  • buf.putInt(LMK_PROCREMOVE);
  • buf.putInt(pid);
  • writeLmkd(buf);
  • }

各协议所携带的参数

lmk协议 所需参数
LMK_TARGET mOomMinFree, mOomAdj两个数组
LMK_PROCPRIO pid, uid, amt (adj)
LMK_PROCREMOVE pid

ProcessList.writeLmkd

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

  • private static void writeLmkd(ByteBuffer buf) {
  • for (int i = 0; i < 3; i++) {
  • if (sLmkdSocket == null) {
  • // 首先尝试打开socket,如果没有打开就睡眠1s,最多会尝试打开3次,如果没有打开就不会再做尝试
  • if (openLmkdSocket() == false) {
  • try {
  • Thread.sleep(1000);
  • } catch (InterruptedException ie) {
  • }
  • continue;
  • }
  • }
  • try {
  • // 写入数据到对端的socket
  • sLmkdOutputStream.write(buf.array(), 0, buf.position());
  • return;
  • } catch (IOException ex) {
  • Slog.w(TAG, "Error writing to lowmemorykiller socket");
  • try {
  • sLmkdSocket.close();
  • } catch (IOException ex2) {
  • }
  • sLmkdSocket = null;
  • }
  • }
  • }

writeLmkd的输入参数是ByteBuffer,用来保存需要传输给lmkd的数据

  • private static boolean openLmkdSocket() {
  • try {
  • // 尝试连接socket
  • sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
  • sLmkdSocket.connect(
  • new LocalSocketAddress("lmkd",
  • LocalSocketAddress.Namespace.RESERVED));
  • // 获取输出流,用于向对端写入数据
  • sLmkdOutputStream = sLmkdSocket.getOutputStream();
  • } catch (IOException ex) {
  • Slog.w(TAG, "lowmemorykiller daemon socket open failed");
  • sLmkdSocket = null;
  • return false;
  • }
  • return true;
  • }

以上的代码便是AMS通过socket来将计算后的adj, minfree阈值写入到lwkd的实现方法

中层:lmkd守护线程,向节点写入数据

  • int main(int argc __unused, char **argv __unused) {
  • struct sched_param param = {
  • .sched_priority = 1,
  • };
  • mlockall(MCL_FUTURE);
  • sched_setscheduler(0, SCHED_FIFO, &param);
  • // 首先完成初始化操作
  • if (!init())
  • // 随后进入主循环,等待AMS发送socket请求
  • mainloop();
  • ALOGI("exiting");
  • return 0;
  • }

```c
static int init(void) {
// 初始化epoll事件
struct epoll_event epev;
int i;
int ret;

...

  • // 创建一个epoll并获取fd
  • epollfd = epoll_create(MAX_EPOLL_EVENTS);
  • ctrl_lfd = android_get_control_socket("lmkd");
  • ret = listen(ctrl_lfd, 1);
  • epev.events = EPOLLIN;
  • // 当监听到socket连接事件后会调用ctrl_connect_handler方法
  • 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;
top Created with Sketch.