1

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_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}

戈壁老王
143 声望61 粉丝

做为一个不称职的老年码农,一直疏忽整理笔记,开博记录一下,用来丰富老年生活,


« 上一篇
Cgroup 用法