3. EEVDF 调度器原理:Earliest Eligible Virtual Deadline First
3.1 引入背景:为什么 CFS 需要进化?
完全公平调度器(CFS)自 Linux 2.6.23 引入以来,一直是主流调度器。它基于虚拟时间(vruntime)的“红黑树”模型,目标是让每个就绪进程获得近似相等的 CPU 时间片。然而,随着数据中心、实时音视频、交互式桌面等场景对低延迟和可预测性的要求越来越高,CFS 的固有缺陷逐渐暴露:
- 唤醒抢占延迟不可控:CFS 在唤醒进程时,仅比较 vruntime 大小决定是否抢占,缺乏对“截止时间”的感知,导致突发唤醒的进程可能长时间得不到调度。
- 权重分配与延迟解耦:nice 值控制权重,但权重相同的进程在 CFS 下无法区分“紧急程度”,交互式进程与批处理进程混跑时延迟抖动大。
- 缺乏显式延迟边界:CFS 没有“最晚完成时间”的概念,无法为实时或软实时任务提供确定性调度保证。
核心痛点: CFS 的“公平”是长期平均公平,而非“截止时间感知”的公平。EEVDF 的引入正是为了在保持公平性的同时,显式地引入虚拟截止时间,从而在调度决策中同时考虑“资格”与“紧急度”。
EEVDF(Earliest Eligible Virtual Deadline First)调度器在 Linux 6.6 合入主线,取代了 CFS 成为默认调度器。它保留了 CFS 的加权公平排队(WFQ)思想,但将调度决策从“最小 vruntime”改为“最早合格虚拟截止时间优先”。
3.2 核心算法:资格与截止时间的双重判定
EEVDF 的核心思想是:只有“合格”的进程才有资格参与调度,而在所有合格进程中,选择“虚拟截止时间”最早的进程运行。 这引入了两个关键概念:
| 概念 | 符号 | 定义 | 作用 |
|---|---|---|---|
| 虚拟运行时间 | V(t) | 系统全局虚拟时间,随实际时间推进,受权重影响 | 衡量进程已获得的 CPU 份额 |
| 合格虚拟时间 | Eligible Time (E) | 进程最早可被调度的虚拟时间点,E = max(V(t), 上次调度时的 deadline) | 确保进程不会在“未轮到”时被提前调度 |
| 虚拟截止时间 | Virtual Deadline (D) | 进程必须完成本轮运行的理论最晚虚拟时间点,D = E + (slice / weight) | 决定调度优先级:截止时间越早,优先级越高 |
| 时间片 | slice | 进程每次调度获得的虚拟时间片长度 | 控制调度粒度与延迟 |
调度决策流程如下:
- 更新全局虚拟时间 V(t):每次时钟 tick 或调度点到来时,根据当前运行进程的权重更新 V(t)。
- 计算每个进程的合格时间 E:对于每个就绪进程,E = max(V(t), 该进程上次调度时的 deadline)。
- 计算每个进程的虚拟截止时间 D:D = E + (进程分配的 slice / 进程权重)。
- 选择调度:在所有 E ≤ V(t) 的“合格”进程中,选择 D 最小的进程运行。
- 更新进程状态:进程被调度后,记录其本次调度的 deadline 为 D,用于下次计算 E。
直观理解: 可以把 EEVDF 想象成一个“有预约的排队系统”。每个进程都有一个“最早入场时间”(E)和一个“最晚离场时间”(D)。只有到了入场时间的顾客才能进入候选区,而收银员总是优先服务那些“最晚离场时间”最早的顾客,确保没有顾客被无限期拖延。
3.3 与 CFS 的核心差异
EEVDF 并非完全推翻 CFS,而是在其基础上进行了关键性的算法升级。下表从多个维度对比两者的差异:
| 对比维度 | CFS (完全公平调度器) | EEVDF (最早合格虚拟截止时间优先) |
|---|---|---|
| 调度决策依据 | 最小 vruntime(红黑树最左节点) | 最早虚拟截止时间 D(仅从合格进程中选) |
| 公平性模型 | 加权公平排队(WFQ),长期平均公平 | 加权公平排队 + 截止时间感知,兼顾短期公平与延迟 |
| 延迟控制 | 隐式:通过 vruntime 追赶实现,无显式边界 | 显式:通过虚拟截止时间 D 提供可预测的延迟上界 |
| 唤醒抢占 | 唤醒进程 vruntime 与当前进程比较,可能频繁抢占 | 唤醒进程必须满足 E ≤ V(t) 且 D 更小才抢占,更平滑 |
| 权重与延迟关系 | 权重仅影响时间片比例,不影响调度顺序 | 权重同时影响时间片长度和截止时间计算,权重越大 D 越小 |
| 数据结构 | 红黑树(按 vruntime 排序) | 红黑树(按虚拟截止时间 D 排序)+ 资格检查 |
| 适用场景 | 通用服务器、批处理、桌面 | 通用 + 延迟敏感型(音视频、交互式、部分实时) |
迁移注意: EEVDF 虽然保留了 CFS 的大部分接口(如 nice 值、cgroup cpu.weight),但调度行为有显著变化。在延迟敏感场景下,EEVDF 通常表现更好;但在某些极度重载的批处理场景中,由于截止时间计算带来的额外开销,吞吐量可能略有下降(通常 < 1%)。建议在关键业务上线前进行 A/B 测试。
3.4 关键数据结构与算法细节
EEVDF 在内核中的实现围绕几个核心数据结构展开:
- struct sched_entity:每个调度实体包含
vruntime(虚拟运行时间)、deadline(上次调度时的虚拟截止时间)、slice(时间片长度)等字段。 - struct cfs_rq:就绪队列维护一棵红黑树,排序键为
deadline(即虚拟截止时间 D)。同时记录全局虚拟时间min_vruntime(即 V(t))。 - 资格检查函数:
entity_eligible(cfs_rq, se)判断se->vruntime <= cfs_rq->min_vruntime是否成立,或更精确地,判断se->deadline是否在合格范围内。
核心算法伪代码如下:
// EEVDF 调度选择主路径
struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq)
{
struct sched_entity *se;
struct rb_node *node;
// 1. 更新全局虚拟时间
update_min_vruntime(cfs_rq);
// 2. 遍历红黑树(按 deadline 排序)
for (node = rb_first(&cfs_rq->tasks_timeline);
node; node = rb_next(node)) {
se = rb_entry(node, struct sched_entity, run_node);
// 3. 资格检查:E <= V(t)
if (entity_eligible(cfs_rq, se)) {
// 4. 选择第一个合格的实体(即 deadline 最小的)
return se;
}
}
// 5. 如果没有合格实体,返回树根(最左节点)
return rb_entry(rb_first(&cfs_rq->tasks_timeline),
struct sched_entity, run_node);
}
// 资格检查简化逻辑
bool entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
// 合格时间 E = max(se->vruntime, se->deadline)
s64 eligible_time = max(se->vruntime, se->deadline);
// 全局虚拟时间 V(t)
s64 virtual_time = cfs_rq->min_vruntime;
return eligible_time <= virtual_time;
}
算法复杂度: EEVDF 的调度选择复杂度仍为 O(log N)(红黑树查找),与 CFS 相同。资格检查是 O(1) 操作。因此,EEVDF 在引入更精细延迟控制的同时,并未增加渐进时间复杂度。
3.5 延迟测量与调优切入点
EEVDF 的引入为延迟测量提供了新的指标和调优方向:
| 调优维度 | 关键参数/指标 | 说明 |
|---|---|---|
| 时间片粒度 | sysctl_sched_min_granularity |
控制最小调度粒度,影响上下文切换频率和延迟 |
| 虚拟截止时间偏移 | se->deadline 与 se->vruntime 的差值 |
差值越小,进程越“紧急”,可通过调整权重间接控制 |
| 唤醒延迟 | wakeup_latency(perf 测量) | EEVDF 下唤醒进程需满足资格条件,延迟更可预测 |
| 调度延迟分布 | 调度延迟的 P99/P999 | EEVDF 通常能降低尾部延迟,适合 SLA 敏感业务 |
实践建议: 在 EEVDF 环境下,调优应重点关注
min_granularity 和 cgroup 权重分配。对于延迟敏感型任务(如 Redis、Nginx),可适当提高其权重,使其虚拟截止时间更小,从而获得更早的调度机会。同时,使用 perf sched 或 trace-cmd 跟踪调度延迟分布,验证调优效果。