Linux 6.19.10 内核调度器算法详解

源码路径: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_NORMALSCHED_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 的 vruntime
  • w_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
相关推荐
|_⊙1 小时前
进程间通信(管道)
linux·运维·服务器
xxl大卡1 小时前
Redis完整详细学习笔记
redis·笔记·学习
星夜夏空991 小时前
FreeRTOS学习(1)——裸机开发与操作系统
单片机·嵌入式硬件·学习
hweiyu001 小时前
Linux命令:iftop
linux·运维·服务器
charlie1145141911 小时前
嵌入式Linux驱动开发——设备树中的 GPIO 配置实战
linux·运维·驱动开发
Hani_971 小时前
Code Coverage系列(七)Code Coverage 原理详细说明
linux·代码覆盖率
Cat_Rocky1 小时前
CICD-Git简单学习 操作流程后续补
git·学习
weixin_550083151 小时前
基于知识图谱的python个性化学习路径推荐系统项目源码
人工智能·学习·知识图谱
Upsy-Daisy1 小时前
OpenClaw 源码解析(九):Channel 接入机制与消息路由流程
linux·运维·网络