在讨论了采用CFS调度算法的动机和内部逻辑以后,可以适当探索CFS的部分具体实现。相关代码位于文件kernel/sched_fair.c当中。可以查看其中四个组成部分。
·时间记账
·进程选择
·调度器入口
·睡眠和唤醒
1.5.1 时间记账:
调度器要为每个进程做时间记账,在多数的Unix系统当中,分配一个时间片给每一个进程。进程的节拍周期随着系统的节拍不断减少。一个进程的时间片被减少到0时,他就会被另一个尚未被减到零的时间片进程抢占。
1.调度器的实体结构
CFS维护进程运行的时间记账,确保进程在给予他的处理器时间内运行。调度器的实体结构(定义在
struct sched_eneity
{
struct load_weigt load;
struct eb_node run_node;
struct list_head group_node;
unsigned int on_rq;
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
u64 prev_sum_exec_runtime;
u64 last_wakeup;
u64 avg_operlap;
u64 nr_migrations;
u64 start_runtime;
u64 avg_wakeup;
//这里省略了很多统计变量,只有在设置了CONFIG_SCHEDSTATS时才启用这些变量
}
调度器的实体结构作为一个名为se的成员变量,潜入在进程描述符 struct task_struct 内。
2.虚拟实时
vruntime变量存放进程的虚拟运行时间,CFS使用vruntime变量来记录一个程序到底运行了多长时间以及他还应该运行多久。
定义在kernel/sched_fair.c文件中的update_cuurr()函数实现了记账的功能。
static void update_surr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr =cfs_rq->curr;
u64 now = rq_of(cfs_rq)->clock;
unsigned long delta_exec;
if(unlikely(!curr))
/*获得从最后一次修改后当前任务所占用的运行总时间*/
delta_exec =(unsignd long)(now -cur->exec_start);
if(!delta_exec)
return ;
_update_curr(cfs_rq,curr,delta_exec);
curr->exec_start = now;
if(entity_is_task(curr))
{
struct task_struct *curtask = task_of(curr);
trace_sched_stat_runtime(curtask,delta_exec,curr->vruntime);
cpuacct_charge(curtask,delta_exec);
account_group_exec_runtime(curtask,delta_exec);
}
}
update_curr()计算了当前进程的执行时间,并且将其存放在变量delta_exec中,然后它又将运行时间传递给了__update_curr(),由后者根据当前可运行进程总数对运行时间进行加权计算。最终将上述的权重值与当前运行进程的vruntime相加。
static inline void
__update_cur(struct cfs_rq *cfs_rq,struct sched_entity *curr,unsigned long delta_exec)
{
unsigned long delta_exec_weighted;
sched_set(curr->exec_max,max((u64)delta_exec,curr->exec_max));
curr->sum_exec_runtime += delta_exec;
schedstat_add(cfs_rq,exec_clock,delta_exec);
delta_exec_weight = calc_delta_fair(delta_exec,curr);
curr->vruntime += delta_exec_weighted;
update_min_vruntime(cfs_rq);
}
update_curr()是有系统定时器调用的,无论是在进程处于可执行状态,还是被堵塞处于不可执行的状态。都可以准确的测量。
1.5.2进程选择
加入存在一个完美的多任务处理器,那么所有可执行进程的vruntime的值将是一样的。但是事实是不存在理想的情况。在需要选择下一个进程的时候,CFS会选择一个vruntime最小的进程。这个就是CFS调度算法的核心。那么具体如何实现选择最小的进程呢?
CFS使用红黑树来组织可运行进程的队列,红黑树被称为rbtree.rbtree是一种以树节点形式存在的数据,每个数据会对应一个键值。通过键值可以快速搜索节点上的数据。但是,通过键值搜索到对应节点的速度与整个数的节点规模呈现指数比的关系。
在没有你的世界里,爱你,叶铮