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

更多相关文章

  1. Android系统分析之Activity的启动流程
  2. Android的内存管理
  3. android init.rc中的service
  4. Android(安卓)9 (P)之init进程启动源码分析指南之三
  5. Android线程学习总结
  6. Android面试题精选,自己收藏下
  7. Android中的soundpool小结
  8. Android(安卓)操作系统的内存回收机制
  9. Android消息处理框架:Looper,Handler,MessageQueue

随机推荐

  1. Android(安卓)由图片资源ID获取图片的文
  2. android 签名和adb命令(时刻更新)
  3. android 手机虚拟按键 震动过程的追溯(1)
  4. Android面试题集锦(四)
  5. Android事件分发机制与嵌套导致触摸事件
  6. Android(安卓)EditView属性解析
  7. Android(安卓)筆記-Linux Kernel SMP (Sy
  8. Android从App跳转到微信小程序,无需微信SD
  9. Android(安卓)6.0 默认桌面壁纸修改
  10. Android应用程序组件Content Provider应