CFS调度的总结 - (单rq vs 多rq)

 近来和一个师兄谈到了CFS调度算法,我以前一直以为CFS的任务就绪队列是全局的,即有全局唯一的rq,但是师兄说是Per-CPU的,于是回来又仔细分析了下代码,发现果然是Per-CPU的。由这个简单的问题先来说说我为啥认为rq是全局唯一的,然后总结下CFS调度算法的一些关键点。
 
一、Per-CPU的rq和全局唯一的rq
    在Linux-2.6内核时代,为了更好的支持多核,Linux调度器普遍采用了per-cpu的run queue,从而克服了多CPU系统中,全局唯一的run queue由于资源的竞争而成为了系统瓶颈的问题,因为在同一时刻,一个CPU访问run queue时,其他的CPU即使空闲也必须等待,大大降低了整体的CPU利用率和系统性能。当使用per-CPU的run queue之后,每个CPU不再使用大内核锁,从而大大提高了并行处理的调度能力。
    有利必有弊,必须辩证的来分析自然界的现象。我开始认为全局的rq比per-cpu的优势在来源于分布式系统中的”排队论“,即典型的银行排队系统,在有一个取票口,多个服务口的情况下,系统才能具有最大的吞吐率(具体参见排队论的相关文章),同时,per-CPU还会产生一些可调度性的NP问题。除了这些理论的问题,查阅了相关的资料后,per-CPU的run queue还存在以下实际的问题:
     采用per-cpu的run queue所带来的好处会被追求公平性的load balance代码所抵消。在目前的CFS调度器中,每颗CPU只维护本地run queue中所有进程的公平性,为了实现跨CPU的调度公平性,CFS必须定时进行 load balance,将一些进程从繁忙的CPU的run queue中移到其他空闲的run queue中。这个load balance的过程需要获得其他run queue的锁,这种操作降低了多运行队列带来的并行性,并且在复杂情况下,这种因 load balance而引入的footprint 将非常可观。
    当然,load balance引入的加锁操作依然比全局锁的代价要低,这种代价差异随着CPU个数的增加而更加显著。但请您注意,假若系统中的CPU个数有限时,多run queue的优势便不明显了。而采用单一队列之后,每一个需要调度的新进程都可以在全局范围内查找最合适的CPU,而无需CFS那样等待load balance代码来决定,这减少了多CPU之间裁决的延迟,最终的结果是更小的调度延迟。
    其次,为了维护多CPU上的公平性,CFS采用了负载平衡机制,这些复杂代码抵消了per cpu queue曾带来的好处。
     下面的数据是新墨西哥大学的 Taylor Groves, Je Knockel, Eric Schulte对采用per-CPU的run queue和全局唯一的run queue做的一个响应时间的试验。

    从上图可以看出采用全局唯一单队列的BFS调度算法的响应时间明显优于采用多per-CPU队列的CFS调度算法,说明CFS更适于交互式系统,即桌面系统。(当然,并不是说BFS就优于CFS,毕竟不同的应用场景各有优势,但是CFS考虑的确实太多了,想支持各种情况- CFS 的目标是支持从桌面到高端服务器的所有应用场景,这种大而全的设计思路导致其必须做一些实现上的折中)。

二、CFS关键点总结

1. 虚拟运行时间(vruntime)变化公式

  1. vruntime += delta * (1024/se.load.weight);

  2. /*delta:进程实际增加的运行时间,即从调度实体被选择获得cpu到调度实体放弃CPU这段时间*/

结论:在实际运行时间相同的情况下,调度实体权重越大,vruntime增加的越慢。

2. 进程的理想运行时间计算公式

  1. ideal_time = slice *(se.load.weight/cfs_rq.load.weight);
  2. /*slice为CFS运行队列中所有进程运行一遍所需要的时间*/
  3. /*slice的经验计算公式如下:*/
  4. if(cfs_rq->nr_running > 5)
  5.   slice = 4 * cfs_rq->nr_running;
  6. else
  7.     slice = 20; /*单位ms*/

3. CFS调度时机

在有了上面几个计算公式之后,就可以总结出CFS调度算法的几个调度时机:

(1) 调度实体的状态转换的时刻:进程终止、进程睡眠等,广义上还包括进程的创建(fork);
(2) 当前调度实体的时机运行时间大于理想运行时间(delta_exec > ideal_runtime),这一步在时钟中断 处理函数中完成;
(3) 调度实体主动放弃CPU,直接调度schedule函数,放弃CPU
(4) 调度实体从中断、异常及系统调用返回到用户态时,回去检查是否需要调度;


调度器初始化 cfs_rq 的源码位于 Linux 内核的 sched_init() 函数中。sched_init() 函数是在内核启动时被调用,用于初始化调度器和调度器相关的数据结构。该函数的定义在文件 kernel/sched/core.c 中。 以下是 sched_init() 函数中初始化 cfs_rq 的代码片段: ``` void __init sched_init(void) { ... for_each_possible_cpu(i) { struct rq *rq = cpu_rq(i); struct cfs_rq *cfs_rq = &rq->cfs; /* Initialize cfs_rq */ rq->nr_running = 0; rq->nr_uninterruptible = 0; cfs_rq->min_vruntime = (u64)(-(1LL << 20)); cfs_rq->tasks_timeline = RB_ROOT_CACHED; cfs_rq->load.weight = SCHED_LOAD_SCALE; cfs_rq->nr_running = 0; cfs_rq->nr_spread_over = 0; cfs_rq->last_weight = 0; cfs_rq->avg.load_sum = 0; cfs_rq->avg.util_sum = 0; cfs_rq->avg.runnable_sum = 0; cfs_rq->avg.load_avg = 0; cfs_rq->avg.util_avg = 0; cfs_rq->avg.runnable_avg = 0; cfs_rq->avg.last_update_time = sched_clock(); cfs_rq->next = cfs_rq->prev = cfs_rq; cfs_rq->h_load.next = cfs_rq->h_load.prev = NULL; cfs_rq->leaf_cfs_rq_list.next = cfs_rq->leaf_cfs_rq_list.prev = cfs_rq; init_rq_bintree(rq); atomic_set(&rq->nr_iowait, 0); rq->online = 1; rq->cpu_capacity_orig = capacity_orig_of(i); rq->cpu_capacity = rq->cpu_capacity_orig; rq->cpu_capacity_boosted = 0; rq->cpu_capacity_orig_boosted = 0; rq->idle_at_tick = 0; rq->last_schedule_tick = 0; rq->skip_clock_update = 0; rq->avg_idle = 0; rq->avg_idle_sum = 0; rq->avg_idle_mis = 0; rq->avg_idle_mis_sum = 0; rq->clock_update_flags = 0; rq->next_balance = jiffies + HZ; rq->next_task_fair = jiffies; rq->clock = 0; rq->clock_task = NULL; rq->idle_stamp = jiffies; rq->last_load_update_tick = jiffies; rq->nr_iowait = 0; rq->iowait_contrib = 0; rq->nr_iowait_last = 0; init_cfs_bandwidth(&cfs_rq->bandwidth); } ... } ``` 在上述代码中,通过 for_each_possible_cpu(i) 遍历每个 CPU,并分别初始化它们的 cfs_rq 数据结构。具体来说,调用 cpu_rq(i) 函数得到第 i 个 CPU 的 rq 对象,然后将 rq->cfs 指向的 cfs_rq 对象的各个成员初始化为默认值。其中包括 min_vruntime、tasks_timeline、load 等字段。这样,所有 CPU 的 cfs_rq 对象都被初始化完成,调度器初始化工作也就完成了。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值