Android中关于cpu/cpuset/schedtune的应用都是基于进程优先级的,根据不同优先级划分进程类型。AMS(ActivityManagerService)和PMS(PackageManagerService)等通过class Process设置进程优先级、调度策略等;android/osProcess JNI通过调用libcutils.so/libutils.so执行getpriority/setpriority/sched_setscheduler/sched_getschedler系统调用或者直接操作CGroup文件节点以达到设置优先级,限制进程CPU资源的目的。
根据优先级,通过设置CGroup的cpu/cpuset/stune控制进程获得CPU执行时间、可调度CPU范围等,以达到对不同优先级进程的控制。
Android关于cpu/cpuset/schedtune的框架结构
进程优先级和调度策略从上到下贯穿其中,但是在不同的层级的名称有一些变化。下面逐一介绍。
class Process以及android/os/Process JNI
frameworks/base/core/java/android/os/Process.java
其他服务通过class Process来设置进程优先级、调度侧率等。
class Process中优先级划分:
public static final int THREAD_PRIORITY_DEFAULT = 0; 应用的默认优先级
/*
* ***************************************
* ** Keep in sync with utils/threads.h **
* ***************************************
*/
public static final int THREAD_PRIORITY_LOWEST = 19; 线程的最低优先级
public static final int THREAD_PRIORITY_BACKGROUND = 10; 后台线程的默认优先级
public static final int THREAD_PRIORITY_FOREGROUND = -2; 前台进程的标准优先级
public static final int THREAD_PRIORITY_DISPLAY = -4; 系统用于显示功能的优先级
public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; 系统用于重要显示功能的优先级
public static final int THREAD_PRIORITY_AUDIO = -16; 音频线程默认优先级
public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; 重要音频线程默认优先级
调度策略划分:
public static final int SCHED_OTHER = 0; 默认调度策略,对应CFS调度类
public static final int SCHED_FIFO = 1; FIFO调度策略,对应RT调度类
public static final int SCHED_RR = 2; RR调度策略,对应RT调度类
public static final int SCHED_BATCH = 3; 批调度策略,对应CFS调度类
public static final int SCHED_IDLE = 5; idle调度策略
class Process相关API,主要用于:
public static final native void setThreadPriority(int tid, int priority)
throws IllegalArgumentException, SecurityException;
public static final native void setThreadScheduler(int tid, int policy, int priority)
throws IllegalArgumentException;
public static final native void setThreadPriority(int tid, int priority)
throws IllegalArgumentException, SecurityException;
public static final native int getThreadPriority(int tid)
throws IllegalArgumentException;
public static final native int getThreadScheduler(int tid)
throws IllegalArgumentException;
public static final native void setThreadGroup(int tid, int group)
throws IllegalArgumentException, SecurityException;
public static final native void setProcessGroup(int pid, int group)
throws IllegalArgumentException, SecurityException;
frameworks/base/core/jni/android_util_Process.cpp
对应JNINativeMethod如下:
static const JNINativeMethod methods[] = {
…
{"setThreadPriority", "(II)V", (void*)android_os_Process_setThreadPriority},
{"setThreadScheduler", "(III)V", (void*)android_os_Process_setThreadScheduler},
{"setCanSelfBackground", "(Z)V", (void*)android_os_Process_setCanSelfBackground},
{"setThreadPriority", "(I)V", (void*)android_os_Process_setCallingThreadPriority},
{"getThreadPriority", "(I)I", (void*)android_os_Process_getThreadPriority},
{"getThreadScheduler", "(I)I", (void*)android_os_Process_getThreadScheduler},
{"setThreadGroup", "(II)V", (void*)android_os_Process_setThreadGroup},
{"setProcessGroup", "(II)V", (void*)android_os_Process_setProcessGroup},
{"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup},
…
};
scheduler相关API直接调用sched_setscheduler/sched_getscheduler。
libcutils.so/libutils.so
在介绍这个函数之前先介绍一下此处所使用的优先级定义,可以看出和class Process中是完全的对应关系:
ANDROID_PRIORITY_LOWEST = 19,
/* use for background tasks */
ANDROID_PRIORITY_BACKGROUND = 10,
/* most threads run at normal priority */
ANDROID_PRIORITY_NORMAL = 0,
/* threads currently running a UI that the user is interacting with */
ANDROID_PRIORITY_FOREGROUND = -2,
/* the main UI thread has a slightly more favorable priority */
ANDROID_PRIORITY_DISPLAY = -4,
/* ui service treads might want to run at a urgent display (uncommon) */
ANDROID_PRIORITY_URGENT_DISPLAY = HAL_PRIORITY_URGENT_DISPLAY,
/* all normal audio threads */
ANDROID_PRIORITY_AUDIO = -16,
/* service audio threads (uncommon) */
ANDROID_PRIORITY_URGENT_AUDIO = -19,
/* should never be used in practice. regular process might not
* be allowed to use this level */
ANDROID_PRIORITY_HIGHEST = -20,
ANDROID_PRIORITY_DEFAULT = ANDROID_PRIORITY_NORMAL,
还需要在研究一下,Sched Policy中使用的优先级映射关系:
/* Keep in sync with THREAD_GROUP_* in frameworks/base/core/java/android/os/Process.java */
typedef enum {
SP_DEFAULT = -1,
SP_BACKGROUND = 0,
SP_FOREGROUND = 1,
SP_SYSTEM = 2, // can't be used with set_sched_policy()
SP_AUDIO_APP = 3,
SP_AUDIO_SYS = 4,
SP_TOP_APP = 5,
SP_CNT,
SP_MAX = SP_CNT - 1,
SP_SYSTEM_DEFAULT = SP_FOREGROUND,
} SchedPolicy;
Threads.cpp中定义了androidSetThreadPriority用于设置线程的优先级。
int androidSetThreadPriority(pid_t tid, int pri)
{
int rc = 0;
int lasterr = 0;
if (pri >= ANDROID_PRIORITY_BACKGROUND) { 如果priority大于等于BACKGROUND,则设置为BACKGROUND类型的调度策略。
rc = set_sched_policy(tid, SP_BACKGROUND);
} else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) { 如果priority小于BACKGROUND,且当线程为BACKGROUND类型,则设置为FOREGROUND类型。
rc = set_sched_policy(tid, SP_FOREGROUND);
}
if (rc) {
lasterr = errno;
}
if (setpriority(PRIO_PROCESS, tid, pri) < 0) { 设置优先级
rc = INVALID_OPERATION;
} else {
errno = lasterr;
}
return rc;
}
set_cpuset_policy根据SchedPolicy类型将tid写入cpuset和schedtune子系统中。
有下面的函数可以得出cpuset、schedtune和不同类型SchedPolicy之间的对应关系:
/dev/cpuset/foreground/tasks SP_FOREGROUND SP_AUDIO_APP SP_AUDIO_SYS
/dev/cpuset/background/tasks SP_BACKGROUND
/dev/cpuset/system-background/tasks SP_SYSTEM
/dev/cpuset/top-app/tasks SP_TOP_APP
/dev/stune/top-app/tasks SP_TOP_APP
/dev/stune/foreground/tasks SP_FOREGROUND SP_AUDIO_APP SP_AUDIO_SYS
/dev/stune/background/tasks SP_BACKGROUND
int set_cpuset_policy(int tid, SchedPolicy policy)
{
// in the absence of cpusets, use the old sched policy
#ifndef USE_CPUSETS
return set_sched_policy(tid, policy);
#else
if (tid == 0) {
tid = gettid();
}
policy = _policy(policy);
pthread_once(&the_once, __initialize);
int fd = -1;
int boost_fd = -1;
switch (policy) {
case SP_BACKGROUND:
fd = bg_cpuset_fd;
boost_fd = bg_schedboost_fd;
break;
case SP_FOREGROUND:
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
fd = fg_cpuset_fd;
boost_fd = fg_schedboost_fd;
break;
case SP_TOP_APP :
fd = ta_cpuset_fd;
boost_fd = ta_schedboost_fd;
break;
case SP_SYSTEM:
fd = system_bg_cpuset_fd;
break;
default:
boost_fd = fd = -1;
break;
}
if (add_tid_to_cgroup(tid, fd) != 0) {
if (errno != ESRCH && errno != ENOENT)
return -errno;
}
#ifdef USE_SCHEDBOOST
if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
if (errno != ESRCH && errno != ENOENT)
return -errno;
}
#endif
return 0;
#endif
}
set_sched_policy设置cpu/schedtune两个子系统,子系统节点和SchedPolicy类型对应如下:
/dev/cpuctl/tasks SP_FOREGROUND SP_AUDIO_APP SP_AUDIO_SYS
/dev/cpuctl/bg_non_interactive/tasks SP_BACKGROUND
/dev/stune/top-app/tasks SP_TOP_APP
/dev/stune/foreground/tasks SP_FOREGROUND SP_AUDIO_APP SP_AUDIO_SYS
/dev/stune/background/tasks SP_BACKGROUND
int set_sched_policy(int tid, SchedPolicy policy)
{
if (tid == 0) {
tid = gettid();
}
policy = _policy(policy);
pthread_once(&the_once, __initialize);
#if POLICY_DEBUG
char statfile[64];
char statline[1024];
char thread_name[255];
snprintf(statfile, sizeof(statfile), "/proc/%d/stat", tid);
memset(thread_name, 0, sizeof(thread_name));
int fd = open(statfile, O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
int rc = read(fd, statline, 1023);
close(fd);
statline[rc] = 0;
char *p = statline;
char *q;
for (p = statline; *p != '('; p++);
p++;
for (q = p; *q != ')'; q++);
strncpy(thread_name, p, (q-p));
}
switch (policy) {
case SP_BACKGROUND:
SLOGD("vvv tid %d (%s)", tid, thread_name);
break;
case SP_FOREGROUND:
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
case SP_TOP_APP:
SLOGD("^^^ tid %d (%s)", tid, thread_name);
break;
case SP_SYSTEM:
SLOGD("/// tid %d (%s)", tid, thread_name);
break;
default:
SLOGD("??? tid %d (%s)", tid, thread_name);
break;
}
#endif
if (__sys_supports_schedgroups) { 是否使能schedtune CGroup
int fd = -1;
int boost_fd = -1;
switch (policy) {
case SP_BACKGROUND:
fd = bg_cgroup_fd;
boost_fd = bg_schedboost_fd;
break;
case SP_FOREGROUND:
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
fd = fg_cgroup_fd;
boost_fd = fg_schedboost_fd;
break;
case SP_TOP_APP:
fd = fg_cgroup_fd;
boost_fd = ta_schedboost_fd;
break;
default:
fd = -1;
boost_fd = -1;
break;
}
if (add_tid_to_cgroup(tid, fd) != 0) {
if (errno != ESRCH && errno != ENOENT)
return -errno;
}
#ifdef USE_SCHEDBOOST
if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
if (errno != ESRCH && errno != ENOENT)
return -errno;
}
#endif
} else { 如果没有使能schedtune CGroup,则使用系统调用sched_setscheduler设置为SCHED_BATCH或者SCHED_NORMAL
struct sched_param param;
param.sched_priority = 0;
sched_setscheduler(tid,
(policy == SP_BACKGROUND) ?
SCHED_BATCH : SCHED_NORMAL,
¶m);
}
if (__sys_supports_timerslack) {
set_timerslack_ns(tid, policy == SP_BACKGROUND ?
TIMER_SLACK_BG : TIMER_SLACK_FG);
}
return 0;
}