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, ¶m)
- 如果支持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_SYS
96 if (Binder.getCallingUid() != Process.BLUETOOTH_UID) {
97 try {
98 // make good use of our CAP_SYS_NICE capability
99 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 constraint
108 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.cpp
493void 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.cpp
299int 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 priority
1085 // 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};
48
49void Thread::SetNativePriority(int newPriority) {
50 if (newPriority < 1 || newPriority > 10) {
51 LOG(WARNING) << "bad priority " << newPriority;
52 newPriority = 5;
53 }
54
55 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;
// 系统线程Group
393 public static final int THREAD_GROUP_SYSTEM = 2;
// 应用音频线程Group
399 public static final int THREAD_GROUP_AUDIO_APP = 3;
// 系统音频线程Group
405 public static final int THREAD_GROUP_AUDIO_SYS = 4;
// 前台顶层线程Group
411 public static final int THREAD_GROUP_TOP_APP = 5;
// RT应用线程Group
417 public static final int THREAD_GROUP_RT_APP = 6;
// 绑定到前台服务的线程Group,关屏时应该限制CPU
424 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"
Group | cpuset | schedtune |
---|---|---|
SP_BACKGROUND | bg_cpuset_fd | bg_schedboost_fd |
SP_FOREGROUND SP_AUDIO_APP SP_AUDIO_SYS |
fg_cpuset_fd | fg_schedboost_fd |
SP_TOP_APP | ta_cpuset_fd | ta_schedboost_fd |
SP_SYSTEM | system_bg_cpuset_fd | N/A |
SP_RESTRICTED | rs_cpuset_fd | N/A |
SP_RT_APP | N/A | rt_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.rc
83 # set default schedTune value for foreground/top-app (only affects EAS)
84 write /dev/stune/foreground/schedtune.prefer_idle 1
85 write /dev/stune/top-app/schedtune.boost 10
86 write /dev/stune/top-app/schedtune.prefer_idle 1
87 write /dev/stune/rt/schedtune.boost 30
88 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.cpp
362int 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的FD
416 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 }
440
441 }
442
// 设置线程的timerslack_ns,后台进程为40ms,其他为50us
443 set_timerslack_ns(tid, policy == SP_BACKGROUND ? TIMER_SLACK_BG : TIMER_SLACK_FG);
444
445 return 0;
446}
Cpuset设置
cpuset子系统用来将进程绑定到指定的CPU和内存节点上。Android中cpuset包含5个cgroups:foreground,background,system-background,restricted,top-app。同样看一下Nexus Marlin上的设置,
device/google/marlin/init.common.rc
799 # update cpusets now that boot is complete and we want better load balancing
800 write /dev/cpuset/top-app/cpus 0-3
801 write /dev/cpuset/foreground/cpus 0-2
802 write /dev/cpuset/background/cpus 0
803 write /dev/cpuset/system-background/cpus 0-2
804 write /dev/cpuset/restricted/cpus 0-1
源码上看,Android只有在AMS启动或其他Service启动时将一些线程设置到相应分组上,之后并没有动态调整分组。接口上有java层的setThreadGroupAndCpuset和native层的set_cpuset_policy()。最终的而实现还是set_cpuset_policy()。
system/core/libcutils/sched_policy.cpp
281int set_cpuset_policy(int tid, SchedPolicy policy)
282{
// 如果不支持cpuset,设置sched
283 // in the absence of cpusets, use the old sched policy
284 if (!cpusets_enabled()) {
285 return set_sched_policy(tid, policy);
286 }
287
288 if (tid == 0) {
289 tid = gettid();
290 }
291 policy = _policy(policy);
292 pthread_once(&the_once, __initialize);
293
294 int fd = -1;
295 int boost_fd = -1;
// 获取cpuset和schedtune的FD
296 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 }
333
334 return 0;
335}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。