一、调度器演进史:从O(1)到CFS到EEVDF

Linux内核进程调度器经历了三次重大范式转变。2.6.23版本引入的CFS(Completely Fair Scheduler)取代了O(1)调度器,用红黑树替代runqueue数组,将时间复杂度从O(1)选择变为O(log N)选择但获得了更公平的CPU时间分配。Linux 6.6版本则引入EEVDF(Earliest Eligible Virtual Deadline First)作为新一代默认调度器,解决了CFS在延迟敏感负载下的尾部延迟问题。

三代对比:

维度O(1)调度器CFSEEVDF
核心数据结构运行队列数组+优先级位图红黑树(按vruntime排序)红黑树(按vruntime)+ 时间轮(按deadline)
选择复杂度O(1) 位图扫描O(log N) 树操作O(log N) + O(lg latency)
公平性模型FIFO/RR时间片虚拟运行时间均衡VRuntime + deadline保证
延迟敏感支持无nice值(粗粒度)latency-nice(精细延迟提示)
引入版本2.6.02.6.236.6

二、CFS核心原理:虚拟运行时间与红黑树

2.1 vruntime:公平的度量衡

CFS的核心思想是维护每个任务的“虚拟运行时间”(virtual runtime),使得所有可运行任务的vruntime以相同速率增长。高优先级任务(低nice值)的vruntime增长更慢,因此能更频繁地被调度;低优先级任务的vruntime增长更快,被调度频率降低。

vruntime的计算公式为:

vruntime += delta_exec × (NICE_0_LOAD / weight)

其中delta_exec是实际执行时间,weight由nice值决定(每级nice差10%的权重,映射表存储在sched_prio_to_weight[40]数组中)。NICE_0_LOAD对应权重1024。

2.2 红黑树:O(log N)的精准选择

CFS使用红黑树(CFS Runqueue的tasks_timeline)键值为vruntime,最左侧节点即为vruntime最小、最应获得CPU的任务。这种设计天然支持:

  • 高效选择:leftmost节点缓存,选择下一个任务O(1)~O(log N)
  • 插入/删除:任务唤醒或阻塞时为O(log N)
  • 范围查询:__pick_first_entity快速定位

2.3 调度粒度与最小抢占间隔

CFS通过sched_min_granularity_ns(默认1ms)和sched_wakeup_granularity_ns(默认1.5ms)控制抢占频率。在CONFIG_HZ=1000的系统中,tick驱动检查vruntime差值:若当前任务vruntime − leftmost->vruntime > sched_min_granularity_ns则触发抢占。

三、EEVDF:延迟保证的新范式

3.1 为什么需要EEVDF

CFS的一个关键问题是:新唤醒的任务初始化为min_vruntime(以避免任务通过sleep-starve机制获得不公平优势),但这导致刚唤醒的高优先级任务必须等待当前所有任务追平其vruntime后才能被调度,这在延迟敏感场景(如游戏帧渲染、音视频采集)引入不可接受的调度延迟。

3.2 EEVDF的deadline模型

EEVDF引入三个核心概念:

  • eligible time(资格时间):任务可被考虑调度的最早时刻
  • virtual deadline(虚拟截止时间):eligible + latency_nice × slice,其中slice与权重相关
  • Earliest Eligible VDF:优先调度vruntime最小且eligible的任务;vruntime相同时选deadline最早的

这确保了新唤醒的高优先级任务能在deadline到期前被优先调度,同时vruntime仍在红黑树中维护全局公平性。

3.3 latency-nice:用户态延迟接口

EEVDF暴露了latency_nice参数(范围−20~19,默认0):

  • latency_nice = −20:极低延迟预算,deadline紧凑(适用于实时性游戏、高频交易)
  • latency_nice = 0:默认行为,与CFS兼容
  • latency_nice = +19:延迟容忍度高,deadline宽松(适合批处理、编译任务)

latency_nice不影响CPU份额权重,仅影响deadline位置——这意味着一个latency_nice=−20的任务仍能获得公平的CPU时间,但在每次唤醒时享受更快的首次调度。

四、调度策略与类

4.1 五大调度策略

策略优先级适用场景
SCHED_FIFO实时硬实时任务
SCHED_RR实时软实时+时间片轮转
SCHED_DEADLINE最高EDF硬实时(CBS+GSN)
SCHED_OTHER/NORMAL普通CFS/EEVDF默认
SCHED_BATCH低优批处理后台任务

4.2 SCHED_DEADLINE的CBS算法

对于严格截止时间需求(如工业控制、车载系统),SCHED_DEADLINE采用Constant Bandwidth Server (CBS)算法:

runtime ≤ deadline ≤ period; bandwidth = runtime/period

内核通过CBS保证每个DL任务在其deadline前获得不低于runtime的CPU时间,超额使用则“throttled”至下一个period。这是Linux支持硬实时场景的关键基础设施。

五、核心调度路径源码分析

5.1 __schedule()主路径

内核调度的核心函数__schedule()流程为:关闭抢占 → pick_next_task()选择下一个任务 → context_switch()切换 → finish_task_switch()清理。CFS类任务遍历for_each_class,按优先级顺序尝试SCHED_FIFO → SCHED_OTHER(DL/FIFO/RR → NORMAL/BATCH → IDLE)。

5.2 context_switch的代价

context_switch涉及:保存/恢复寄存器、切换地址空间(switch_mm)、TLB刷新、L1/L2缓存局部性丢失。典型成本:2~15μs(取决于缓存污染程度)。这就是为什么CFS尽量让当前任务运行够sched_min_granularity_ns再切换——减少不必要的上下文切换。

5.3 负载均衡:SMP与NUMA

多核场景下,每个CPU维护独立的cfs_rq,通过域层级(sched_domain)实现负载均衡:

  • DIE域:同一物理CPU核心间的SMT负载均衡(cost最低)
  • MC域:同一NUMA节点内的core间均衡
  • NUMA域:跨节点均衡(触发阈值imbalance_pct,通常125%关键差异才迁移)

try_to_wake_up()会将任务放入wake_domain的候选CPU列表,idle_balance()在目标CPUidle时拉任务,在周期性tick中也会检查是否需要均衡。

六、cgroup带宽控制

6.1 CPU Bandwidth(cpu.max)

cgroup v2通过cpu.max实现流量整形:echo "100000 100000" > cpu.max限制该cgroup每100ms周期内最多使用100μs CPU(即10%配额)。底层通过throttled队列+rt_bandwidth实现借入/借出模型:某周期未用完的额度可以存入“bank”供下一周期透支(CONFIG_RT_GROUP_SCHED)。

6.2 CFS带宽控制器(cpu.cfs_quota_us)

CFS Bandwidth Controller的工作周期为cfs_period_us(默认100ms),任务超过quota后被throttled出可运行队列,直到下一周期充值。这对容器化场景中精确限制某个服务的CPU用量至关重要。

6.3 实时带宽预留

CONFIG_RT_GROUP_SCHED允许为cgroup设定实时带宽(sched_rt_runtime_us),确保普通实时任务不会因CFS任务而饿死。这是虚拟化/嵌套容器场景下保障服务质量(QoS)的基础。

七、sched_ext:BPF可编程调度器

Linux 6.12引入了sched_ext框架,允许用户空间编写BPF程序完全替换内核调度器(包括核心调度逻辑)。这使得实验性调度策略(如基于机器学习的调度、特定负载定制策略)可以无需重新编译内核即加载运行,极大加速了新调度算法的迭代速度。

八、生产环境调优矩阵

场景关键调优推荐值/配置
高性能交易isolcpus+NOHZ_FULL+CPU隔离kernel boot参数
Web服务器(Nginx)sched_min_granularity_ns=10000000; cpu.cfs_quota_us=-1默认即可
K8s节点static策略+TopologyManager+cpu manager--cpu-manager-policy=static
数据库(MySQL/PG)hugepages+numactl --interleave+disable numa balancingecho 0 > /proc/sys/kernel/numa_balancing
桌面/游戏低latency内核+gamemode+schedtoollatency_nice=-12(gamemode自动设置)
视频采集SCHED_FIFO+chrtchrt -f 50 <command>
批量编译SCHED_BATCH+nice 19nice -19 <command>
虚拟化(QEMU/KVM)iothread(SCHED_FIFO)+vCPU bondtaskset + virsh emulatorpin
AI训练(PyTorch)CPU affinity绑定numa node+禁用超线程boost干扰numactl --cpunodebind=0 --membind=0
嵌入式/实时PREEMPT_RT补丁+SCHED_DEADLINE定期任务deadline保障

九、基准测试与验证方法

9.1 调度延迟测量

使用cyclictest测量抖动:

cyclictest -l 1000000 -m -Sp 90 -i 200 -h 400

参数含义:loop=100万、mlock锁定内存、SCHED_FIFO优先级90、interval 200μs、histogram上限400。典型值:PREEMRT内核 max latency ~10μs,通用内核 max latency ~200μs+。

3.2 perf sched分析

perf sched系列工具链:perf sched record → perf sched latency(各任务平均/最大等待时间)→ perf sched map(CPU迁移热力图)→ perf sched timehist(时间线上的调度事件)。这些是诊断调度问题的标配工具。

3.3 Trace事件

ftrace sched事件:sched_switch/sched_wakeup/sched_migrate_task。配合trace-cmd或perf ftrace记录后,可以用kernelshark可视化分析调度行为。

十、总结

Linux内核调度器从O(1)到CFS再到EEVDF的演进,始终围绕一个核心矛盾:公平性 vs 延迟敏感性。CFS用vruntime+红黑树实现了“完全公平”,但牺牲了新唤醒任务的首次调度延迟;EEVDF引入deadline机制在保持公平的同时保障了延迟,latency-nice让用户态能精细控制延迟偏好。sched_ext则打开了BPF可编程调度的大门,未来我们可能会看到基于eBPF的自适应调度策略在生产环境中落地。

推荐关联阅读:

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部