内容概要:对于Android线程所属的CGroup,一些资料认为是通过线程优先级来进行划分的。但经实践,发现部分Android版本与该观点并不一致,那么实际情况又是怎样的呢?本篇文章就该问题进行了探讨。

一、CGroup简要介绍

在Linux中,不同线程分配cpu时间片的策略首先是基于线程优先级的,线程优先级越高,越容易分配到cpu。但是这样就产生了低优先级线程一直都被抢占cpu时间的问题,为解决该问题,Linux 2.6.23版本中引入了CFS策略,该策略不但会参考单个线程的优先级,还会追踪每个线程已经获取到的time slice数量,如果高优先级的线程已经执行了很长时间,但低优先级的线程一直在等待,后续系统会保证低优先级的线程也能获取更多的CPU时间。但这就产生了新的问题:优先级高的线程并不一定总能在争取时间片上有绝对的优势,反映在Android中,就会出现UI线程被后台线程抢占cpu时间的问题。所以在Linux 2.6.24中又引入了cgroups的概念,让从属于特定CGroup的线程能够占据更多的时间片而不被低线程抢占,从而提升了总体效率

[1]。

二、Android中不同版本所属CGroup调研

在Android中,存在两类特别重要的CGroup,一类是foreground group,UI线程属于这一类。另一类是background group,工作线程属于这一类。那么你可能要问,线程所属foreground group和background group到底怎样划分呢?查阅API文档以及相关资料

[2],当我们使用setThreadPriority,nice值大于等于THREAD_PRIORITY_BACKGROUND将属于background group,其余属于foreground group。

但是,这种说法正确嘛?让我们来做个实验,首先通过以下代码设置线程优先级。

private static final ThreadFactory sDBThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(@NonNull Runnable r){
WorkerRunnable wr = new WorkerRunnable(r, Process.THREAD_PRIORITY_BACKGROUND);
return new Thread(wr, "Async DB Thread #" + mCount.getAndIncrement());
}
};
private static class WorkerRunnable implements Runnable{
Runnable runnable;
int priority;
String tag;
public WorkerRunnable(Runnable runnable, int priority){
this(runnable, priority, "WorkerRunnable");
}
public WorkerRunnable(Runnable runnable, int priority, String tag){
this.runnable = runnable;
this.priority = priority;
this.tag = tag;
}
@Override
public void run(){
android.os.Process.setThreadPriority(priority);
runnable.run();
}
@Override
public String toString(){
return tag + ":" + runnable;
}
}

在Android5.1.0中当线程优先级为Process.THREAD_PRIORITY_BACKGROUND的Async DB Thread所属的进程处于前台时,其cgroup却为fg,当所属进程进入到后台时,其cgroup又为bg:

Android 高通CPU调度 android cpu调度策略_Android 高通CPU调度

Android 高通CPU调度 android cpu调度策略_线程优先级_02

而在Android 4.1中,无论是否处于前台,其cgroup都为bg:

Android 高通CPU调度 android cpu调度策略_Android_03

看来,实际情况是和版本有关,那么线程的cgroup是具体怎样设置的呢?查阅相关源代码

[3],找到是在sched_policy.c的get_sched_policy方法进行相关设置的,这里以Android4.1为例,其他版本逻辑基本一致,除了Android7.0

[4],增加了一层宏定义判断#ifdef USE_CPUSETS。

int get_sched_policy(int tid, SchedPolicy *policy)
{
#ifdef HAVE_GETTID
if (tid == 0) {
tid = gettid();
}
#endif
pthread_once(&the_once, __initialize);
if (__sys_supports_schedgroups) {
char grpBuf[32];
if (getSchedulerGroup(tid, grpBuf, sizeof(grpBuf)) < 0)
return -1;
if (grpBuf[0] == '\0') {
*policy = SP_SYSTEM;
} else if (!strcmp(grpBuf, "apps/bg_non_interactive")) {
*policy = SP_BACKGROUND;
} else if (!strcmp(grpBuf, "apps")) {
*policy = SP_FOREGROUND;
} else {
errno = ERANGE;
return -1;
}
} else {
int rc = sched_getscheduler(tid);
if (rc < 0)
return -1;
else if (rc == SCHED_NORMAL)
*policy = SP_FOREGROUND;
else if (rc == SCHED_BATCH)
*policy = SP_BACKGROUND;
else {
errno = ERANGE;
return -1;
}
}
return 0;
}

上述代码基本逻辑概要如下:

Android 高通CPU调度 android cpu调度策略_Android_04

该方法首先查看变量__sys_supports_schedgroups是否为true,

if (!access("/dev/cpuctl/tasks", F_OK)) {
__sys_supports_schedgroups = 1;

} 也就是查看/dev/cpuctl/tasks是否存在:当/dev/cpuctl/tasks存在时,access返回0,满足条件__sys_supports_schedgroups为1。

那么Android5.1满足什么样的条件呢?实际上,在Android5.1的init.rc

[5]中会创建了该tasks文件,但4.1.1

[6]不存在该文件。那么5.1.0系统就使用getSchedulerGroup得到cgroup,也就是查找/proc/线程id/cgroup文件中的2:cpu:/一行的内容。

当整个进程在前台时,其2:cpu:/的值为空,而处于后台时,则为bg_non_interactive。

Android 高通CPU调度 android cpu调度策略_android c 多线程_05

而/proc//cgroup 文件中的2:cpu:的值又是怎样写进去的呢?参考Linux源码cgroup.c中的处理

[7]和相关资料

[8],发现创建的子线程也是同样用的所属进程的cgroup。

接下来是Android4.1.1,他直接使用sched_getscheduler,也就是我们之前sched_setscheduler设置的policy:

sched_setscheduler(tid, (policy == SP_BACKGROUND) ? SCHED_BATCH : SCHED_NORMAL, ¶m); 而sched_setscheduler则是由set_sched_policy调用:

if (gDoSchedulingGroup) {
if (prio >= ANDROID_PRIORITY_BACKGROUND) {
set_sched_policy(androidGetTid(), SP_BACKGROUND);
} else if (prio > ANDROID_PRIORITY_AUDIO) {
set_sched_policy(androidGetTid(), SP_FOREGROUND);
} else {
// defaults to that of parent, or as set by requestPriority()
}
}

也就是通过线程的优先级来设置所属CGroup。

上面提到的/dev/cpuctl/tasks和在Android7.0中引入的/dev/cpuset/等文件的创建在init.rc中进行定义,不同的版本有不同的定义,这里查阅不同版本的源码,做一下Android不同版本的CGroup的实际情况的总结:

Android 4.0 Ice Cream Sandwich,根据线程优先级设置cgroup

[9]

Android 4.1/4.2/4.3 Jelly Bean,根据线程优先级设置cgroup

[6]

Android 4.4 KitKat,根据线程优先级设置cgroup

[10]

Android 5.0/5.1 Lollipop,取所属进程的cgroup值

[5]

Android 6.0 Marshmallow,取所属进程的cgroup值

[11]

Android 7.0 Nougat,取所属进程的cgroup值