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 进程每次调度获得的虚拟时间片长度 控制调度粒度与延迟

调度决策流程如下:

  1. 更新全局虚拟时间 V(t):每次时钟 tick 或调度点到来时,根据当前运行进程的权重更新 V(t)。
  2. 计算每个进程的合格时间 E:对于每个就绪进程,E = max(V(t), 该进程上次调度时的 deadline)。
  3. 计算每个进程的虚拟截止时间 D:D = E + (进程分配的 slice / 进程权重)。
  4. 选择调度:在所有 E ≤ V(t) 的“合格”进程中,选择 D 最小的进程运行。
  5. 更新进程状态:进程被调度后,记录其本次调度的 deadline 为 D,用于下次计算 E。
直观理解: 可以把 EEVDF 想象成一个“有预约的排队系统”。每个进程都有一个“最早入场时间”(E)和一个“最晚离场时间”(D)。只有到了入场时间的顾客才能进入候选区,而收银员总是优先服务那些“最晚离场时间”最早的顾客,确保没有顾客被无限期拖延。
EEVDF 调度决策流程 更新全局 V(t) 遍历就绪队列 计算合格时间 E = max(V, last_deadline) 计算虚拟截止时间 D = E + slice/weight 选择 min(D) 且 E ≤ V 的进程运行 关键约束 只有 E ≤ V(t) 的进程 才被视为“合格” 不合格进程必须等待

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 测试。
CFS vs EEVDF 调度决策对比 CFS 调度 红黑树按 vruntime 排序 P1: vruntime=100 P2: vruntime=120 P3: vruntime=150 选择最小 vruntime: P1 EEVDF 调度 红黑树按虚拟截止时间 D 排序 P1: E=80, D=200 (合格: E≤V=100 ✓) P2: E=110, D=180 (不合格: E>V=100 ✗) P3: E=90, D=150 (合格: E≤V=100 ✓) 选择 min(D) 且合格: P3 vs

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 跟踪调度延迟分布,验证调优效果。
EEVDF 延迟测量关键指标 调度延迟 从任务就绪到被调度 运行的时间间隔 perf sched latency 唤醒延迟 唤醒进程从睡眠到 获得 CPU 的时间 trace-cmd wakeup 截止时间漂移 实际完成时间与 虚拟截止时间的偏差 /proc/sched_debug 调优目标 降低 P99 调度延迟,减少唤醒延迟抖动 确保虚拟截止时间与实际需求匹配