源码路径:
kernel/sched/内核版本:Linux 6.19.10
调度器总体架构
Linux 内核调度器采用**多调度类(Scheduling Class)**设计,不同类型的任务由不同的调度类管理,调度类之间有严格的优先级顺序:
优先级(高 → 低)
┌─────────────────────────────────────────────────────┐
│ stop_sched_class 停机调度类(CPU迁移/热插拔) │ 最高
├─────────────────────────────────────────────────────┤
│ dl_sched_class 截止时间调度类(SCHED_DEADLINE) │
├─────────────────────────────────────────────────────┤
│ rt_sched_class 实时调度类(SCHED_FIFO/RR) │
├─────────────────────────────────────────────────────┤
│ fair_sched_class 公平调度类(SCHED_NORMAL/BATCH) │
├─────────────────────────────────────────────────────┤
│ ext_sched_class 可扩展调度类(sched_ext/BPF) │
├─────────────────────────────────────────────────────┤
│ idle_sched_class 空闲调度类(SCHED_IDLE) │ 最低
└─────────────────────────────────────────────────────┘
每个调度类实现统一的接口(struct sched_class),核心接口包括:
| 接口函数 | 作用 |
|---|---|
enqueue_task |
将任务加入运行队列 |
dequeue_task |
将任务从运行队列移除 |
pick_next_task |
选择下一个要运行的任务 |
put_prev_task |
将当前任务放回队列 |
task_tick |
时钟中断处理(检查是否需要抢占) |
wakeup_preempt |
唤醒时检查是否抢占当前任务 |
select_task_rq |
为任务选择目标 CPU |
一、EEVDF 调度算法(kernel/sched/fair.c)
1.1 算法背景与设计目标
EEVDF(Earliest Eligible Virtual Deadline First,最早合格虚拟截止时间优先) 是 Linux 6.6 引入、替代旧版 CFS 的主调度算法,适用于 SCHED_NORMAL 和 SCHED_BATCH 策略的普通进程。
设计目标:
- 在多任务竞争 CPU 时,按照任务权重(nice值)比例公平地分配 CPU 时间
- 同时提供延迟保证:请求时间片小的任务能更快得到响应
- 解决旧版 CFS 在任务睡眠/唤醒时 lag 丢失的问题
1.2 核心数学模型
虚拟时间(Virtual Time)
每个任务维护一个虚拟运行时间 vruntime,其增长速率与任务权重成反比:
Δvruntime = Δreal_time × (NICE_0_LOAD / weight)
权重越高(nice值越低),vruntime 增长越慢,从而获得更多 CPU 时间。
服务滞后(Lag)
lag_i = S - s_i = w_i × (V - v_i)
其中:
S= 理想服务时间s_i= 任务 i 实际获得的服务时间V= 系统虚拟时间(所有任务 vruntime 的加权平均)v_i= 任务 i 的 vruntimew_i= 任务 i 的权重
公平性约束 :∑ lag_i = 0(所有任务的滞后之和为零)
系统虚拟时间 V(加权平均)
V = (∑ v_i × w_i) / (∑ w_i)
在代码中通过 avg_vruntime() 计算,使用增量形式避免溢出:
c
// kernel/sched/fair.c: avg_vruntime()
// 维护: sum_w_vruntime = ∑(v_i - v0) × w_i
// 维护: sum_weight = ∑ w_i
// V = sum_w_vruntime / sum_weight + v0
任务资格(Eligibility)
任务 i 有资格运行,当且仅当 lag_i >= 0,即:
V >= v_i ⟺ entity_eligible(cfs_rq, se) == true
c
// kernel/sched/fair.c
int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
return vruntime_eligible(cfs_rq, se->vruntime);
// 等价于: avg_vruntime(cfs_rq) >= se->vruntime
}
虚拟截止时间(Virtual Deadline)
deadline_i = vruntime_i + slice_i / w_i
= vruntime_i + calc_delta_fair(slice, se)
其中 slice 默认为 sysctl_sched_base_slice(700μs × log2(CPU数))。
1.3 核心数据结构
c
// include/linux/sched.h
struct sched_entity {
struct load_weight load; // 任务权重(由nice值决定)
struct rb_node run_node; // 红黑树节点(按deadline排序)
u64 vruntime; // 虚拟运行时间
u64 deadline; // 虚拟截止时间 = vruntime + slice/weight
u64 slice; // 时间片请求量(request size)
s64 vlag; // 虚拟滞后 = V - vruntime(迁移时保存)
u64 vprot; // 保护截止时间(防止过早抢占)
u64 min_vruntime; // 子树最小vruntime(augmented RB-tree)
u64 min_slice; // 子树最小slice
u64 max_slice; // 子树最大slice
int on_rq; // 是否在运行队列中
int sched_delayed; // 是否延迟出队
int custom_slice; // 是否使用自定义时间片
struct sched_avg avg; // PELT 负载跟踪
// ...
};
struct cfs_rq {
struct load_weight load; // 队列总权重
unsigned int nr_queued; // 队列中的实体数
unsigned int h_nr_runnable; // 可运行任务数(含子组)
struct rb_root_cached tasks_timeline; // 红黑树(按deadline排序)
struct sched_entity *curr; // 当前运行的实体
struct sched_entity *next; // 下一个候选(buddy机制)
u64 zero_vruntime; // 虚拟时间基准点 v0
s64 sum_w_vruntime;// ∑(v_i - v0) × w_i
long sum_weight; // ∑ w_i
struct sched_avg avg; // PELT 队列负载
// ...
};
1.4 完整过程链路
链路一:任务入队(enqueue)
enqueue_task_fair(rq, p, flags)
└── for_each_sched_entity(se):
└── enqueue_entity(cfs_rq, se, flags)
├── place_entity(cfs_rq, se, flags) ← 计算初始vruntime和deadline
│ ├── vruntime = avg_vruntime(cfs_rq) - lag ← EEVDF策略#1: 保留lag
│ └── deadline = vruntime + calc_delta_fair(slice, se)
├── update_curr(cfs_rq) ← 更新当前任务的vruntime
├── update_load_avg(cfs_rq, se, ...) ← 更新PELT负载统计
├── __enqueue_entity(cfs_rq, se) ← 插入红黑树
│ ├── sum_w_vruntime_add(cfs_rq, se) ← 更新加权平均
│ └── rb_add_augmented_cached(...) ← 按deadline插入,维护min_vruntime
└── se->on_rq = 1
place_entity() 详解(lag 保留机制):
c
// kernel/sched/fair.c: place_entity()
static void place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
u64 vslice, vruntime = avg_vruntime(cfs_rq); // V = 当前系统虚拟时间
s64 lag = 0;
vslice = calc_delta_fair(se->slice, se); // slice / weight
// EEVDF 策略 #1: 保留 lag(PLACE_LAG feature)
// 防止任务睡眠后重新入队时 lag 丢失
if (sched_feat(PLACE_LAG) && cfs_rq->nr_queued && se->vlag) {
lag = se->vlag;
// 补偿新任务加入对 V 的影响
// lag = lag × (W + w_i) / W
load = cfs_rq->sum_weight;
lag *= load + scale_load_down(se->load.weight);
lag = div_s64(lag, load);
}
se->vruntime = vruntime - lag; // v_i = V - vlag
// EEVDF: vd_i = ve_i + r_i/w_i
se->deadline = se->vruntime + vslice;
// 新任务给半个时间片,减少竞争冲击
if (sched_feat(PLACE_DEADLINE_INITIAL) && (flags & ENQUEUE_INITIAL))
vslice /= 2;
}
链路二:时钟中断(tick)
scheduler_tick()
└── task_tick_fair(rq, curr, queued)
└── for_each_sched_entity(se):
└── entity_tick(cfs_rq, se, queued)
├── update_curr(cfs_rq) ← 更新vruntime
│ ├── delta_exec = now - exec_start
│ ├── vruntime += calc_delta_fair(delta_exec, curr)
│ ├── update_deadline(cfs_rq, curr) ← 检查是否需要新deadline
│ │ └── if vruntime >= deadline:
│ │ └── deadline = vruntime + calc_delta_fair(slice, se)
│ └── if resched || !protect_slice(curr):
│ └── resched_curr_lazy(rq) ← 标记需要重新调度
├── update_load_avg(cfs_rq, se, UPDATE_TG)
└── avg_vruntime(cfs_rq) ← 更新 zero_vruntime
update_curr() 核心逻辑:
c
// kernel/sched/fair.c: update_curr()
static void update_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
s64 delta_exec;
delta_exec = update_se(rq, curr); // 计算实际执行时间
// 虚拟时间推进: Δvruntime = Δreal × (NICE_0_LOAD / weight)
curr->vruntime += calc_delta_fair(delta_exec, curr);
// 检查是否需要更新截止时间(消耗完一个时间片请求)
resched = update_deadline(cfs_rq, curr);
// 如果需要重调度,或者保护期已过,触发调度
if (resched || !protect_slice(curr)) {
resched_curr_lazy(rq);
clear_buddies(cfs_rq, curr);
}
}
链路三:选择下一个任务(pick_next)
schedule()
└── pick_next_task(rq, prev, rf)
└── pick_next_task_fair(rq, prev, rf)
└── pick_task_fair(rq, rf)
└── do {
│ pick_next_entity(rq, cfs_rq)
│ └── pick_eevdf(cfs_rq)
│ └── __pick_eevdf(cfs_rq, protect=true)
│ ├── [快速路径] 只有1个任务 → 直接返回
│ ├── [buddy机制] 检查 cfs_rq->next
│ ├── [左节点] 检查最左节点是否eligible
│ └── [堆搜索] O(log n) 遍历红黑树
│ ├── 左子树有eligible任务 → 向左
│ ├── 当前节点eligible → 选中
│ └── 否则 → 向右
└── } while (group_cfs_rq(se)) // 处理组调度层级
__pick_eevdf() 核心算法:
c
// kernel/sched/fair.c: __pick_eevdf()
// 在红黑树中找到:eligible(lag>=0)且 deadline 最早的任务
// 红黑树按 deadline 排序,同时维护 min_vruntime 增强信息
static struct sched_entity *__pick_eevdf(struct cfs_rq *cfs_rq, bool protect)
{
struct rb_node *node = cfs_rq->tasks_timeline.rb_root.rb_node;
struct sched_entity *se = __pick_first_entity(cfs_rq); // deadline最小的节点
struct sched_entity *curr = cfs_rq->curr;
struct sched_entity *best = NULL;
// 只有1个任务,直接返回
if (cfs_rq->nr_queued == 1)
return curr && curr->on_rq ? curr : se;
// buddy 机制:优先选择 next buddy(缓存亲和性)
if (sched_feat(PICK_BUDDY) && cfs_rq->next &&
entity_eligible(cfs_rq, cfs_rq->next))
return cfs_rq->next;
// 当前任务在保护期内(未消耗完时间片),继续运行
if (curr && protect && protect_slice(curr))
return curr;
// 最左节点(deadline最小)如果eligible,直接选中
if (se && entity_eligible(cfs_rq, se)) {
best = se;
goto found;
}
// 堆搜索:利用 min_vruntime 增强信息剪枝
while (node) {
struct rb_node *left = node->rb_left;
// 左子树有eligible任务(min_vruntime <= V),优先向左
if (left && vruntime_eligible(cfs_rq,
__node_2_se(left)->min_vruntime)) {
node = left;
continue;
}
se = __node_2_se(node);
// 当前节点是左子树中deadline最小的eligible节点
if (entity_eligible(cfs_rq, se)) {
best = se;
break;
}
node = node->rb_right;
}
found:
// 如果当前任务的deadline更早,优先当前任务
if (!best || (curr && entity_before(curr, best)))
best = curr;
return best;
}
时间复杂度 :O(log n),利用增强红黑树的 min_vruntime 字段剪枝。
链路四:唤醒抢占(wakeup preemption)
try_to_wake_up(p)
└── ttwu_queue(p, cpu, wake_flags)
└── wakeup_preempt(rq, p, wake_flags)
└── check_preempt_wakeup_fair(rq, p, wake_flags)
├── find_matching_se(&se, &pse) ← 找到同层级的调度实体
├── update_curr(cfs_rq)
├── [PREEMPT_SHORT] pse->slice < se->slice → 允许抢占
├── [NEXT_BUDDY] 设置 cfs_rq->next = pse
└── __pick_eevdf(cfs_rq, protect=false) == pse
└── resched_curr_lazy(rq) ← 触发重调度
1.5 调度特性开关(features.h)
| 特性 | 默认 | 作用 |
|---|---|---|
PLACE_LAG |
on | 入队时保留任务的 lag,防止睡眠后公平性损失 |
PLACE_DEADLINE_INITIAL |
on | 新任务给半个时间片,减少竞争冲击 |
PLACE_REL_DEADLINE |
on | 迁移时保留相对截止时间 |
RUN_TO_PARITY |
on | 当前任务运行到 lag=0 点才允许被抢占 |
PREEMPT_SHORT |
on | 允许时间片更短的任务抢占当前任务 |
DELAY_DEQUEUE |
on | 延迟出队:不eligible的任务留在队列中消耗负lag |
DELAY_ZERO |
on | 出队时将正lag截断为0 |
WAKEUP_PREEMPTION |
on | 允许唤醒时抢占 |
NEXT_BUDDY |
off | 优先调度上次唤醒的任务(缓存亲和) |
PICK_BUDDY |
on | 允许选择 buddy 任务 |
CACHE_HOT_BUDDY |
on | buddy 任务视为缓存热,减少迁移 |
1.6 PELT 负载跟踪(kernel/sched/pelt.c)
PELT(Per-Entity Load Tracking) 为每个调度实体维护指数衰减的负载统计:
load_avg(t) = load_avg(t-1) × decay + running(t)
衰减周期为 32ms,半衰期约 22ms。用于:
- 负载均衡决策(
cpu_load()) - CPU 频率调节(
cpufreq_update_util()) - 能效感知调度(EAS)
二、CFS 带宽控制(kernel/sched/fair.c)
2.1 设计目标
通过 cgroup 的 cpu.cfs_quota_us / cpu.cfs_period_us 限制任务组的 CPU 使用率,防止某个 cgroup 占用过多 CPU。
2.2 核心数据结构
c
struct cfs_bandwidth {
raw_spinlock_t lock;
ktime_t period; // 周期(默认100ms)
u64 quota; // 每周期配额(us)
u64 runtime; // 当前周期剩余配额
u64 burst; // 突发配额
struct hrtimer period_timer; // 周期定时器
struct hrtimer slack_timer; // 松弛定时器(回收未用配额)
struct list_head throttled_cfs_rq; // 被限速的cfs_rq列表
};
2.3 过程链路
任务运行
└── update_curr(cfs_rq)
└── account_cfs_rq_runtime(cfs_rq, delta_exec)
├── cfs_rq->runtime_remaining -= delta_exec
└── if runtime_remaining <= 0:
└── assign_cfs_rq_runtime(cfs_rq) ← 向全局池申请配额
└── if 申请失败:
└── resched_curr(rq) ← 触发重调度
schedule()
└── put_prev_task_fair()
└── check_cfs_rq_runtime(cfs_rq)
└── if runtime_remaining <= 0:
└── throttle_cfs_rq(cfs_rq) ← 限速
├── 加入 cfs_b->throttled_cfs_rq 列表
└── walk_tg_tree(tg_throttle_down) ← 冻结整个层级
周期定时器触发
└── sched_cfs_period_timer()
└── do_sched_cfs_period_timer()
├── __refill_cfs_bandwidth_runtime() ← 补充配额
└── distribute_cfs_runtime() ← 分发配额给被限速的队列
└── unthrottle_cfs_rq() ← 解除限速
└── walk_tg_tree(tg_unthrottle_up) ← 恢复整个层级
三、负载均衡(kernel/sched/fair.c)
3.1 触发时机
1. 周期性均衡(软中断):
sched_balance_trigger(rq)
└── raise_softirq(SCHED_SOFTIRQ)
└── sched_balance_softirq()
└── sched_balance_domains(rq, idle)
2. 新空闲均衡(CPU变空闲时):
schedule()
└── pick_next_task_fair() → idle
└── sched_balance_newidle(rq, rf)
3. NOHZ 空闲均衡(tickless CPU):
nohz_balancer_kick() → IPI → _nohz_idle_balance()
3.2 完整过程链路
sched_balance_rq(this_cpu, this_rq, sd, idle)
│
├── should_we_balance() ← 判断本CPU是否应该执行均衡
│
├── sched_balance_find_src_group(env) ← 找最繁忙的调度组
│ ├── update_sd_lb_stats() ← 统计各组负载
│ │ └── update_sg_lb_stats() ← 统计每个调度组
│ │ ├── group_load, group_util, sum_nr_running
│ │ └── group_type: has_spare/fully_busy/misfit/overloaded
│ ├── update_sd_pick_busiest() ← 选择最繁忙组
│ └── calculate_imbalance() ← 计算需要迁移的负载量
│ └── env->imbalance, env->migration_type
│
├── sched_balance_find_src_rq(env, group) ← 找最繁忙的CPU
│
├── detach_tasks(env) ← 从源CPU摘取任务
│ ├── can_migrate_task() ← 检查任务是否可迁移
│ │ ├── cpus_ptr 亲和性检查
│ │ ├── task_hot() 缓存热检查
│ │ └── migrate_degrades_locality() NUMA局部性检查
│ └── detach_task() ← 从源队列移除
│
└── attach_tasks(env) ← 将任务附加到目标CPU
└── attach_task() → activate_task() → enqueue_task_fair()
3.3 调度域层级(Scheduling Domain)
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
│ │ │ │ │ │ │ │
└──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘
SMT域 SMT域 SMT域 SMT域
└────┬────┘ └────┬────┘
MC域(L2共享) MC域(L2共享)
└──────────┬──────────┘
NUMA域
均衡从最低层域开始,逐层向上,频率随层级升高而降低。
3.4 能效感知调度(EAS)
在非对称 CPU 容量系统(大小核)上,唤醒时选择能效最优的 CPU:
select_task_rq_fair(p, prev_cpu, wake_flags)
└── if !rd_overutilized && has_energy_model:
└── find_energy_efficient_cpu(p, prev_cpu)
├── for each perf_domain:
│ ├── eenv_pd_busy_time() ← 计算PD当前繁忙时间
│ ├── compute_energy(pd, -1) ← 基准能耗
│ ├── compute_energy(pd, prev_cpu) ← 放在prev_cpu的能耗
│ └── compute_energy(pd, max_spare_cap_cpu) ← 放在最空闲CPU的能耗
└── 选择能耗增量最小的CPU
3.5 NUMA 均衡
task_tick_numa(rq, curr) ← 每隔 numa_scan_period 触发
└── task_work_add(numa_work)
└── task_numa_work() ← 扫描任务的内存映射
└── change_prot_numa() ← 将页表项设为PROT_NONE触发缺页
缺页异常
└── task_numa_fault()
├── task_numa_placement() ← 统计各NUMA节点的访问频率
│ └── 找到访问最多的节点 → sched_setnuma(p, max_nid)
└── numa_migrate_preferred() ← 迁移到首选节点
└── task_numa_migrate()
├── task_numa_find_cpu() ← 在目标节点找最优CPU
└── migrate_task_to() 或 migrate_swap()
四、实时调度算法(kernel/sched/rt.c)
4.1 调度策略
| 策略 | 说明 |
|---|---|
SCHED_FIFO |
先进先出,不被同优先级任务抢占,只被更高优先级抢占 |
SCHED_RR |
轮转,同优先级任务按时间片轮转(默认100ms) |
优先级范围:1(最低)~ 99(最高),高于所有普通进程。
4.2 核心数据结构
c
struct rt_rq {
struct rt_prio_array active; // 优先级位图 + 任务链表
unsigned int rt_nr_running;
unsigned int rr_nr_running;
struct rt_bandwidth *rt_bandwidth; // RT带宽限制
// ...
};
struct rt_prio_array {
DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); // 100位优先级位图
struct list_head queue[MAX_RT_PRIO]; // 每个优先级的任务链表
};
4.3 过程链路
入队
enqueue_task_rt(rq, p, flags)
└── enqueue_rt_entity(rt_se, flags)
└── __enqueue_rt_entity(rt_se, flags)
├── list_add_tail(&rt_se->run_list, queue) ← 加入对应优先级链表
└── __set_bit(prio, array->bitmap) ← 设置优先级位图
选择下一个任务(O(1))
pick_next_task_rt(rq, prev, rf)
└── pick_next_rt_entity(rt_rq)
├── idx = sched_find_first_bit(array->bitmap) ← 找最高优先级(O(1))
└── list_first_entry(&array->queue[idx]) ← 取链表头
时钟中断(SCHED_RR 轮转)
task_tick_rt(rq, p, queued)
└── if p->policy == SCHED_RR:
├── p->rt.time_slice--
└── if time_slice == 0:
├── p->rt.time_slice = sched_rr_timeslice ← 重置时间片
├── list_move_tail(&rt_se->run_list, queue) ← 移到链表尾
└── resched_curr(rq) ← 触发重调度
SMP 推拉均衡
// 推(当前CPU有高优先级RT任务无法运行)
push_rt_task(rq, pull)
└── find_lowest_rq(task) ← 找优先级最低的CPU
└── cpupri_find() ← 在 cpupri 数据结构中查找
└── migrate_task_to(p, lowest_cpu)
// 拉(当前CPU变空闲)
pull_rt_task(this_rq)
└── for_each_domain(this_cpu, sd):
└── find_highest_rq(sd) ← 找有可迁移RT任务的最高优先级CPU
└── migrate_task_to(p, this_cpu)
4.4 RT 带宽限制
防止 RT 任务饿死普通任务:
// 默认: RT任务每1秒最多使用950ms(95%)
/proc/sys/kernel/sched_rt_period_us = 1000000 (1s)
/proc/sys/kernel/sched_rt_runtime_us = 950000 (950ms)
sched_rt_period_timer()
└── 每个周期重置 rt_bandwidth->rt_runtime
└── 如果RT任务超出配额 → 限速(throttle)
五、EDF 截止时间调度(kernel/sched/deadline.c)
5.1 算法原理
SCHED_DEADLINE 实现了 CBS(Constant Bandwidth Server) 算法,基于 EDF(Earliest Deadline First):
每个任务有三个参数:
runtime(Q):每个周期内最多运行的时间deadline(D):相对截止时间period(T):周期(T >= D >= Q)
可调度性条件 :∑(Q_i / T_i) <= 1(所有任务的利用率之和不超过100%)
5.2 核心数据结构
c
struct sched_dl_entity {
struct rb_node rb_node; // 红黑树节点(按deadline排序)
u64 dl_runtime; // 每周期配额 Q
u64 dl_deadline; // 相对截止时间 D
u64 dl_period; // 周期 T
u64 runtime; // 当前周期剩余配额
u64 deadline; // 当前绝对截止时间
int dl_throttled; // 是否被限速
int dl_yielded; // 是否主动让出
struct hrtimer dl_timer; // 补充定时器
// ...
};
5.3 过程链路
入队
enqueue_task_dl(rq, p, flags)
└── enqueue_dl_entity(dl_se, flags)
├── update_dl_entity(dl_se) ← 更新截止时间
│ └── if 超过截止时间或新任务:
│ └── deadline = now + dl_deadline ← 设置新的绝对截止时间
└── __enqueue_dl_entity(dl_se)
└── rb_add_cached(&dl_se->rb_node, &dl_rq->rb_root, dl_entity_preempt)
// 按 deadline 排序插入红黑树
选择下一个任务
pick_next_task_dl(rq, prev, rf)
└── pick_next_dl_entity(dl_rq)
└── rb_first_cached(&dl_rq->rb_root) ← 取deadline最小的任务(O(1))
时钟中断
task_tick_dl(rq, p, queued)
└── update_curr_dl(rq)
├── delta_exec = now - curr->exec_start
├── dl_se->runtime -= delta_exec ← 消耗配额
└── if runtime <= 0:
└── __dl_clear_params(p)
└── throttle_dl_task(p) ← 限速
└── hrtimer_start(dl_timer, deadline) ← 等到下个周期
补充定时器触发
dl_task_timer()
└── replenish_dl_entity(dl_se)
├── dl_se->runtime = dl_se->dl_runtime ← 补充配额
├── dl_se->deadline += dl_se->dl_period ← 推进截止时间
└── enqueue_task_dl(rq, p, ENQUEUE_REPLENISH)
5.4 DL Server(为 CFS 提供实时保障)
Linux 6.6 引入 dl_server 机制,为 CFS 任务提供实时服务保障,防止 RT/DL 任务完全饿死普通任务:
c
// kernel/sched/fair.c: fair_server_init()
void fair_server_init(struct rq *rq)
{
struct sched_dl_entity *dl_se = &rq->fair_server;
init_dl_entity(dl_se);
dl_server_init(dl_se, rq, fair_server_pick_task);
}
// fair_server 作为一个 DL 实体,定期为 CFS 任务"预留"CPU时间
六、Stop 调度类(kernel/sched/stop_task.c)
6.1 设计目标
Stop 调度类是最高优先级的调度类,用于执行需要独占 CPU 的内核操作:
- CPU 热插拔(
cpu_stop_work) - 任务迁移(
active_load_balance_cpu_stop) - 内存迁移
6.2 过程链路
stop_one_cpu(cpu, fn, arg)
└── cpu_stop_queue_work(cpu, work)
└── wake_up_process(stopper->thread) ← 唤醒 migration/N 内核线程
migration/N 线程运行
└── smpboot_thread_fn()
└── cpu_stopper_thread()
└── work->fn(work->arg) ← 执行停机工作
Stop 任务的 pick_next_task 直接返回 stopper->thread,无需任何排序。
七、Idle 调度类(kernel/sched/idle.c)
7.1 设计目标
当 CPU 没有任何可运行任务时,运行 swapper/N(PID 0)空闲进程,进入低功耗状态。
7.2 过程链路
schedule()
└── pick_next_task() → 所有调度类都返回NULL
└── pick_next_task_idle(rq)
└── return rq->idle ← 返回 swapper 进程
swapper 进程运行
└── cpu_idle_loop()
├── tick_nohz_idle_enter() ← 停止时钟(tickless)
├── cpuidle_idle_call() ← 进入 C-state 低功耗状态
│ └── cpuidle_enter_state() ← 调用平台相关的 idle 指令(如 HLT/WFI)
└── 等待中断唤醒
八、可扩展调度器 sched_ext(kernel/sched/ext.c)
8.1 设计目标
sched_ext(BPF 可扩展调度器) 是 Linux 6.12 引入的革命性特性,允许用户通过 eBPF 程序在运行时替换调度策略,无需修改内核代码。
应用场景:
- 游戏/实时音视频的自定义低延迟调度
- 数据中心工作负载的专用调度策略
- 调度算法研究与快速原型验证
8.2 架构
用户空间 BPF 程序
├── scx_ops.enqueue() ← 自定义入队逻辑
├── scx_ops.dispatch() ← 自定义任务分发
├── scx_ops.select_cpu() ← 自定义CPU选择
├── scx_ops.runnable() ← 任务变为可运行时回调
└── scx_ops.running() ← 任务开始运行时回调
内核 sched_ext 框架
├── 验证 BPF 程序安全性(verifier)
├── 提供 DSQ(Dispatch Queue)抽象
│ ├── SCX_DSQ_GLOBAL 全局共享队列
│ ├── SCX_DSQ_LOCAL per-CPU 本地队列
│ └── 用户自定义 DSQ
└── 回退机制:BPF程序出错时自动切回CFS
8.3 过程链路
enqueue_task_scx(rq, p, flags)
└── SCX_CALL_OP(SCX_KF_ENQUEUE, enqueue, p, enq_flags)
└── 调用用户BPF程序的 ops.enqueue()
└── scx_bpf_dispatch(p, dsq_id, slice, enq_flags)
└── 将任务放入指定的 DSQ
pick_next_task_scx(rq, prev, rf)
└── SCX_CALL_OP(SCX_KF_DISPATCH, dispatch, cpu, prev)
└── 调用用户BPF程序的 ops.dispatch()
└── scx_bpf_consume(dsq_id) ← 从DSQ取任务到本地队列
└── 从 local DSQ 取出任务运行
九、调度器核心框架(kernel/sched/core.c)
9.1 主调度函数 schedule()
schedule()
└── __schedule(SM_NONE)
├── prev = rq->curr
├── deactivate_task(rq, prev, ...) ← 如果prev要睡眠,移出队列
├── pick_next_task(rq, prev, rf) ← 选择下一个任务
│ └── 按优先级遍历调度类:
│ stop → dl → rt → fair → ext → idle
├── context_switch(rq, prev, next) ← 上下文切换
│ ├── switch_mm_irqs_off() ← 切换内存空间(地址空间)
│ └── switch_to(prev, next, prev) ← 切换寄存器/栈(arch相关)
└── finish_task_switch() ← 完成切换后的清理
9.2 上下文切换过程
context_switch(rq, prev, next)
│
├── prepare_task_switch(rq, prev, next)
│
├── [内存空间切换]
│ └── switch_mm_irqs_off(prev->mm, next->mm, next)
│ └── load_new_mm_cr3() ← 切换页表基址(CR3寄存器,x86)
│
└── [寄存器/栈切换]
└── switch_to(prev, next, prev)
└── __switch_to_asm() ← 汇编实现
├── 保存 prev 的寄存器到内核栈
├── 切换内核栈指针(RSP)
└── 恢复 next 的寄存器
└── ret → 跳转到 next 上次被切换出的位置
9.3 抢占机制
// 内核抢占点
preempt_schedule() ← 软件抢占点(preempt_enable()时)
preempt_schedule_irq() ← 中断返回时抢占
└── __schedule(SM_PREEMPT)
// 标记需要重调度
resched_curr(rq)
└── set_tsk_need_resched(curr) ← 设置 TIF_NEED_RESCHED 标志
└── 下次抢占点时触发 schedule()
十、各调度算法对比总结
| 特性 | EEVDF/CFS | RT (FIFO/RR) | Deadline (EDF) | sched_ext |
|---|---|---|---|---|
| 调度策略 | SCHED_NORMAL/BATCH | SCHED_FIFO/RR | SCHED_DEADLINE | 用户自定义 |
| 优先级 | nice (-20~19) | 1~99 | 按deadline | 用户定义 |
| 算法 | EEVDF(虚拟截止时间) | 优先级队列 | EDF+CBS | BPF程序 |
| 时间复杂度 | O(log n) | O(1) | O(log n) | 用户定义 |
| 公平性 | 按权重比例公平 | 无(优先级绝对) | 无(截止时间优先) | 用户定义 |
| 延迟保证 | 软保证(slice) | 硬保证(优先级) | 硬实时保证 | 用户定义 |
| 适用场景 | 普通桌面/服务器任务 | 音视频/控制系统 | 硬实时(工业控制) | 特殊工作负载 |
| 饥饿风险 | 无(lag机制保证) | 低优先级可能饥饿 | 无(CBS保证) | 用户负责 |
| SMP支持 | 完整(负载均衡) | 推拉均衡 | 推拉均衡 | 用户定义 |
| cgroup支持 | 完整(带宽控制) | 部分 | 部分 | 部分 |
十一、调度器调优参数
/proc/sys/kernel/ 相关参数
| 参数 | 默认值 | 说明 |
|---|---|---|
sched_base_slice_ns |
700000 (700μs) | EEVDF 基础时间片(随CPU数对数缩放) |
sched_migration_cost_ns |
500000 (500μs) | 任务迁移代价阈值(缓存热判断) |
sched_rt_period_us |
1000000 (1s) | RT 带宽控制周期 |
sched_rt_runtime_us |
950000 (950ms) | RT 任务每周期最大运行时间 |
numa_balancing |
1 | 是否启用 NUMA 自动均衡 |
/sys/kernel/debug/sched/features 运行时调整
bash
# 查看当前调度特性
cat /sys/kernel/debug/sched/features
# 禁用 PLACE_LAG(调试用)
echo NO_PLACE_LAG > /sys/kernel/debug/sched/features
# 启用 NEXT_BUDDY(提高缓存亲和性)
echo NEXT_BUDDY > /sys/kernel/debug/sched/features