随笔之Android平台上的进程调度探讨
一由来
最近在翻阅MediaProvider的时候,突然想起之前碰到的一个问题,该问题是这样的:
当时看到这个现象,直观感觉就是MediaProvider抢占CPU能力不够。直接把该现象告诉领导,这个事情也就结了。但是一直没在代码中找到依据:总有地方设置进程的优先级吧??
后来,时间充裕了,想起这个问题。果不其然,在MediaScannerService中,找到答案:
public void run()
{
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +
Process.THREAD_PRIORITY_LESS_FAVORABLE);
……
}
上面代码显示得将本进程的优先级设置为BACKGROUND+LESS_FAVORABLE。
那么这个优先级是什么呢?
本随笔将关注两个问题:
二 Android平台中的进程调度接口
从最上的Java层看,Anroid提供的Process类封装了进程调度优先级,调度策略等一些API。下图是整体调用流程和相关文件位置。
图1 进程调度的API以及调用分发流程
从上图我们可知:
进程调度的优先级,这个应该不难理解。简单地说:
l OS在调度进程的时候是遵循一定规则的,优先级高的进程分配CPU的时间多,而优先级低的进程相对分配的CPU时间少。(这个仅是理论上的,具体如何分配是和OS相关的)
下面我们看看androidSetThreadPriority的实现。
int androidSetThreadPriority(pid_t tid, int pri)
{
#if defined(HAVE_PTHREADS) //目前仅支持POSIX
//phtread_once保证这个线程创建时会首先执行一次(仅此一次,类似Class的constructor)
//checkDoSchedulingGroup函数,该函数判断系统是否设置了”debug.sys.noschedgroups”
pthread_once(&gDoSchedulingGroupOnce, checkDoSchedulingGroup);
if (gDoSchedulingGroup) {
if (pri >= ANDROID_PRIORITY_BACKGROUND) {
//设置调度策略。这个我们待会会碰到。
rc = set_sched_policy(tid, SP_BACKGROUND);
} else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
rc = set_sched_policy(tid, SP_FOREGROUND);
}
}
……
//设置调度优先级。
if (setpriority(PRIO_PROCESS, tid, pri) < 0) {
rc = INVALID_OPERATION;
}
……
}
从上面代码发现,Android直接调用了系统API setpriority,其中有三个参数,其原型是:
int setpriority(int which, int who, int prio);
看来,设置进程调度优先级还是很简单的嘛!
Linux的进程调度除了简单的设置nice值外,还可以设置调度策略。这个问题我们放到最后讨论。先来看看上层API。
int androidSetThreadSchedulingGroup(pid_t tid, int grp)
{
……
#if defined(HAVE_PTHREADS)
……
if (set_sched_policy(tid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
SP_BACKGROUND : SP_FOREGROUND)) {
#endif
}
int set_sched_policy(int tid, SchedPolicy policy)
{
pthread_once(&the_once, __initialize);//先运行__initialize函数
#if POLICY_DEBUG
……
#endif
//看看系统是否支持调度策略
if (__sys_supports_schedgroups) {
if (add_tid_to_cgroup(tid, policy)) {
……
}
} else {
struct sched_param param; //调度参数,类型为sched_param
param.sched_priority = 0;
sched_setscheduler(tid,//如果系统不支持的话,直接调用OS的调度策略设置API
(policy == SP_BACKGROUND) ?
SCHED_BATCH : SCHED_NORMAL,
¶m);
}
}
initialize这个函数很关键,这里的__sys_supports_schedgroups用来检查android系统,而不是OS。
static void __initialize(void) {
char* filename;
//如果android手机上有/dev/cpuctl/tasks文件,则__sys_supports_schedgroups为1
//目前我的索爱手机并没有该文件。
if (!access("/dev/cpuctl/tasks", F_OK)) {
__sys_supports_schedgroups = 1;
filename = "/dev/cpuctl/tasks";
normal_cgroup_fd = open(filename, O_WRONLY);
……
filename = "/dev/cpuctl/bg_non_interactive/tasks";
bg_cgroup_fd = open(filename, O_WRONLY);
……
} else {
__sys_supports_schedgroups = 0;
}
}
目前还没有找到解释/dev/cpuctl/tasks这个特殊文件的地方。有明白的网友请不吝赐教.
下面我们看看linux提供的设置调度策略的函数,其原型是:
int sched_setscheduler(pid_t pid, int policy,const struct sched_param *param);
从前面可以看出,Android上进程调度还是依赖OS提供的调度机制。当然上层API还是比较简单易懂的,但是Linux OS调度到底是怎么样的呢?不妨探讨一下。
三 Linux OS进程调度机制探讨
这里将探讨一下Linux OS的进程调度实现的原理,具体代码就不深挖了。
先来说说一些基础知识:
当然,进程调度是一套复杂的算法,图2展示了Linux上进程调度算法的演化历史。
图2 Linux进程调度算法演化史
1. 调度算法
我们先介绍下和进程调度息息相关的进程优先级相关的知识。
那么,在Linux中,这个调度优先级是通过nice值来反映的,从-20到19。值越大表示该进程越nice,这个nice是对其他进程的nice,所以优先级越低。
再解决优先级的概念后,我们再来看第二个基本问题:CPU资源:time-slice是什么?以及它如何与优先级结合起来?
这种以时间(一般为ms)作为时间片单位,并且将时间片与优先级直接映射的方式有什么缺点呢?
我们考虑二个例子:
那么Linux的CFS(Completely Fair Scheduler)算法是怎么做的呢?
CFS基于一种叫perfect multitasking模型来构建调度算法。
上面这段话实在不太好理解。我们举个例子:
理解这种PM模型的关键在占据50%CPU这句话上,对于那种普通调度模型中,在一定时间内,进程占据了100%CPU。而在PM模型中,进程占据CPU却是1/n。
好了,CFS具体是实现这个模型的呢?
注意,Linux内核中,进程和线程用同一个结构体表示,所以线程也叫轻量级进程。调度的时候是不区分理论上的进程和线程的。
上面这些理论还是有点搞不清楚,下面我们看个例子。
PM这个模型还是没说清楚,目前根据《Linux Kernel Development》只能得出上面的结论。
2. 调度策略和调度class
sched_setscheduler函数用来设置进程的调度策略。
在实际的内核代码中,我们发现在选择下一个可运行的进程时,存在这一个for循环:
[->kernel/sched.c]
static inline struct task_struct * pick_next_task(struct rq *rq)
{
const struct sched_class *class;
struct task_struct *p;
//调度算法是一个集合,每次使用优先级最高的调度算法。这个算法类在代码中用
//sched_class表示
class = sched_class_highest;
for ( ; ; ) {
p = class->pick_next_task(rq);
if (p)
return p;
class = class->next;
}
}
Linux内核目前使用了两个调度算法类,一个是fair_sched_class,对应于非实时的调度。另外一个是rt_sched_class,对应于实时调度算法。从上面的函数中可知:
按照刚才所说,一共只有两个调度算法,那么我们在sched_setscheduler设置的BATCH/IDLE/NORMAL又有什么用呢?
static void
__setscheduler(struct rq *rq, struct task_struct *p, int policy, int prio)
{
……
if (rt_prio(p->prio))
p->sched_class = &rt_sched_class;
else //IDLE/BATCH/NORMAL等设置的都是fair_sched_class
p->sched_class = &fair_sched_class;
set_load_weight(p);
}
//set_load_weight就是根据policy(NORMAL/IDLE/BATCH),得到一个比重
static void set_load_weight(struct task_struct *p)
{
if (task_has_rt_policy(p)) {
p->se.load.weight = prio_to_weight[0] * 2;
p->se.load.inv_weight = prio_to_wmult[0] >> 1;
return;
}
if (p->policy == SCHED_IDLE) {
p->se.load.weight = WEIGHT_IDLEPRIO;
p->se.load.inv_weight = WMULT_IDLEPRIO;
return;
}
p->se.load.weight = prio_to_weight[p->static_prio - MAX_RT_PRIO];
p->se.load.inv_weight = prio_to_wmult[p->static_prio - MAX_RT_PRIO];
}
根据我们前面所说,再结合上面的代码,sched_setscheduler实际上:
四 总结
本随笔从Java层提供的进程调度API开始,介绍了Linux OS上的进程调度相关的知识。对于那些仅需要知道工作原理的人来说,这些知识应该是足够了。
最难理解的还是那个PM模型,希望以后有机会去看看原始的论文。
下面是本随笔的参考文章:
[1] Linux Kernel Development 3rd Edition,chapter 4
[2] http://en.wikipedia.org/wiki/Completely_Fair_Scheduler