The Principles and Implementation of the Linux CFS Scheduler
发表于 2025-06-09
linux CFS 调度原理与实现
CFS(Completely Fair Scheduler) 在linux 2.6.23版本进入主线。
1. 基础架构图
2. 调度器概述
2.1 调度器种类
目前linux中有3类主要的调度器:
- 截止时间 调度器
- 实时 调度器
- 完全公平 调度器
内核中per-CPU runqueue运行队列数据结构中定义了相应的运行队列。 也定义了对应的调度类。
struct rq {
//部分省略
struct cfs_rq cfs;
struct rt_rq rt;
struct dl_rq dl;
//部分省略
}
const struct sched_class fair_sched_class;
const struct sched_class rt_sched_class;
const struct sched_class dl_sched_class;
//两个特殊的调度类
const struct sched_class idle_sched_class;
const struct sched_class stop_sched_class;
- idle_sched_class 用于在没有任务时,运行idle线程
- stop_sched_class 用于执行”migration/x”线程。
2.2 调度策略
目前linux中有 6 种调度策略,它们对应不同的调度器,后面会有个表格总结。
- 普通调度
- 先进先出调度
- 轮转调度
- 批量调度
- 空闲调度
- 截止时间调度
从内核的宏定义可以看到
#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
/* SCHED_ISO: reserved but not implemented yet */
#define SCHED_IDLE 5
#define SCHED_DEADLINE 6
2.3 优先级和nice
- 内核中的优先级
deadline的优先级就是 -1 实时任务的优先级范围是 [0,99], 普通认为的优先级范围是 [100,139],
- 用户态用nice来设置普通进程的优先级
nice和prio的转化逻辑如下
#define MAX_NICE 19
#define MIN_NICE -20
#define NICE_WIDTH (MAX_NICE - MIN_NICE + 1) //40
#define MAX_RT_PRIO 100
#define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH) //140
#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2) //120
/*
* Convert user-nice values [ -20 ... 0 ... 19 ]
* to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ],
* and back.
*/
#define NICE_TO_PRIO(nice) ((nice) + DEFAULT_PRIO) //nice + 120
#define PRIO_TO_NICE(prio) ((prio) - DEFAULT_PRIO) //prio - 120
2.4 总结表格
| 调度器 | 调度队列 | 调度类 | 调度策略 | 优先级 |
|---|---|---|---|---|
| 普通调度 | cfs_rq | fair_sched_class | SCHED_NORMAL,SCHED_BATCH | 100~139 |
| 实时调度 | rt_rq | rt_sched_class | SCHED_FIFO,SCHED_RR | 0~99 |
| deadline调度 | dl_rq | dl_sched_class | SCHED_DEADLINE | -1 |
3. CFS 完全公平调度器
CFS调度发展到现在,其结构和实现比最初负责了很多,但是核心逻辑基本不变。我们聚焦在核心逻辑,忽略细枝末节,方便尽快理解和掌握CFS调度。
3.1 核心结构和相关变量
3.1.1 cfs_rq
/* CFS-related fields in a runqueue */
struct cfs_rq {
//省略无关
struct rb_root_cached tasks_timeline;
struct sched_entity *curr;
//省略无关
}
3.1.2 rb_root_cached
struct rb_root_cached {
struct rb_root rb_root;
struct rb_node *rb_leftmost;
};
3.1.3 sched_entity
struct sched_entity {
//省略无关
struct rb_node run_node;
u64 vruntime;
//省略无关
}
3.1.4 task_struct
struct task_struct {
//省略无关
struct sched_entity se;
//省略无关
}
3.2 核心原理机制
3.2.1 vruntime(虚拟运行时间)
进程运行的每一单位时间都会增加其 vruntime,计算公式考虑调度权重(即 nice 值),nice 值越低(优先级高)vruntime 增长越慢。
3.2.2 红黑树调度策略
所有就绪的调度实体按 vruntime 组织在红黑树中,调度器每次选择 vruntime 最小(最左)的实体执行。 为了实现O(1)操作,增加了一个 leftmode指针,指向红黑树最左的实体。当调度器选择vruntime最小(最左)时,可以直接得到,而不用从root去遍历。
3.2.3 调度逻辑
- 主动:任务调用cond_resched或schedule主动让出时间片。
- 被动:当任务运行的时间超过了其 slice(时间配额),调度器会设置 TIF_NEED_RESCHED 标志。后续在调度点(如返回用户态)检查此标志并触发调度。
调度点 以非抢占式(CONFIG_PREEMPTION未设置)为例。 a. 系统调用或异常返回用户态时 b. 中断处理返回用户态时
3.3 CFS调度逻辑
这是个简化后的CFS调度框架。我们基于它来理清CFS调度的基础逻辑。
3.3.1 结构梳理
在多核系统中,每个cpu都有一个调度队列结构 rq,它包含了 CFS调度队列cfs。cfs是CFS调度队列结构,我们目前只关注优化后的红黑树结构tasks_timeline和当前运行实体*curr 。
tasks_timeline 结构包含了一个 rb_root红黑树,还有一个rb_leftmode指针,它指向红黑树中最左侧的节点,也就是vruntime最小的节点。这样做的优点就是实现了O(1)操作,不用去红黑树中遍历查找。
3.3.2 调度方式
- schedule 主动调度(通常设置TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE)
进程等待资源(I/O,锁等)或condition不满足时,调用schedule()让出CPU。
- cond_resched 主动检查调度(不改变任务状态)
条件:抢占计数器是否为0,是否设置了重新调度标志 TIF_NEED_RESCHED 在不可抢占系统中,为了避免任务执行时间过长,导致其他任务starve。显式请求调度,如果需要,则安全地让出CPU;如果不需要,则什么都不做,进程继续运行。
- 系统调用/异常返回/中断返回(隐式调用schedule)
都是在返回用户态前,对重新调度标志 TIF_NEED_RESCHED 进行检查,如果设置了,则会进入schedule(),
3.3.3 时间片耗尽
系统每个tick运行一次tick_sched_timer,它会调用
本文访问次数:... 次