1. 课程导论与背景:Linux调度器发展史与ARM64平台移植必要性

1.1 Linux调度器演进全景

Linux内核调度器经历了从简单到复杂、从粗放到精细的四个主要阶段。每个阶段的更迭都源于对吞吐量、公平性、实时性和功耗控制等维度的更高要求。理解这段演进史,是进行平台移植适配的前提。

阶段 内核版本 核心算法 时间复杂度 主要缺陷
O(n) 调度器 2.4 及更早 循环遍历所有进程 O(n) 多核扩展性差,交互体验糟糕
O(1) 调度器 2.6.0 ~ 2.6.22 优先级位图 + 双队列 O(1) 交互进程响应不够平滑,公平性不足
CFS 调度器 2.6.23 ~ 6.5 红黑树 + 虚拟运行时间 O(log n) 对延迟敏感型负载支持不够灵活
EEVDF 调度器 6.6+ 红黑树 + 虚拟截止时间 O(log n) 仍在持续优化中
关键认知: 调度器演进的核心矛盾始终是“公平性 vs 吞吐量 vs 延迟”的三元平衡。ARM64平台的多核异构特性(big.LITTLE / DSU)放大了这一矛盾,使得移植适配成为刚需。

1.2 各阶段调度器深度解析

1.2.1 O(n) 调度器:朴素但低效

早期Linux调度器采用全局就绪队列,每次调度时遍历所有进程计算“好度”(goodness)。

  • 数据结构: 单一全局链表
  • 调度开销: 随进程数线性增长,高负载下CPU空转严重
  • 多核问题: 全局锁竞争激烈,SMP扩展性极差
  • 交互体验: 无抢占点设计,桌面操作卡顿明显
移植启示: O(n)调度器在单核ARM上尚可运行,但在多核ARM64上会因全局锁导致性能崩溃。现代ARM64 SoC通常有4~8个核心,O(n)模型完全不可用。

1.2.2 O(1) 调度器:位图加速的里程碑

Ingo Molnar 在2.6内核中引入了基于优先级位图的O(1)调度器,将调度决策时间降为常数级。

  • 双队列结构: active 队列 + expired 队列,时间片耗尽后交换
  • 优先级位图: 140级优先级,通过 __ffs() 指令O(1)找到最高优先级进程
  • 负载均衡: 引入 per-CPU 运行队列,支持进程迁移
  • 实时性: 支持 SCHED_FIFO / SCHED_RR 实时策略
/* O(1) 调度器核心数据结构示意 */
struct prio_array {
    unsigned long bitmap[BITMAP_SIZE];  /* 优先级位图 */
    struct list_head queue[MAX_PRIO];   /* 每优先级链表 */
    int nr_active;                      /* 活跃进程计数 */
};

struct rq {
    spinlock_t lock;
    struct prio_array *active, *expired;
    struct task_struct *curr;
    /* ... */
};
ARM64移植陷阱: O(1)调度器依赖 __ffs() 位操作指令。ARM64的 CLZ(前导零计数)指令与x86的 BSF 行为不同,需要在内核汇编层做适配。此外,ARM64的缓存行大小(通常64字节)与x86不同,影响 per-CPU 数据结构的对齐填充。

1.2.3 CFS 调度器:公平性的数学表达

Con Kolivas 的“楼梯调度器”思想启发了 CFS(Completely Fair Scheduler),由 Ingo Molnar 在2.6.23中合入。CFS用红黑树替代了优先级数组,以“虚拟运行时间”(vruntime)为核心度量。

  • 红黑树: 按 vruntime 排序,最左节点为下一个调度进程
  • 虚拟运行时间: vruntime += 实际运行时间 * (NICE_0_LOAD / 进程权重)
  • 调度粒度: 默认调度周期 sysctl_sched_latency(6ms),最小粒度 sysctl_sched_min_granularity(0.75ms)
  • 组调度: 支持 CPU cgroup,实现进程组级别的公平分配
/* CFS 核心调度实体 */
struct sched_entity {
    struct rb_node      run_node;       /* 红黑树节点 */
    unsigned long       vruntime;       /* 虚拟运行时间 */
    u64                 exec_start;     /* 当前执行开始时间 */
    u64                 sum_exec_runtime; /* 总执行时间 */
    struct load_weight  load;           /* 调度权重 */
    /* ... */
};
ARM64适配要点: CFS的vruntime计算涉及64位乘除法。ARM64的 MUL/UMULL 指令与x86的 IMUL 在溢出行为上一致,但ARM64缺少硬件除法器(部分低端核),需内核使用 do_div() 软除法,这会引入额外延迟。移植时需评估是否启用 ARM64_HAS_DIV 特性。

1.2.4 EEVDF 调度器:延迟感知的新一代

EEVDF(Earliest Eligible Virtual Deadline First)在Linux 6.6中作为可选调度器引入,旨在解决CFS对延迟敏感型负载(如音视频、游戏)支持不足的问题。

  • 虚拟截止时间: 每个进程维护 deadline,调度器选择最早截止时间的进程
  • 资格判定: 只有“合格”(eligible)的进程才参与调度,避免饥饿
  • 抢占边界: 更精确的抢占点计算,减少不必要的上下文切换
  • 兼容性: 与CFS共享红黑树结构,调度策略接口不变
/* EEVDF 调度实体扩展 */
struct sched_entity {
    struct rb_node      run_node;
    unsigned long       vruntime;
    u64                 deadline;       /* 虚拟截止时间 */
    u64                 min_deadline;   /* 子实体最小截止时间 */
    s64                 lag;            /* 滞后量,用于资格判定 */
    /* ... */
};
移植价值: EEVDF的截止时间计算依赖高精度时间戳计数器(CNTVCT_EL0)。ARM64的通用定时器频率在不同平台差异巨大(1MHz ~ 100MHz),移植时必须校准 arch_timer 驱动,否则deadline计算会严重偏差。

1.3 ARM64平台特性与调度器移植必要性

1.3.1 ARM64架构关键特性

特性 描述 对调度器的影响
多核异构(big.LITTLE / DSU) 大小核架构,不同核心频率/功耗/微架构差异巨大 调度器需感知核心类型,实现异构负载均衡
通用定时器(Generic Timer) CNTVCT_EL0 提供虚拟计数器,频率由平台定义 vruntime/deadline 计算依赖精确时间基准
缓存拓扑 L1/L2 私有缓存 + L3 共享缓存,簇间延迟差异 调度器需考虑缓存亲和性,减少迁移开销
电源管理(PSCI / cpuidle) 支持 CPU 热插拔、深度睡眠状态 调度器需配合 idle 管理,避免唤醒延迟
虚拟化扩展(VHE / nVHE) 支持 KVM 虚拟化,Host/Guest 调度协同 调度器需处理虚拟机 vCPU 的调度上下文
SVE / SME 向量扩展 可变长度向量寄存器,上下文切换开销大 调度器需优化 SVE 上下文保存/恢复策略

1.3.2 移植必要性的三个维度

  1. 性能维度: x86调度器假设同构核心,ARM64的big.LITTLE架构要求调度器能识别核心类型(通过 cpu_capacity 值),将重负载调度到大核,轻负载调度到小核。未经移植的调度器会导致大核过载或小核浪费。
  2. 功耗维度: ARM64移动设备对功耗极度敏感。调度器需与 cpuidle 子系统协同,在CPU空闲时快速进入深度睡眠。CFS/EEVDF的调度周期和粒度直接影响唤醒频率和功耗。
  3. 实时维度: ARM64在汽车、工业控制领域广泛应用,需要 PREEMPT_RT 补丁。调度器移植必须确保自旋锁、中断上下文、RCU等机制在ARM64上的实时性符合要求。
常见移植误区: 很多开发者认为“调度器是架构无关的”,直接将x86的调度器配置移植到ARM64。实际上,arch/arm64 下的 Kconfig 中关于 SCHED_MC、SCHED_SMT、ARM64_HAS_32BIT_EL0 等选项必须根据具体SoC配置,否则调度器无法正确识别拓扑和指令集兼容性。

1.4 调度器架构与ARM64的映射关系

下图展示了Linux调度器核心组件与ARM64硬件特性的映射关系,帮助理解移植工作的切入点。

Linux调度器核心组件与ARM64硬件映射 调度器核心层 CFS / EEVDF 调度类 vruntime / deadline 计算 负载均衡 (load_balance) 组调度 (cgroup) 调度策略 (SCHED_OTHER/RT/DEADLINE) ARM64 架构适配层 cpu_capacity 计算 拓扑检测 (MC/SMT) 上下文切换 (switch_to) 中断/异常处理 SVE/SME 上下文保存 ARM64 硬件抽象层 通用定时器 (CNTVCT_EL0) PSCI 电源管理 GIC 中断控制器 缓存一致性协议 MMU / 页表管理 移植核心工作:在架构适配层实现 cpu_capacity 校准、拓扑检测、上下文切换优化 确保调度器核心层与ARM64硬件抽象层正确交互 调度决策 上下文切换 硬件执行 图:调度器三层架构与ARM64硬件映射关系

1.5 本课程实践路线图

本课程将按照以下路径,逐步完成调度器从x86到ARM64的完整移植适配:

  1. 环境准备: 搭建ARM64交叉编译环境,配置QEMU/真实硬件调试环境
  2. 内核配置: 分析 arch/arm64/Kconfig 调度相关选项,生成最小配置
  3. 拓扑检测: 实现 store_cpu_topology(),正确识别DSU簇和核心类型
  4. cpu_capacity 校准: 基于Dhrystone或EEMBC基准测试,计算每个核心的算力值
  5. 定时器适配: 校准 arch_timer 频率,确保vruntime/deadline计算准确
  6. 上下文切换优化: 优化 cpu_switch_to() 汇编代码,减少寄存器保存/恢复开销
  7. 负载均衡改造: 修改 find_busiest_group() 和 find_idlest_cpu() 支持异构核心
  8. 电源管理协同: 实现 select_task_rq_fair() 与 cpuidle 的唤醒延迟优化
  9. 实时性增强: 移植 PREEMPT_RT 补丁,验证ARM64上的抢占延迟
  10. 性能验证: 使用 cyclictest、hackbench、schbench 等工具进行基准测试
课程目标: 完成本课程后,学员将能够独立将Linux 6.6+内核的CFS/EEVDF调度器移植到任意ARM64 SoC平台,并针对big.LITTLE架构进行性能调优。