LINUX进程调度之CFS算法
一.概述
Linux自2.6.23以来引入了完全公平调度算法;在调度中通过调度器类sched_class对不同的调度算法做了很好的封装,优先级在0-99的实时进程采用调度器类rt_sched_class,对于优先级在100-139的普通进程采用调度器类fair_sched_class,即为完全公平调度类,即本文重点要阐述的内容,空闲进程使用调度器类idle_sched_class。对于普通进程,O(1)算法中的每个进程都采用时间片的概念来实现调度的判断,当一个进程分配给它的时间片耗尽则产生一次调度时机,以选择新的进程来执行。完全公平调度算法则基本上不依靠时间片来判断,但是它也计算进程的运行时间,但是它是根据由优先级确定的权重来计算可以得到运行时间,与等待队列上等待时间最长的进程比较,来确定进程是否需要调度,调度选择的是等待时间最长的进程。
二.时间的统计
2.1进程权重
在完全公平调度算法中,最重要的是根据优先级确定的权重,以及由权重不同产生的CPU时间。
不同优先级的权重如下:
static constint prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 4904, 3906,
/* -5*/ 3121, 2501, 1991, 1586, 1277,
/* 0*/ 1024, 820, 655, 526, 423,
/* 5*/ 335, 272, 215, 172, 137,
/* 10*/ 110, 87, 70, 56, 45,
/* 15*/ 36, 29, 23, 18, 15,
};
对应优先级从100-139(也可以说是从nice值-20-19),其权重基准是0对应1024,从0提高到1,权重减少10%,从0到-1,即优先级提高一个等级,权重增加10%的CPU时间。
就绪进程有个权重的总和,当有进程启动或者加入到队列里,系统就会根据每个进程的权重算出总的权重,留待后面使用;反之,当有进程没有就绪则将其权重从总和中减掉,可参考函数inc_nr_running()和dec_nr_running()
2.2时钟统计
在进程创建的时候要初始化几个时间变量:
static void __sched_fork(struct task_struct*p)
{
p->se.exec_start = 0; //进程开始运行时间
p->se.sum_exec_runtime = 0;//本次总的运行时间
p->se.prev_sum_exec_runtime = 0;//上次总的运行时间,该值在调度到另外一个进程时由当前的运行时间赋值,即se->prev_sum_exec_runtime = se->sum_exec_runtime;
}
在时钟中断函数中会周期执行task_tick_fair()函数,里面统计一些时间变量。
static voidupdate_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr =cfs_rq->curr;
u64 now = rq_of(cfs_rq)->clock; //取得当前时间
unsigned long delta_exec;
……
delta_exec = (unsigned long)(now -curr->exec_start); //进程执行的时间
__update_curr(cfs_rq, curr, delta_exec);
curr->exec_start = now; //更新进程开始的运行时间
…..
}
static inlinevoid
__update_curr(structcfs_rq *cfs_rq, struct sched_entity *curr,
unsigned long delta_exec)
{
……
curr->sum_exec_runtime += delta_exec;
……
delta_exec_weighted = delta_exec;//从时钟计算中得出的运行时间
if (unlikely(curr->load.weight !=NICE_0_LOAD)) {
delta_exec_weighted =calc_delta_fair(delta_exec_weighted,
&curr->load);//考虑了权重以后的运行时间
}
curr->vruntime += delta_exec_weighted; //该值判断进程在红黑树中的位置,该值越小,越靠近左边,越容易被重新调度到,反之,越到,越是右移,调度机会变小
}
在时钟周期中统计完时间后,就要判断该进程运行时间是否已经到达分配给它的份额:
static void
check_preempt_tick(structcfs_rq *cfs_rq, struct sched_entity *curr)
{
unsigned long ideal_runtime, delta_exec;
ideal_runtime = sched_slice(cfs_rq,curr);//计算当前进程允许占用的时间。
delta_exec = curr->sum_exec_runtime -curr->prev_sum_exec_runtime;
if (delta_exec > ideal_runtime)
resched_task(rq_of(cfs_rq)->curr);//设立调度标记
}