Android 进程调度之adj算法

一、概述

提到进程调度,可能大家首先想到的是Linux cpu调度算法,进程优先级之类概念,本文并不打算介绍这些内容,而是介绍Android framework层中承载activity/service/contentprovider/broadcastrecevier的进程是如何根据组件运行状态而动态调节进程自身的状态。进程有两个比较重要的状态值,即adj(定义在ProcessList.java)和procState(定义在ActivityManager.java)。

本文是根据android 6.0原生系统的算法分析,不同手机厂商都会有各自的激进策略。先来看看系统adj和procState有哪些级别。

1.1 ADJ级别

定义在ProcessList.java文件,oom_adj划分为16级,从-17到16之间取值。adj值越大,优先级越低,adj<0的进程都是系统进程。

ADJ级别 取值 解释
UNKNOWN_ADJ 16 一般指将要会缓存进程,无法获取确定值
CACHED_APP_MAX_ADJ 15 不可见进程的adj最大值
CACHED_APP_MIN_ADJ 9 不可见进程的adj最小值
SERVICE_B_AD 8 B List中的Service(较老的、使用可能性更小)
PREVIOUS_APP_ADJ 7 上一个App的进程(往往通过按返回键)
HOME_APP_ADJ 6 Home进程
SERVICE_ADJ 5 服务进程(Service process)
HEAVY_WEIGHT_APP_ADJ 4 后台的重量级进程,system/rootdir/init.rc文件中设置
BACKUP_APP_ADJ 3 备份进程
PERCEPTIBLE_APP_ADJ 2 可感知进程,比如后台音乐播放
VISIBLE_APP_ADJ 1 可见进程(Visible process)
FOREGROUND_APP_ADJ 0 前台进程(Foreground process
PERSISTENT_SERVICE_ADJ -11 关联着系统或persistent进程
PERSISTENT_PROC_ADJ -12 系统persistent进程,比如telephony
SYSTEM_ADJ -16 系统进程
NATIVE_ADJ -17 native进程(不被系统管理)

lmkd会根据会根据当前系统可能内存的情况,来决定杀掉不同adj级别的进程,Android进程生命周期与ADJ

1.2 进程state级别

定义在ActivityManager.java文件,process_state划分18类,从-1到16之间取值。

state级别 取值 解释
PROCESS_STATE_CACHED_EMPTY 16 进程处于cached状态,且为空进程
PROCESS_STATE_CACHED_ACTIVITY_CLIENT 15 进程处于cached状态,且为另一个cached进程(内含Activity)的client进程
PROCESS_STATE_CACHED_ACTIVITY 14 进程处于cached状态,且内含Activity
PROCESS_STATE_LAST_ACTIVITY 13 后台进程,且拥有上一次显示的Activity
PROCESS_STATE_HOME 12 后台进程,且拥有home Activity
PROCESS_STATE_RECEIVER 11 后台进程,且正在运行receiver
PROCESS_STATE_SERVICE 10 后台进程,且正在运行service
PROCESS_STATE_HEAVY_WEIGHT 9 后台进程,但无法执行restore,因此尽量避免kill该进程
PROCESS_STATE_BACKUP 8 后台进程,正在运行backup/restore操作
PROCESS_STATE_IMPORTANT_BACKGROUND 7 对用户很重要的进程,用户不可感知其存在
PROCESS_STATE_IMPORTANT_FOREGROUND 6 对用户很重要的进程,用户可感知其存在
PROCESS_STATE_TOP_SLEEPING 5 与PROCESS_STATE_TOP一样,但此时设备正处于休眠状态
PROCESS_STATE_FOREGROUND_SERVICE 4 拥有一个前台Service
PROCESS_STATE_BOUND_FOREGROUND_SERVICE 3 拥有一个前台Service,且由系统绑定
PROCESS_STATE_TOP 2 拥有当前用户可见的top Activity
PROCESS_STATE_PERSISTENT_UI 1 persistent系统进程,并正在执行UI操作
PROCESS_STATE_PERSISTENT 0 persistent系统进程
PROCESS_STATE_NONEXISTENT -1 不存在的进程

1.3 核心方法

调整进程的adj的3大护法:

  • updateOomAdjLocked:更新adj,当目标进程为空,或者被杀则返回false;否则返回true;
  • computeOomAdjLocked:计算adj,返回计算后RawAdj值;
  • applyOomAdjLocked:应用adj,当需要杀掉目标进程则返回false;否则返回true。

前面提到调整adj的3大护法,最为常见的方法便是computeOomAdjLocked,这也是其他各个方法在需要更新adj时会调用的方法,该方法有3个不同参数的同名方法,定义如下:

updateOomAdjLocked的实现过程中依次会computeOomAdjLockedapplyOomAdjLocked

1.4 使用场景

到底哪些地方会需要updateOomAdjLocked呢,其实很多地方,下面列举常见场景:

  • Activity的start/resume/finish;
  • Service的start/bind/unbind;
  • broadcast的分发/处理;
  • contentprovider的发布/移除/获取;
  • 进程的kill/attach等。

二、ADJ算法

ADJ算法,其核心也就是updateOomAdjLocked方法。

2.1 一参updateOomAdjLocked

[-> ActivityManagerService.java]

该方法主要功能:

  1. 执行五参updateOomAdjLocked
  2. 当app经过更新adj操作后,其cached状态改变(包括由cached变成非cached,或者非cached变成cached),或者curRawAdj=16,则执行无参updateOomAdjLocked

2.2 五参updateOomAdjLocked

该方法是private方法,只提供给一参和无参的同名方法调用,系统中并没有其他地方调用。

2.3 无参updateOomAdjLocked

2.3.1 涉及的部分参数

[-> ProcessList.java]

  • 空进程存活时长: MAX_EMPTY_TIME = 30min
  • (缓存+空)进程个数上限:MAX_CACHED_APPS = SystemProperties.getInt(“sys.fw.bg_apps_limit”,32) = 32(默认);
  • 空进程个数上限:MAX_EMPTY_APPS = computeEmptyProcessLimit(MAX_CACHED_APPS) = MAX_CACHED_APPS/2 = 16;
  • trim空进程个数上限:TRIM_EMPTY_APPS = computeTrimEmptyApps() = MAX_EMPTY_APPS/2 = 8;
  • trim缓存进程个数上限:TRIM_CACHED_APPS = computeTrimCachedApps() = MAX_CACHED_APPS-MAX_EMPTY_APPS)/3 = 5;
  • TRIM_CRITICAL_THRESHOLD = 3;

[-> AMS.java]

  • mBServiceAppThreshold = SystemProperties.getInt(“ro.sys.fw.bservice_limit”, 5);
  • mMinBServiceAgingTime =SystemProperties.getInt(“ro.sys.fw.bservice_age”, 5000);
  • mProcessLimit = ProcessList.MAX_CACHED_APPS
  • mProcessLimit = emptyProcessLimit(空进程上限) + cachedProcessLimit(缓存进程上限)
  • oldTime = now – ProcessList.MAX_EMPTY_TIME;
  • LRU进程队列长度 = numEmptyProcs(空进程数) + mNumCachedHiddenProcs(cached进程) + mNumNonCachedProcs(非cached进程)
  • emptyFactor = numEmptyProcs/3, 且大于等于1
  • cachedFactor = mNumCachedHiddenProcs/3, 且大于等于1

2.3.2 代码

2.3.3 updateOomAdjLocked小节

updateOomAdjLocked过程比较复杂,主要分为更新adj(满足条件则杀进程)和根据memFactor来调度执行TrimMemory操作;

第一部分:更新adj(满足条件则杀进程)

  • 遍历mLruProcesses进程
    • 当进程未分配adj的情况
      • 当进程procState=14或15,则设置adj=curCachedAdj(初始化=9);
        • 当curCachedAdj != nextCachedAdj,且stepCached大于cachedFactor时 则curCachedAdj = nextCachedAdj,(nextCachedAdj加2,nextCachedAdj上限为15);
      • 否则,则设置adj=curEmptyAdj(初始化=9);
        • 当curEmptyAdj != nextEmptyAdj,且stepEmpty大于EmptyFactor时 则curEmptyAdj = nextEmptyAdj,(nextEmptyAdj加2,nextEmptyAdj上限为15);
    • 根据当前进程procState状态来决策:
      • 当curProcState=14或15,且cached进程超过上限(cachedProcessLimit=16),则杀掉该进程
      • 当curProcState=16的前提下:
        • 当空进程超过上限(TRIM_EMPTY_APPS=8),且空闲时间超过30分钟,则杀掉该进程
        • 否则,当空进程超过上限(emptyProcessLimit=16),则杀掉该进程
    • 没有services运行的孤立进程,则杀掉该进程;

第二部分:根据memFactor来调度执行TrimMemory操作;

  • 根据CachedAndEmpty个数来调整内存因子memFactor(值越大,级别越高):
    • 当CachedAndEmpty < 3,则memFactor=3;
    • 当CachedAndEmpty < 5,则memFactor=2;
    • 当CachedAndEmpty >=5,且numCached<=5,numEmpty<=8,则memFactor=1;
    • 当numCached>5 或numEmpty>8,则memFactor=0;
  • 当内存因子不是普通0级别的情况下,根据memFactor来调整前台trim级别(fgTrimLevel):
    • 当memFactor=3,则fgTrimLevel=TRIM_MEMORY_RUNNING_CRITICAL;
    • 当memFactor=2,则fgTrimLevel=TRIM_MEMORY_RUNNING_LOW;
    • 否则(其实就是memFactor=1),则fgTrimLevel=TRIM_MEMORY_RUNNING_MODERATE
    • 再遍历mLruProcesses队列进程:
      • 当curProcState > 12且没有被am杀掉,则执行TrimMemory操作;
      • 否则,当curProcState = 9 且trimMemoryLevel<TRIM_MEMORY_BACKGROUND,则执行TrimMemory操作;
      • 否则,当curProcState > 7, 且pendingUiClean =true时
        • 当trimMemoryLevel<TRIM_MEMORY_UI_HIDDEN,则执行TrimMemory操作;
        • 当trimMemoryLevel<fgTrimLevel,则执行TrimMemory操作;
  • 当内存因子等于0的情况下,遍历mLruProcesses队列进程:
    • 当curProcState >=7, 且pendingUiClean =true时,
      • 当trimMemoryLevel< TRIM_MEMORY_UI_HIDDEN,则执行TrimMemory操作;

2.4 computeOomAdjLocked

该方法比较长,下面分几个部分来展开说明: (adj和procState的取值原则是以优先级高为主)

2.4.1 进程为空的情况

2.4.2 当maxAdj<=0情况

当maxAdj <=0的情况,也就意味这不允许app将其adj调整到低于前台app的优先级别。

该过程执行后将直接返回

  • curProcState = ActivityManager.PROCESS_STATE_PERSISTENT_UI;
  • curAdj = app.maxAdj

2.4.3 计算adj和procState

Case adj procState
当app是当前展示的app adj=0 procState=2
当instrumentation不为空时 adj=0 procState=4
当进程存在正在接收的broadcastrecevier adj=0 procState=11
当进程存在正在执行的service adj=0 procState=10
以上条件都不符合 adj=cachedAdj procState=16

其中cachedAdj大于等于9,该值来源于computeOomAdjLocked输入参数

2.4.4 非前台activity的情况

对于进程中的activity处于非前台情况

  • 当activity可见, 则adj=1,procState=2;
  • 当activity正在暂停或者已经暂停, 则adj=2,procState=2;
  • 当activity正在停止, 则adj=2,procState=13(且activity尚未finish);
  • 以上都不满足,否则procState=14

2.4.5 adj > 2的情况

当adj > 2的情况的前提下:

  • 当存在前台service时,则adj=2, procState=4;
  • 当强制前台时,则adj=2, procState=6;

2.4.6 HeavyWeightProces情况

当进程为HeavyWeightProcess,则adj=4, procState=9;

2.4.7 HomeProcess情况

当进程为HomeProcess情况,则adj=6, procState=12;

2.4.8 PreviousProcess情况

当进程为PreviousProcess情况,则adj=7, procState=13;

2.4.9 备份进程情况

对于备份进程的情况,则adj=3, procState=7或8

2.4.10 Service情况

当adj>0 或 schedGroup为后台线程组 或procState>2时,双重循环遍历:

  • 当service已启动,则procState<=10;
    • 当service在30分钟内活动过,则adj=5,cached=false;
  • 获取service所绑定的connections
    • 当client与当前app同一个进程,则continue;
    • 当client进程的ProcState >=cache,则设置为空进程
    • 当进程存在显示的ui,则将当前进程的adj和ProcState值赋予给client进程
    • 当不存在显示的ui,且service上次活动时间距离现在超过30分钟,则只将当前进程的adj值赋予给client进程
    • 当前进程adj > client进程adj的情况
      • 当service进程比较重要时,则设置adj >= -11
      • 当client进程adj<2,且当前进程adj>2时,则设置adj=2;
      • 当client进程adj>1时,则设置adj = clientAdj
      • 否则,设置adj <= 1;
      • 若client进程不是cache进程,则当前进程也设置为非cache进程
    • 当绑定的是前台进程的情况
      • 当client进程状态为前台时,则设置mayBeTop=true,并设置client进程procState=16
      • 当client进程状态 < 2的前提下:若绑定前台service,则clientProcState=3;否则clientProcState=6
    • 当connections并没有绑定前台service时,则clientProcState >= 7
    • 保证当前进程procState不会必client进程的procState大
  • 当进程adj >0,且activity可见 或者resumed 或 正在暂停,则设置adj = 0

2.4.11 ContentProvider情况

当adj>0 或 schedGroup为后台线程组 或procState>2时,双重循环遍历:

  • 当client与当前app同一个进程,则continue;
  • 当client进程procState >=14,则设置成procState =16
  • 没有ui展示,则保证adj >=0
  • 当client进程状态为前台时,则设置mayBeTop=true,并设置client进程procState=16设置为空进程
  • 当client进程状态 < 2时,则clientProcState=3;
  • procState 比client进程值更大时,则取client端的状态值。
  • 当contentprovider存在外部进程依赖(非framework)时,则设置adj =0, procState=6

2.4.12 调整adj

2.5 AMS.applyOomAdjLocked

该方法主要功能:

  1. 把curRawAdj值赋给setRawAdj
  2. 把adj值 发送给lmkd守护进程
  3. 当app标记waitingToKill,且没有广播接收器运行在该进程,并且调度组为后台非交互组,则杀掉该进程,设置applyOomAdjLocked过程失败;
  4. 设置进程组信息
  5. 设置进程状态
  6. 执行pss统计操作,以及计算下一次pss时间
  7. 设置进程状态改变;

三、总结

调整进程的adj的3大护法:

  • updateOomAdjLocked:更新adj,当目标进程为空,或者被杀则返回false;否则返回true;
  • computeOomAdjLocked:计算adj,返回计算后RawAdj值;
  • applyOomAdjLocked:应用adj,当需要杀掉目标进程则返回false;否则返回true。

3.1 updateOomAdjLocked

主要工作:

  • 遍历mLruProcesses进程,更新进程adj,并杀掉满足以下条件的进程:
    • 当curProcState=14或15,且cached进程超过上限(cachedProcessLimit=16)
    • 当curProcState=16,且空进程超过上限(TRIM_EMPTY_APPS=8),且空闲时间超过30分钟
    • 当curProcState=16,且空进程超过上限(emptyProcessLimit=16)
  • 根据memFactor来调度执行TrimMemory操作;

3.2 computeOomAdjLocked

主要工作:计算进程的adj和procState

  • 进程为空的情况
  • maxAdj<=0
  • 计算各种状态下(当前显示activity, 症结接收的广播/service等)的adj和procState
  • 非前台activity的情况
  • adj > 2的情况
  • HeavyWeightProces情况
  • HomeProcess情况
  • PreviousProcess情况
  • 备份进程情况
  • Service情况
  • ContentProvider情况
  • 调整adj

原则1:取大优先,Android给进程优先级评级策略是选择最高的优先级,例如:当进程既有后台Service,也有前台Activity时,该进程的优先级则会评定为前台进程(adj=0),而非服务进程(adj=5).

原则2:

一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。 ???

3.3 applyOomAdjLocked

主要工作:

  • 把adj值相关信息发送给lmkd守护进程
  • 当app标记waitingToKill,且没有广播接收器运行在该进程,并且调度组为后台非交互组,则杀掉该进程;
  • 设置进程组信息/进程状态
  • 执行pss统计操作,以及计算下一次pss时间

apply过程中只有当waitingToKill情况下杀掉该进程,则会返回false;否则都是返回true。

作者微博:@Gityuan

原文出处:Gityuan.com

1 收藏 评论

关于作者:gityuan

Android系统工程师,www.gityuan.com博主,个人新浪微博:http://weibo.com/gityuan 个人主页 · 我的文章 · 3 ·     

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部