1. 前言
进程调度是一个操作系统的核心功能之一。调度算法的优劣直接影响到了一个操作系统的使用体验。Linux 作为目前业界使用最为广泛的开源操作系统,其内部的进程调度器在社区的trade off
下优化了一轮又一轮。目前最为成熟的调度器是CFS
(Completely Fair Scheduler,完全公平调度器),CFS
的设计目标是实现对 CPU 资源的公平分配,使得每一个进程都能获得相对相等的 CPU 运行时间。
CFS
的特点包括:公平性、无饥饿、负载均衡以及动态优先级。
2. 虚拟运行时间
为了实现对 CPU 资源的公平分配,以使得每个进程都获得相对相等的 CPU 运行时间。CFS
调度器抽象出来一个概念vritual clock
(虚拟时钟)以及vruntime
(虚拟运行时间)。
所谓虚拟其实是与真实相对的,我们都知道 CPU 中都存在一个时钟(Clock
)部件,它以固定的频率产生脉冲,驱动CPU内部的各个部件按照时序执行操作。这个 CPU 中的时钟就是与虚拟时钟相对的真实时钟。一个进程在 CPU 上运行的时间,可以由 CPU 的时钟来计量。这个计量得到的时间就是进程的真实运行时间。
时钟可以是晶振构成、也可以是振荡器、亦或者时钟发生器等。
vruntime
(虚拟运行时间)其实是在真实运行时间的基础上加入进程优先级的因素。在虚拟时钟中,进程的实际运行时间公式如下:
<math xmlns="http://www.w3.org/1998/Math/MathML"> 实际运行时间 = 调度周期 ∗ 进程权重 所有进程权重之和 实际运行时间 = 调度周期 * \frac{进程权重}{所有进程权重之和} </math>实际运行时间=调度周期∗所有进程权重之和进程权重
在CFS
调度器中,调度器认为相同的调度周期内所有进程的虚拟运行时间应该是要相同的。
所有的进程都会在一颗红黑树中按照虚拟运行时间的大小,从小到大进行排序。每个调度时机,CFS
调度器都选择红黑树中虚拟运行时间最小的进程调度上 CPU 进行运行。以确保在相同的调度周期,红黑树中所有的进程都趋近相同的值。
3. 定时更新统计信息
与其他调度器更新运行时间直接加一不同。由于CFS
调度器涉及虚拟运行时间,所以这部分代码比较复杂。更新虚拟运行时间的主要逻辑位于调度类的update_curr
函数中,该函数会由 CPU 的时间中断定时触发,收集并计算当前的虚拟运行时间。
scss
static void update_curr(struct cfs_rq *cfs_rq)
{
.....
// 更新虚拟运行时间
curr->vruntime += calc_delta_fair(delta_exec, curr);
update_deadline(cfs_rq, curr);
update_min_vruntime(cfs_rq);
}
// 计算占用 CPU 的时间间隔
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
if (unlikely(se->load.weight != NICE_0_LOAD))
delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
return delta;
}
// 执行真正的计算
static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw)
{
u64 fact = scale_load_down(weight);
u32 fact_hi = (u32)(fact >> 32);
int shift = WMULT_SHIFT;
int fs;
__update_inv_weight(lw);
if (unlikely(fact_hi)) {
fs = fls(fact_hi);
shift -= fs;
fact >>= fs;
}
fact = mul_u32_u32(fact, lw->inv_weight);
fact_hi = (u32)(fact >> 32);
if (fact_hi) {
fs = fls(fact_hi);
shift -= fs;
fact >>= fs;
}
return mul_u64_u32_shr(delta_exec, fact, shift);
}
上面的代码可以总结为下面的公式:
<math xmlns="http://www.w3.org/1998/Math/MathML"> 进程的虚拟运行时间 = 进程的实际运行时间 ∗ N I C E _ 0 _ L A D 进程的优先级 进程的虚拟运行时间 = 进程的实际运行时间 * \frac{NICE\_0\_LAD}{进程的优先级} </math>进程的虚拟运行时间=进程的实际运行时间∗进程的优先级NICE_0_LAD
4. 结论
可以这样认为,在CFS
调度器中,进程优先级越高的进程,将获得更多的机会上 CPU 运行。也即是说,同个调度周期,进程优先级越高的进程,实际运行时间越多。