随笔之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