一、内容预览

Android进程系列第六篇---LowmemoryKiller机制分析(上)_第1张图片 概要.png

二、概述

前面进程系列已经更新了五篇,本文(基于Android O源码),梳理LMK杀进程机制上篇,主要总结AMS和LowmemoryKiller通信的方式以及LowmemoryKiller的原理。
Android进程系列第一篇---进程基础
Android进程系列第二篇---Zygote进程的创建流程
Android进程系列第三篇---SystemServer进程的创建流程
Android进程系列第四篇---SystemServer进程的启动流程
Android进程系列第五篇---应用进程的创建流程

1、为什么引入LowmemoryKiller?
进程的启动分冷启动和热启动,当用户退出某一个进程的时候,并不会真正的将进程退出,而是将这个进程放到后台,以便下次启动的时候可以马上启动起来,这个过程名为热启动,这也是Android的设计理念之一。这个机制会带来一个问题,每个进程都有自己独立的内存地址空间,随着应用打开数量的增多,系统已使用的内存越来越大,就很有可能导致系统内存不足。为了解决这个问题,系统引入LowmemoryKiller(简称lmk)管理所有进程,根据一定策略来kill某个进程并释放占用的内存,保证系统的正常运行。

2、 LMK基本原理?
所有应用进程都是从zygote孵化出来的,记录在AMS中mLruProcesses列表中,由AMS进行统一管理,AMS中会根据进程的状态更新进程对应的oom_adj值,这个值会通过文件传递到kernel中去,kernel有个低内存回收机制,在内存达到一定阀值时会触发清理oom_adj值高的进程腾出更多的内存空间,这就是Lowmemorykiller工作原理。

3、LMK基本实现方案
所以根据不同手机的配置,就有对应的杀进程标准,这个标准用minfree和adj两个文件来定义:

/sys/module/lowmemorykiller/parameters/minfree:里面是以","分割的一组数,每个数字代表一个内存级别。
/sys/module/lowmemorykiller/parameters/adj:对应上面的一组数,每个数组代表一个进程优先级级别

用小米note3举例:

wangjing@wangjing-OptiPlex-7050:~$ adb rootrestarting adbd as rootwangjing@wangjing-OptiPlex-7050:~$ adb shelljason:/ # cat /sys/module/lowmemorykiller/parameters/minfree18432,23040,27648,32256,55296,80640jason:/ # jason:/ # cat /sys/module/lowmemorykiller/parameters/adj0,100,200,300,900,906jason:/ # 

minfree中数值的单位是内存中的页面数量,一般情况下一个页面是4KB,当内存低于80640的时候,系统会杀死adjj>=906级别的进程,当内存低于55296的时候,系统会杀死adj>=900级别的进程。不同配置的机器这两个文件会有区别,我把minfree文件中的值理解成五个水位线,而adj这个文件中的值与minfree文件中的数值一一对应,意味着到达什么样的水位线,杀死对应数值的进程。

对于应用进程来说,也需要有自身的adj,由AMS负责更新。定义在oom_adj和oom_score_adj文件中:
/proc/pid/oom_adj:代表当前进程的优先级,这个优先级是kernel中的优先级。
/proc/pid/oom_score_adj:这个是AMS上层的优先级,与ProcessList中的优先级对应

比如查看一下头条进程的adj值,如下:

jason:/ # ps -ef |grep news                                                                                                                                                                                u0_a159       7113  1119 8 15:21:12 ?     00:00:11 com.ss.android.article.newsu0_a159       7188  1119 0 15:21:12 ?     00:00:00 com.ss.android.article.news:adu0_a159       7299  1119 1 15:21:16 ?     00:00:02 com.ss.android.article.news:pushu0_a159       7384  1119 1 15:21:17 ?     00:00:00 com.ss.android.article.news:pushserviceroot          7838  6429 3 15:23:35 pts/0 00:00:00 grep newsjason:/ # cat proc/7113/oom_adj                                                                                                                                                                            0jason:/ # cat proc/7113/oom_score_adj                                                                                                                                                                      0jason:/ # cat proc/7113/oom_adj                                                                                                                                                                            12jason:/ # cat proc/7113/oom_score_adj                                                                                                                                                                      700jason:/ # 

当头条位于前台进程的时候oom_adj值为0,oom_score_adj值也是0,当退出成为后台进程的时候,oom_adj值为12,oom_score_adj值是700。

其实oom_adj与oom_score_adj这两个值是有换算关系的。

kernel/drivers/staging/android/lowmemorykiller.c271static short lowmem_oom_adj_to_oom_score_adj(short oom_adj)272{273 if (oom_adj == OOM_ADJUST_MAX)274     return OOM_SCORE_ADJ_MAX;275 else276     return (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;277}

其中OOM_ADJUST_MAX=-15,OOM_SCORE_ADJ_MAX=1000,OOM_DISABLE=-17,那么换算就是:oom_score_adj=12*1000/17=700。高版本的内核都不在使用oom_adj,而是用oom_score_adj,oom_score_adj是一个向后兼容。

综上总结一下LMK的基本原理,如下

Android进程系列第六篇---LowmemoryKiller机制分析(上)_第2张图片 LMK基本原理.png

用户在启动一个进程之后,通常伴随着启动一个Activity游览页面或者一个Service播放音乐等等,这个时候此进程的adj被AMS提高,LMK就不会杀死这个进程,当这个进程要做的事情做完了,退出后台了,此进程的adj很快又被AMS降低。当需要杀死一个进程释放内存时,一般先根据当前手机剩余内存的状态,在minfree节点中找到当前等级,再根据这个等级去adj节点中找到这个等级应该杀掉的进程的优先级, 之后遍历所有进程并比较进程优先级adj与优先级阈值,并杀死优先级低于阈值的进程,达到释放内存的目的。本文不讨论adj的计算,只讨论lmk原理。

三、LowmemoryKiller机制剖析

总的来说,Framework层通过调整adj的值和阈值数组,输送给kernel中的lmk,为lmk提供杀进程的原材料,因为用户空间和内核空间相互隔离,就采用了文件节点进行通讯,用socket将adj的值与阈值数组传给lmkd(5.0之后不在由AMS直接与lmk通信,引入lmkd守护进程),lmkd将这些值写到内核节点中。lmk通过读取这些节点,实现进程的kill,所以整个lmk机制大概可分成三层。

Android进程系列第六篇---LowmemoryKiller机制分析(上)_第3张图片 LMK三层架构.png
3.1、Framework层

AMS中与adj调整的有三个核心的方法,如下

  • AMS.updateConfiguration:更新窗口配置,这个过程中,分别向/sys/module/lowmemorykiller/parameters目录下的minfree和adj节点写入相应数值;

  • AMS.applyOomAdjLocked:应用adj,当需要杀掉目标进程则返回false;否则返回true,这个过程中,调用setOomAdj(),向/proc/pid/oom_score_adj写入oom_adj 后直接返回;

  • AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked:进程死亡后,调用remove(),直接返回;

3.1.1、 AMS.updateConfiguration
   public boolean updateConfiguration(Configuration values) {       synchronized(this) {           if (values == null && mWindowManager != null) {               // sentinel: fetch the current configuration from the window manager               values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);           }           if (mWindowManager != null) {               // Update OOM levels based on display size.               mProcessList.applyDisplaySize(mWindowManager);           }        .....       }   }

mProcessList是ProcessList对象,调用applyDisplaySize方法,基于屏幕尺寸,更新LMK的水位线

/frameworks/base/services/core/java/com/android/server/am/ProcessList.java198    void applyDisplaySize(WindowManagerService wm) {199        if (!mHaveDisplaySize) {200            Point p = new Point();201            // TODO(multi-display): Compute based on sum of all connected displays' resolutions.202            wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p);203            if (p.x != 0 && p.y != 0) {                     //传入屏幕的尺寸204                updateOomLevels(p.x, p.y, true);205                mHaveDisplaySize = true;206            }207        }208    }

传入屏幕的尺寸更新水位线,逻辑很简单

210    private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {211        // Scale buckets from avail memory: at 300MB we use the lowest values to212        // 700MB or more for the top values.213        float scaleMem = ((float)(mTotalMemMb-350))/(700-350);214215        //根据屏幕大小计算出scale216        int minSize = 480*800;  //  384000217        int maxSize = 1280*800; // 1024000  230400 870400  .264218        float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);            //google代码就是这么写的,表示不好评价了219        if (false) {220            Slog.i("XXXXXX", "scaleMem=" + scaleMem);221            Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth222                    + " dh=" + displayHeight);223        }224225        float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;226        if (scale < 0) scale = 0;227        else if (scale > 1) scale = 1;228        int minfree_adj = Resources.getSystem().getInteger(229                com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);230        int minfree_abs = Resources.getSystem().getInteger(231                com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);232        if (false) {233            Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs);234        }235236        final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;237       //通过下面的运算,将mOomMinFreeLow和mOomMinFreeHigh经过运算         // 最后得出的 值存入mOomMinFree中,而如何计算这个值,是根据当前屏幕的分辨率和内存大小来238        for (int i=0; i

这里携带的命令协议是LMK_TARGET,它对应到kernel里面执行的函数是cmd_target,要求kernel干的事情就是更新两面两个文件

/sys/module/lowmemorykiller/parameters/minfree/sys/module/lowmemorykiller/parameters/adj

这两个文件的作用我已经在开头说过了,我把minfree文件中的值理解成五个水位线,而adj这个文件中的值与minfree文件中的数值一一对应,意味着到达什么样的水位线,杀死对应数值的进程。而AMS里面就是通过调用applyDisplaySize方法,基于屏幕尺寸以及机器的CPU位数,更新LMK的水位线的。

3.1.2、 AMS.applyOomAdjLocked

在看applyOomAdjLocked方法,这个方法的作用是应用adj,这个过程中,调用setOomAdj(),向/proc/pid/oom_score_adj写入oom_adj 后直接返回;系统中更新adj的操作很频繁,四大组件的生命周期都会影响着adj的值。而更新adj一般由applyOomAdjLocked完成。在看代码之前,在回温一下AMS中adj的定义,Android M与之后的adj定义有所区别。

Android进程系列第六篇---LowmemoryKiller机制分析(上)_第4张图片 Android M Android进程系列第六篇---LowmemoryKiller机制分析(上)_第5张图片 Android M之后

可以看到M之后的adj数值变大的,为什么呢


Android进程系列第六篇---LowmemoryKiller机制分析(上)_第6张图片 image.png

因为这样adj可以更加细化了,即使相同进程,不同任务栈的adj也可以不一样。从Android P开始,进一步细化ADJ级别,增加了VISIBLE_APP_LAYER_MAX(99),是指VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)之间有99个槽,则可见级别ADJ的取值范围为[100,199]。 算法会根据其所在task的mLayerRank来调整其ADJ,100加上mLayerRank就等于目标ADJ,layer越大,则ADJ越小。

再次科普一下,我们可以用下面的两个办法随时查看adj的值 。

1、cat proc//oom_score_adj2、adb shell dumpsys activity o/p

好了,现在来看AMS调用applyOomAdjLocked更新adj。

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java22000    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now, long nowElapsed) {             .........2200922010        if (app.curAdj != app.setAdj) {                    //之前的adj不等于计算的adj,需要更新22011            ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);22012            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) {22013                String msg = "Set " + app.pid + " " + app.processName + " adj "22014                        + app.curAdj + ": " + app.adjType;22015                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);22016            }22017            app.setAdj = app.curAdj;22018            app.verifiedAdj = ProcessList.INVALID_ADJ;22019        }                .........22020}
/frameworks/base/services/core/java/com/android/server/am/ProcessList.java630    public static final void setOomAdj(int pid, int uid, int amt) {631        if (amt == UNKNOWN_ADJ)632            return;633634        long start = SystemClock.elapsedRealtime();635        ByteBuffer buf = ByteBuffer.allocate(4 * 4);636        buf.putInt(LMK_PROCPRIO);637        buf.putInt(pid);638        buf.putInt(uid);639        buf.putInt(amt);              //将AMS已经计算好的adj值通过socket发送到lmkd640        writeLmkd(buf);641        long now = SystemClock.elapsedRealtime();642        if ((now-start) > 250) {643            Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid644                    + " = " + amt);645        }646    }

这里携带的命令协议是LMK_PROCPRIO,对应kernel里面cmd_procprio函数,要求kernel干的事情是---把AMS发送过来的adj值更新到下面的文件中去。这样内存紧张的时候,LMK就会遍历内核中进程列表,杀死相应adj的进程了。

3.1.3、 AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked

进程死掉后,会调用该进程的ProcessList.remove方法,也会通过Socket通知lmkd更新adj。

/frameworks/base/services/core/java/com/android/server/am/ProcessList.java651    public static final void remove(int pid) {652        ByteBuffer buf = ByteBuffer.allocate(4 * 2);653        buf.putInt(LMK_PROCREMOVE);654        buf.putInt(pid);655        writeLmkd(buf);656    }

这里携带的命令协议是LMK_PROCREMOVE,对应kernel里面的cmd_procremove函数,要求kernel干的事情是,当进程死亡了,删除/proc/下面的文件。

上面三大方法最后都是通过writeLmkd与lmkd通信,现在看看writeLmkd中怎么和lmkd通信的,首先需要打开与lmkd通信的socket,lmkd创建名称为lmkd的socket,节点位于/dev/socket/lmkd

658    private static boolean openLmkdSocket() {659        try {660            sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);661            sLmkdSocket.connect(662                new LocalSocketAddress("lmkd",663                        LocalSocketAddress.Namespace.RESERVED));664            sLmkdOutputStream = sLmkdSocket.getOutputStream();665        } catch (IOException ex) {666            Slog.w(TAG, "lowmemorykiller daemon socket open failed");667            sLmkdSocket = null;668            return false;669        }670671        return true;672    }

当sLmkdSocket创建之后,就用它来发送数据到对端(lmkd)

674    private static void writeLmkd(ByteBuffer buf) {675       //尝试三次676        for (int i = 0; i < 3; i++) {677            if (sLmkdSocket == null) {678                    if (openLmkdSocket() == false) {679                        try {680                            Thread.sleep(1000);681                        } catch (InterruptedException ie) {682                        }683                        continue;684                    }685            }686687            try {688                sLmkdOutputStream.write(buf.array(), 0, buf.position());689                return;690            } catch (IOException ex) {691                Slog.w(TAG, "Error writing to lowmemorykiller socket");692693                try {694                    sLmkdSocket.close();695                } catch (IOException ex2) {696                }697698                sLmkdSocket = null;699            }700        }701    }702}

四、总结

这篇文章主要是总结lmk的初步的工作原理,如何为系统的资源保驾护航。核心原理就是Framework层通过调整adj的值和阈值数组,输送给kernel中的lmk,为lmk提供杀进程的原材料。通过前面的分析AMS中给lmkd发送数据原材料有三个入口,携带的命令协议也有三种,如下。

功能 AMS对应方法 命令 内核对应函数
LMK_PROCPRIO PL.setOomAdj() 设置指定进程的优先级,也就是oom_score_adj cmd_procprio
LMK_TARGET PL.updateOomLevels() 更新/sys/module/lowmemorykiller/parameters/中的minfree以及adj cmd_target
LMK_PROCREMOVE PL.remove() 移除进程 cmd_procremove

关于kenel中的工作流程,下面一篇分解。

更多相关文章

  1. Android 根文件系统启动过程。
  2. Android init进程——源码分析
  3. Android 上传头像(文件)到服务器
  4. Android 进程保活
  5. Android代码混淆配置(Proguard文件解析)
  6. 升级Android SDK后ADT找不到adb.exe文件的解决办法
  7. Android线程优先级设置方法
  8. android 文件系统分析
  9. android下解析xml文件遇到中文问题

随机推荐

  1. ADT在线安装
  2. Android(安卓)Review
  3. Android(安卓)4.4(KitKat)中VSync信号的
  4. Android实现组合键监听功能
  5. Android(安卓)学习笔记8---SAX和DOM解析X
  6. 如何制作Jar包并在android中调用jar包
  7. Android开发:布局分区域设置不同背景色
  8. 搭建Android开发环境写第一个Hello World
  9. Android开发学习笔记(十三) ProgressDialog
  10. [Android] 开发资料收集:动画