2、Linux调度器概述:CFS调度器、实时调度器、调度类与调度策略简介

Linux内核调度器是操作系统的核心组件,负责在多个可运行的任务之间分配CPU时间,以最大化系统吞吐量、最小化响应延迟,并满足不同工作负载的优先级与实时性要求。在组调度(Group Scheduling)与CPU域迁移(Domain Migration)场景下,理解调度器的内部架构与策略是进行参数调优的前提。

本章将系统介绍Linux调度器的三大核心组件:CFS调度器(完全公平调度器)、实时调度器(RT调度器),以及它们所依托的调度类与调度策略体系。我们将通过结构图、对比表格和代码示例,帮助您建立清晰的调度器知识框架。

2.1 调度器整体架构

Linux内核采用模块化的调度类(Scheduling Class)架构,每个调度类负责管理一组具有相同调度策略的任务。调度器在每次进程切换时,按照优先级从高到低的顺序遍历调度类,选择下一个要运行的任务。

核心概念:调度类是一种“可插拔”的调度策略实现,内核通过 struct sched_class 结构体统一接口,使得不同调度算法可以共存于同一系统中。
Linux调度器整体架构图 Linux调度器整体架构 调度核心 (schedule() / __schedule()) 调度类选择 (for_each_class 优先级遍历) 停止调度类 (stop_sched_class) 实时调度类 (rt_sched_class) CFS调度类 (fair_sched_class) 空闲调度类 (idle_sched_class) 遍历顺序:停止类 → 实时类 → CFS类 → 空闲类。高优先级调度类有任务时,低优先级类中的任务不会获得CPU。 组调度(cgroup)在CFS类和实时类内部实现,不影响调度类之间的优先级关系。

2.2 调度类与调度策略

每个调度类支持一种或多种调度策略(Scheduling Policy)。调度策略决定了任务在所属调度类中的具体行为规则,例如时间片分配方式、优先级计算方法和抢占条件。

调度类 调度策略 策略标识 典型用途
停止类 — — CPU热插拔、migration线程(不可被抢占)
实时类 (RT) 先进先出 SCHED_FIFO 硬实时任务,时间敏感型控制
实时类 (RT) 轮转 SCHED_RR 软实时任务,同优先级时间片轮转
CFS类 普通公平 SCHED_NORMAL (SCHED_OTHER) 普通用户进程、后台服务
CFS类 批处理 SCHED_BATCH 非交互批处理任务,减少唤醒抢占
CFS类 空闲 SCHED_IDLE 极低优先级后台任务
空闲类 — — 每个CPU的空闲线程(pid=0)
调优提示:在组调度场景下,SCHED_NORMAL 策略的CFS任务受 cpu.shares 控制组权重影响。实时任务(SCHED_FIFO/SCHED_RR)则受 cpu.rt_runtime_us 和 cpu.rt_period_us 限制,防止实时任务饿死CFS任务。

2.3 CFS调度器:完全公平调度

CFS(Completely Fair Scheduler)是Linux内核自2.6.23版本以来的默认调度器,其设计哲学是“模拟一个理想的多任务处理器”——在理想情况下,每个任务应获得完全相等的CPU时间比例。

2.3.1 核心数据结构:红黑树

CFS使用红黑树(Red-Black Tree)来组织可运行队列(runqueue)中的任务。每个任务对应一个调度实体(struct sched_entity),其键值为虚拟运行时间(vruntime)。

  • vruntime:任务的虚拟运行时间,计算公式为 vruntime += 实际运行时间 × (NICE_0_LOAD / 任务权重)。
  • 权重(weight):由任务的nice值决定,nice值越低,权重越高,vruntime增长越慢,从而获得更多CPU时间。
  • 选择逻辑:每次调度时,CFS选择红黑树中最左侧节点(即vruntime最小的任务)运行。
CFS红黑树调度示意图 CFS红黑树调度示意图 vruntime: 120 vruntime: 80 vruntime: 200 vruntime: 50 vruntime: 100 ← 当前选中运行的任务 CFS始终选择红黑树最左侧(vruntime最小)的任务运行。 任务运行后vruntime增加,向右移动;新任务或唤醒任务vruntime被设置为当前最小值。

2.3.2 CFS关键参数

参数名 默认值 说明 调优影响
sched_latency_ns 6,000,000 (6ms) 目标调度延迟,CFS期望在此时间内让所有可运行任务至少运行一次 减小可降低交互延迟,增大可提升吞吐
sched_min_granularity_ns 750,000 (0.75ms) 任务最小运行时间片,防止频繁上下文切换 增大可减少切换开销,但可能增加延迟
sched_wakeup_granularity_ns 1,000,000 (1ms) 唤醒抢占粒度,控制唤醒任务能否抢占当前任务 增大可减少抢占次数,适合CPU密集型负载
sched_nr_migrate 32 负载均衡时一次最多迁移的任务数 影响CPU域迁移的批量大小

2.4 实时调度器:RT调度器

实时调度器负责管理 SCHED_FIFO 和 SCHED_RR 策略的任务。与CFS不同,RT调度器使用静态优先级(范围1~99,数值越大优先级越高),且任务不会被低优先级的CFS任务抢占。

2.4.1 SCHED_FIFO 与 SCHED_RR 对比

特性 SCHED_FIFO SCHED_RR
时间片 无时间片限制,运行直到主动让出CPU或阻塞 有固定时间片(默认100ms),用完后同优先级轮转
抢占条件 仅被更高优先级的FIFO/RR任务抢占 同优先级时间片耗尽,或被更高优先级任务抢占
适用场景 需要持续占用CPU的实时任务(如中断处理线程) 多个同优先级实时任务需要公平分享CPU
组调度支持 支持,通过 cpu.rt_runtime_us 限制组总RT时间 支持,与FIFO共享同一限制
风险提示:实时任务(尤其是SCHED_FIFO)如果进入死循环或无限轮询,会永久阻塞所有CFS任务,导致系统“假死”。务必通过 cpu.rt_runtime_us 设置组级别的实时时间上限,或使用 kernel.sched_rt_runtime_us 全局限制(默认950ms/1s周期)。

2.5 调度类与组调用的交互

在组调度(cgroup v1 cpu 子系统或 v2 cpu 控制器)场景下,调度类的工作方式发生重要变化:

  • CFS组调度:每个cgroup被抽象为一个 task_group,内部包含自己的CFS红黑树。CPU时间先在组之间按 cpu.shares 比例分配,再在组内任务之间按CFS规则分配。
  • RT组调度:每个 task_group 有独立的RT带宽限制(cpu.rt_runtime_us),组内所有实时任务共享该带宽。
  • CPU域迁移:负载均衡器在遍历调度域时,会考虑每个cgroup的负载分布,将任务(或整个cgroup的负载)在CPU之间迁移。
组调度与调度类交互图 组调度与调度类交互示意图 根cgroup (/) 组A (shares=1024) 组B (shares=2048) 组A CFS红黑树 任务A1 (vruntime: 80) 任务A2 (vruntime: 120) 组B CFS红黑树 任务B1 (vruntime: 60) 任务B2 (vruntime: 150) 负载均衡器跨组迁移任务 组B的shares是组A的2倍,因此在CFS调度周期中,组B获得的总CPU时间是组A的2倍。 组内任务之间按各自vruntime公平竞争组内分配到的CPU时间。

2.6 调度策略选择与性能影响

在组调度与CPU域迁移调优场景中,选择合适的调度策略直接影响参数配置效果:

  • 延迟敏感型负载(如数据库、Web服务器):使用 SCHED_NORMAL,配合较小的 sched_latency_ns 和 sched_min_granularity_ns。
  • CPU密集型批处理(如科学计算、视频编码):使用 SCHED_BATCH,减少唤醒抢占,提升缓存亲和性。
  • 硬实时控制(如工业机器人、音频处理):使用 SCHED_FIFO,配合cgroup RT带宽限制防止系统锁死。
  • 后台低优先级任务(如日志清理、备份):使用 SCHED_IDLE,仅在CPU空闲时运行。
关键结论:调度类与调度策略的选择决定了任务在CPU域迁移时的行为模式。CFS任务受vruntime和shares影响,迁移后能快速适应新CPU的负载;实时任务迁移时需注意CPU亲和性(affinity)设置,避免跨域迁移导致缓存失效。在后续章节中,我们将深入探讨如何通过 /proc/sys/kernel/sched_* 参数和cgroup配置来优化这些行为。