Android进程调度

转载自品略图书馆 http://www.pinlue.com/article/2020/03/1300/0310021948056.html

 

Linux系统将进程分为实时进程和普通进程,实时进程的优先级范围为0~99,普通进程为100~139,并且二者的调度策略也是不通的。Android系统是基于Linux系统之上开发的,其充分利用了Linux系统的一些特性,有些甚至可以做为开发范本。这篇文章分析一下Android系统是如何利用Linux进程调度策略来管理进程优先级的,源码参考Android 9.0。

实时进程

Android中对实时进程使用了SCHED_FIFO策略,这个策略使用先进先出的管理规则,进程占有CPU时,如果没有更高优先级的实时进程抢占或主动让出,进程将保持使用CPU。这也说明Android系统希望实时进程能高优先级的持续运行,不想其因为时间片的耗尽而中断执行。但系统是不会让实时进程一直运行的,CPU消耗型的进程是不会设置为实时进程的,实时进程更倾向于为实时性较为敏感的IO消耗型进程服务。

Android系统在以下几个地方设置了SCHED_FIFO调度策略,

AMS中,当属性"sys.use_fifo_ui"设置为1时,将前台进程的UI线程和Render线程设置为为实时策略,否则为普通进程。同时设置了SCHED_RESET_ON_FORK位,表明其子进程会恢复默认的调度策略。

Process.setThreadScheduler(tid, Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);

SurfaceFlinger中,在亮屏时使用实时调度,灭屏时使用非实时调度。

sched_setscheduler(0, SCHED_FIFO, &param)

如果支持Audio FastMixer,FastMixer使用实时调度,通过requestPriority()设置。

Android新增加的AAudioService中,将client线程设置为实时策略,通过requestPriority()设置。

属性"camera.fifo.disable"没有被设置时,将Cameraservice的request线程设置为实时策略,通过requestPriority()设置。

Audio HIDL使用实时策略,通过requestPriority()设置。

frameworks/base/services/core/java/com/android/server/os/SchedulingPolicyService.java 83 public int requestPriority(int pid, int tid, int prio, boolean isForApp) { ...... // UID为AudioServer,CameraServer,Bluetooth时才能使用该接口, // 优先级为1~3,线程组ID(tgid)与pid不同时不能使用该接口92 if (!isPermitted() || prio < PRIORITY_MIN ||93 prio > PRIORITY_MAX || Process.getThreadGroupLeader(tid) != pid) {94 return PackageManager.PERMISSION_DENIED;95 } // 非Bluetooth时,App设置到THREAD_GROUP_RT_APP,非App设置到THREAD_GROUP_AUDIO_SYS96 if (Binder.getCallingUid() != Process.BLUETOOTH_UID) {97 try {98 // make good use of our CAP_SYS_NICE capability99 Process.setThreadGroup(tid, !isForApp ?100 Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_RT_APP);101 } catch (RuntimeException e) { ......104 }105 }106 try {107 // must be in this order or it fails the schedulability constraint108 Process.setThreadScheduler(tid, Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK,109 prio);110 } catch (RuntimeException e) { ......113 }114 return PackageManager.PERMISSION_GRANTED;115 }

可以看出Android对实时调度策略的使用是很谨慎的,只有与硬件相关的数据传输进程才会使用实时策略。大部分的进程还是做为普通进程通过优先级的调整来管理。

普通进程

Android中的普通进程使用SCHED_OTHER调度策略,它将普通进程的优先级化为9个级别。

frameworks/base/core/java/android/os/Process.java // 默认优先级225 public static final int THREAD_PRIORITY_DEFAULT = 0; // 最低优先级240 public static final int THREAD_PRIORITY_LOWEST = 19; // 后台线程优先级,略低于默认优先级,可以减少对用户交互的影响250 public static final int THREAD_PRIORITY_BACKGROUND = 10; // 前台线程优先级,正在进行交互的应用使用这个级别261 public static final int THREAD_PRIORITY_FOREGROUND = -2; // 显示相关线程的优先级,用于更新用户界面271 public static final int THREAD_PRIORITY_DISPLAY = -4; // 重要显示线程的优先级,屏幕合成和获取输入事件使用这个级别281 public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 视频相关线程的优先级290 public static final int THREAD_PRIORITY_VIDEO = -10; // 声音相关线程的优先级299 public static final int THREAD_PRIORITY_AUDIO = -16; // 重要声音线程的优先级308 public static final int THREAD_PRIORITY_URGENT_AUDIO = -19;

在原生代码的注释中,除了THREAD_PRIORITY_DEFAULT、THREAD_PRIORITY_LOWEST和THREAD_PRIORITY_BACKGROUND以外,都标注“用户通常不应该修改为此优先级”。这表明了Android的态度,它并不希望应用开发者随意去修改优先级。但在代码中并没有做出这种限制,实际上用户可以通过android.os.Process.setThreadPriority()设置-20~19任意一个值。setThreadPriority()与Linux命令nice的作用相同,在100~139间调整普通进程的优先级。

frameworks/base/core/jni/android_util_Process.cpp493void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz,494 jint pid, jint pri)495{ ...... // 调用libutils中设置优先级的接口511 int rc = androidSetThreadPriority(pid, pri); ......522}system/core/libutils/Threads.cpp299int androidSetThreadPriority(pid_t tid, int pri)300{ ...... // 根据优先级设置线程的调度304 if (pri >= ANDROID_PRIORITY_BACKGROUND) {305 rc = set_sched_policy(tid, SP_BACKGROUND);306 } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {307 rc = set_sched_policy(tid, SP_FOREGROUND);308 } ...... // 使用Linux系统接口设置优先级 314 if (setpriority(PRIO_PROCESS, tid, pri) < 0) {316 ......321}

除了setThreadPriority(),Android中还有一个调整优先级的接口:java.lang.Thread.setPriority()。

libcore/ojluni/src/main/java/java/lang/Thread.java // 最小级别258 public final static int MIN_PRIORITY = 1; // 缺省级别263 public final static int NORM_PRIORITY = 5; // 最大级别268 public final static int MAX_PRIORITY = 10; ......1080 public final void setPriority(int newPriority) {1081 ThreadGroup g;1082 checkAccess(); // 验证级别有效性1083 if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {1084 // Android-changed: Improve exception message when the new priority1085 // is out of bounds.1086 throw new IllegalArgumentException("Priority out of range: " + newPriority);1087 }1088 if((g = getThreadGroup()) != null) { // 设置的优先级不能大于线程组的最大优先级1089 if (newPriority > g.getMaxPriority()) {1090 newPriority = g.getMaxPriority();1091 }1092 synchronized(this) {1093 this.priority = newPriority;1094 if (isAlive()) { // 设置优先级的native方法1095 nativeSetPriority(newPriority);1096 }1097 }1098 }1099 }

nativeSetPriority()中直接调用了Thread::SetNativePriority()。

art/runtime/thread_android.cc // 优先级的级别数组,并不是连续的,还是基于Android定义的优先级36static const int kNiceValues[10] = {37 ANDROID_PRIORITY_LOWEST, // 1 (MIN_PRIORITY)38 ANDROID_PRIORITY_BACKGROUND + 6,39 ANDROID_PRIORITY_BACKGROUND + 3,40 ANDROID_PRIORITY_BACKGROUND,41 ANDROID_PRIORITY_NORMAL, // 5 (NORM_PRIORITY)42 ANDROID_PRIORITY_NORMAL - 2,43 ANDROID_PRIORITY_NORMAL - 4,44 ANDROID_PRIORITY_URGENT_DISPLAY + 3,45 ANDROID_PRIORITY_URGENT_DISPLAY + 2,46 ANDROID_PRIORITY_URGENT_DISPLAY // 10 (MAX_PRIORITY)47};4849void Thread::SetNativePriority(int newPriority) {50 if (newPriority < 1 || newPriority > 10) {51 LOG(WARNING) << "bad priority " << newPriority;52 newPriority = 5;53 }5455 int newNice = kNiceValues[newPriority-1];56 pid_t tid = GetTid(); ...... // 设置线程的调度65 if (newNice >= ANDROID_PRIORITY_BACKGROUND) {66 set_sched_policy(tid, SP_BACKGROUND);67 } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {68 set_sched_policy(tid, SP_FOREGROUND);69 }70 // 设置线程的优先级71 if (setpriority(PRIO_PROCESS, tid, newNice) != 0) {72 PLOG(INFO) << *this << " setPriority(PRIO_PROCESS, " << tid << ", " << newNice << ") failed";73 }74}

Thread.setPriority()的实现最终与Process.setThreadPriority()的实现类似,但是级别的定义略有不同。Thread.setPriority()可以设置10个级别,转换成Linux的nice级别为,

static const int kNiceValues[10] = { 19, 16, 13, 10, 0, -2, -4, -5, -6, -8};优先级管理的关键点

根据上述对实时进程和普通进程的分析,我们可以总结出Android系统中对优先级管理的关键点。

Android系统中存在两种调度策略分别用于实时进程和普通进程:SCHED_FIFO和SCHED_OTHER。

实时进程只是Android系统中非常小的一部分,用于与硬件设备相关的数据传输进程。

普通进程有两种优先级调整接口:android.os.Process.setThreadPriority()和java.lang.Thread.setPriority()。二者的级别定义不同,framework中主要使用setThreadPriority()。

Android应用的默认优先级是THREAD_PRIORITY_DEFAULT,对应Linux内核中的值为120。

Android系统并不希望应用开发随意调整优先级,但并没有在代码中做限制。其目的是希望应用进程不要影响系统的运行。

Android中的优先级别划分似乎主要为framework使用,服务线程创建时可以根据级别设置优先级,而应用启动时都是默认优先级。

Android系统可能根据需要动态调整优先级,例如应用启动时将前台应用的UI线程和Render线程调整到-10。

如果没有充分的考虑,不要调整优先级,否则可能干扰系统运行。

线程Group

Android系统中除了使用优先级来影响进程调度外,还使用的Linux的Cgroup机制来影响线程对cpu资源的使用。先看一下Android在Process中定义的Group类别。

frameworks/base/core/java/android/os/Process.java // 默认Group,仅仅可以在setProcessGroup()中使用,不能用于setThreadGroup()。 // 设置时,小于优先级THREAD_PRIORITY_BACKGROUND的线程被移动到前台线程Group,其他线程不变。369 public static final int THREAD_GROUP_DEFAULT = -1; // 后台线程Group,该Group中线程拥有限制的CPU资源378 public static final int THREAD_GROUP_BG_NONINTERACTIVE = 0; // 前台线程Group,该Group中线程拥有正常的CPU资源387 private static final int THREAD_GROUP_FOREGROUND = 1; // 系统线程Group393 public static final int THREAD_GROUP_SYSTEM = 2; // 应用音频线程Group399 public static final int THREAD_GROUP_AUDIO_APP = 3; // 系统音频线程Group405 public static final int THREAD_GROUP_AUDIO_SYS = 4; // 前台顶层线程Group411 public static final int THREAD_GROUP_TOP_APP = 5; // RT应用线程Group417 public static final int THREAD_GROUP_RT_APP = 6; // 绑定到前台服务的线程Group,关屏时应该限制CPU424 public static final int THREAD_GROUP_RESTRICTED = 7;

虽然Android在framework中定义了许多Group的级别,但在底层设置Cgroup时并没有完全按照这些级别来建立cgroups。Android在底层使用了Cgroup的cpuset和schedtune子系统,根据Group级别进行设置。Cgroup的文件定义以及Group级别对应如下。

fg_cpuset_fd --- "/dev/cpuset/foreground/tasks"bg_cpuset_fd --- "/dev/cpuset/background/tasks"system_bg_cpuset_fd --- "/dev/cpuset/system-background/tasks"ta_cpuset_fd --- "/dev/cpuset/top-app/tasks"rs_cpuset_fd --- "/dev/cpuset/restricted/tasks"ta_schedboost_fd --- "/dev/stune/top-app/tasks"fg_schedboost_fd --- "/dev/stune/foreground/tasks"bg_schedboost_fd --- "/dev/stune/background/tasks"rt_schedboost_fd --- "/dev/stune/rt/tasks"Groupcpusetschedtune

SP_BACKGROUNDbg_cpuset_fdbg_schedboost_fd

SP_FOREGROUND

SP_AUDIO_APP

SP_AUDIO_SYSfg_cpuset_fdfg_schedboost_fd

SP_TOP_APPta_cpuset_fdta_schedboost_fd

SP_SYSTEMsystem_bg_cpuset_fdN/A

SP_RESTRICTEDrs_cpuset_fdN/A

SP_RT_APPN/Art_schedboost_fd

Schedtune设置

schedtune子系统是ARM/Linaro为了EAS新增的一个子系统,主要用来控制进程调度选择CPU以及boost触发。通过权重来分配CPU负载能力来实现快速运行,高权重意味着会享受到更好的cpu负载来处理对应的任务。Android从7.1开始,放弃使用cpu子系统来分配CPU资源,改为使用schedtune子系统。Android的schedtune包含4个cgroups:foreground,background,top-app,rt。其参数可以在device的init.rc中设置,例如Nexus Marlin手机上的设置如下。

device/google/marlin/init.common.rc83 # set default schedTune value for foreground/top-app (only affects EAS)84 write /dev/stune/foreground/schedtune.prefer_idle 185 write /dev/stune/top-app/schedtune.boost 1086 write /dev/stune/top-app/schedtune.prefer_idle 187 write /dev/stune/rt/schedtune.boost 3088 write /dev/stune/rt/schedtune.prefer_idle 1

调整线程schedtune分组的入口有,

在调整线程优先级时,底层会同时使用set_sched_policy()修改。

通过setThreadGroup()和setThreadGroupAndCpuset(),更改单个线程的分组。

通过setProcessGroup(),该接口会改变整个进程中所有线程的分组。例如在调整OomAdj时,会整体迁移进程的分组。

native层直接调用set_sched_policy()来修改。

修改Group的上层接口可能不同,但最终都会使用native层的set_sched_policy()来修改。

system/core/libcutils/sched_policy.cpp362int set_sched_policy(int tid, SchedPolicy policy)363{364 if (tid == 0) {365 tid = gettid();366 }367 policy = _policy(policy);368 pthread_once(&the_once, __initialize); ......414 if (schedboost_enabled()) {415 int boost_fd = -1; // 根据Group获取schedtune的FD416 switch (policy) {417 case SP_BACKGROUND:418 boost_fd = bg_schedboost_fd;419 break;420 case SP_FOREGROUND:421 case SP_AUDIO_APP:422 case SP_AUDIO_SYS:423 boost_fd = fg_schedboost_fd;424 break;425 case SP_TOP_APP:426 boost_fd = ta_schedboost_fd;427 break;428 case SP_RT_APP:429 boost_fd = rt_schedboost_fd;430 break;431 default:432 boost_fd = -1;433 break;434 }435 // 将线程tid加入到schedtune分组436 if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {437 if (errno != ESRCH && errno != ENOENT)438 return -errno;439 }440441 }442 // 设置线程的timerslack_ns,后台进程为40ms,其他为50us443 set_timerslack_ns(tid, policy == SP_BACKGROUND ? TIMER_SLACK_BG : TIMER_SLACK_FG);444445 return 0;446}Cpuset设置

cpuset子系统用来将进程绑定到指定的CPU和内存节点上。Android中cpuset包含5个cgroups:foreground,background,system-background,restricted,top-app。同样看一下Nexus Marlin上的设置,

device/google/marlin/init.common.rc799 # update cpusets now that boot is complete and we want better load balancing800 write /dev/cpuset/top-app/cpus 0-3801 write /dev/cpuset/foreground/cpus 0-2802 write /dev/cpuset/background/cpus 0803 write /dev/cpuset/system-background/cpus 0-2804 write /dev/cpuset/restricted/cpus 0-1

源码上看,Android只有在AMS启动或其他Service启动时将一些线程设置到相应分组上,之后并没有动态调整分组。接口上有java层的setThreadGroupAndCpuset和native层的set_cpuset_policy()。最终的而实现还是set_cpuset_policy()。

system/core/libcutils/sched_policy.cpp281int set_cpuset_policy(int tid, SchedPolicy policy)282{ // 如果不支持cpuset,设置sched283 // in the absence of cpusets, use the old sched policy284 if (!cpusets_enabled()) {285 return set_sched_policy(tid, policy);286 }287288 if (tid == 0) {289 tid = gettid();290 }291 policy = _policy(policy);292 pthread_once(&the_once, __initialize);293294 int fd = -1;295 int boost_fd = -1; // 获取cpuset和schedtune的FD296 switch (policy) {297 case SP_BACKGROUND:298 fd = bg_cpuset_fd;299 boost_fd = bg_schedboost_fd;300 break;301 case SP_FOREGROUND:302 case SP_AUDIO_APP:303 case SP_AUDIO_SYS:304 fd = fg_cpuset_fd;305 boost_fd = fg_schedboost_fd;306 break;307 case SP_TOP_APP :308 fd = ta_cpuset_fd;309 boost_fd = ta_schedboost_fd;310 break;311 case SP_SYSTEM:312 fd = system_bg_cpuset_fd;313 break;314 case SP_RESTRICTED:315 fd = rs_cpuset_fd;316 break;317 default:318 boost_fd = fd = -1;319 break;320 }321 // 增加线程tid到cpuset分组322 if (add_tid_to_cgroup(tid, fd) != 0) {323 if (errno != ESRCH && errno != ENOENT)324 return -errno;325 }326 // 增加线程tid到schedtune分组327 if (schedboost_enabled()) {328 if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {329 if (errno != ESRCH && errno != ENOENT)330 return -errno;331 }332 }333334 return 0;335}

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值