理解这种PM模型的关键在占据50%CPU这句话上,对于那种普通调度模型中,在一定时间内,进程占据了100%CPU。而在PM模型中,进程占据CPU却是1/n。
好了,CFS具体是实现这个模型的呢?
之前每个进程分配的固定时间的算法现在被替代成每个进程运行的时间将是全体进程个数的函数。这个对应为上面模型描述中的1/n。 nice值不再直接对应时间片。在CFS中,nice值对应一个比重。优先级越高的进程,该比重越大。 有了这些参数,每个进程运行的“timeslice“将是它的比重和那个n(表示当前系统内所有处于可执行状态的进程/线程)的函数。
注意,Linux内核中,进程和线程用同一个结构体表示,所以线程也叫轻量级进程。调度的时候是不区分理论上的进程和线程的。
尽管CFS尽力避开以时间作为调度单位,但是实现中还是需要时间作为资源使用的考量。那么CFS将这个时间称之为target latency。用来对应PM模型中那个infinitly small schedule duration time。假设该值是20ms,那么2个同优先级进程运行时,每个进程使用10ms,4个同优先级进程的时候每个使用5ms。随着进程数增加,每个进程的时间会趋向于0。那么CFS的实现中,增加了一个minimum granularity,这个时间是最小时间,目前为1ms。上面这些理论还是有点搞不清楚,下面我们看个例子。
一个0级进程和一个5级进程。最后计算出来5级进程的比重将是0级进程的1/3。那么20ms的target latency情况下,0级进程分配15ms,5级进程分配5ms。此时nice值不再直接对应时间值,而仅仅是一个相对CPU时间的比重。所以,假设这两个进程一个是10级,一个是15级,最后计算出来的时间也是15ms和5ms。
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实际上:
设置该进程的调度算法类为fair_sched_class 根据policy,设置该进程的weight。(这个weight的作用,以后在介绍kernel sched的时候再来讨论)四 总结
本随笔从Java层提供的进程调度API开始,介绍了Linux OS上的进程调度相关的知识。对于那些仅需要知道工作原理的人来说,这些知识应该是足够了。
最难理解的还是那个PM模型,希望以后有机会去看看原始的论文。
下面是本随笔的参考文章:
[1] Linux Kernel Development 3rd Edition,chapter 4