SMP:核心数量如何影响调度,多核为什么不能只靠调度器¶
日期:2026-09-16 标签:smp, riscv, scheduler, ipi, memory-ordering, k3
来源:QEMU 单核与真板多核差异的深度讲解。 范围:为什么单核调通不能直接在多核真板上让调度器自动多核调度。
结论¶
调度器只能在已经启动、初始化并加入调度域的 hart 之间分配任务。它不是 CPU 启动器,也不会自动修复中断、内存映射、锁、IPI 和驱动里的单核假设。
所以单核调通后直接烧多核真板,两种结果:
- 真板只启动一个 hart:仍按单核运行,但没利用多核。
- 真板启动多个 hart:SMP 基础没准备好时,要么在进调度器之前崩溃,要么运行一段时间后出现竞态、丢唤醒、死锁和队列损坏。
核心数量到底指什么¶
至少四个完全不同的数字:
| 概念 | 含义 |
|---|---|
| configured / max hart | 内核编译时最多支持多少 hart,例如 MAX_CPU_NUM=16 |
| present hart | DT、固件或硬件描述里存在多少 hart |
| online hart | 已启动并完成 per-hart 初始化的 hart |
| schedulable hart | 调度器允许放置普通任务的 hart |
以 K3 为例:静态资料有 16 个 AP hart,但真板启动可能只有其中 8 个被固件拉起,也可能有 hart 被保留。调度器真正能用的只有最后一层,不是 datasheet 上的总数。
当前 StarryOS 构建里 SMP=16 同时影响六处:
MAX_CPU_NUM等编译期配置。- 是否启用 SMP feature。
- per-hart 数组、run queue、启动栈的大小。
- QEMU 的
-smp 16参数。 - 内核要启动并等待多少个 secondary hart。
- 中断控制器要映射多少个 hart context。
因此它不只是"告诉调度器有 16 个核"。
为什么从 1 核变 2 核是质变¶
从 8 核到 16 核大多是规模变化;从 1 核到 2 核是并发模型变化。
单核的"关中断"不再是互斥¶
单核:hart 0 关中断,本 hart 上的 task 和 ISR 都无法打断临界区,共享数据暂安全。
多核:hart 0 关自己中断,hart 1 仍可同时访问同一份数据。这正是当前 critical-section 的问题。本地关 IRQ 只能阻止本 hart 上的并发,阻止不了另一个 hart。
多核需要三件事叠加:
- 本地 IRQ 关闭
- 跨 hart 全局锁
- Acquire/Release 内存序
调度器不会自动给所有共享变量加锁。
唤醒另一个 hart 需要 IPI¶
假设 queue owner 固定在 hart 5 并进入 WFI 休眠,网络 IRQ 在 hart 11 到达。hart 11 把 owner 标记为 Ready,hart 5 仍在 WFI。
只把任务放入 hart 5 的 run queue,不一定能立刻唤醒它,通常还要发核间中断 IPI:
否则任务只能等下次 timer tick 偶然醒来。调度器可以选择任务,前提是有人让目标 hart 获得一次调度机会。
每个 hart 都需要独立初始化¶
一个 secondary hart 加入调度前,至少需要:
固件启动 hart
→ 分配独立启动栈
→ 初始化 per-CPU 数据
→ 设置页表和 trap 入口
→ 初始化本地 timer 和中断 context
→ 创建本 hart 的 idle task
→ 创建本 hart 的 run queue
→ 标记 online
→ 进入调度循环
任一步没完成,调度器都不能安全地把任务送过去。
当前 stack runner 可能在 secondary run queue 初始化完成前生成,而默认 affinity 允许它落到任意 hart,于是被分配到尚未初始化的 run queue。
中断控制器资源也跟 hart 数量有关¶
SMP=16 探针是现成的例子。当前 QEMU 平台只映射了约 0x210000 大小的 PLIC 区域,而 QEMU 选 boot hart 11,对应 supervisor interrupt context 23:
结果内核还没初始化调度器,就在 PLIC 初始化时 page fault。调度器无能为力,因为故障发生在它初始化之前。
单核掩盖内存序和竞态¶
单核上很多错误代码"看起来能运行":写状态、唤醒任务、任务读状态,基本按时间顺序发生。
多核上写状态和唤醒可能发生在 hart 0,读取在 hart 1。缺少 Release/Acquire 关系时,hart 1 可能看见新的 generation,却仍读到旧状态字段,或丢失一次 register/wake 交错。这是共享内存协议问题,不是调度算法问题。
调度器能自动做什么¶
底层条件成立后,调度器能自动:
- 从 online run queue 选择任务。
- 根据 affinity 选择允许的 hart。
- 做负载分配。
- 任务阻塞时运行其他任务。
- 任务被唤醒后重新排队。
- 在允许的 hart 之间迁移任务。
不能自动完成:
- 启动 secondary hart。
- 初始化每个 hart 的 trap、timer、中断 context。
- 扩大 PLIC / IMSIC MMIO 映射。
- 判断某个 hart 是否被固件或 RP 系统保留。
- 给共享数据补锁和内存序。
- 保证驱动硬件 queue 只有一个 owner。
- 给远端 ready task 发 IPI(除非调度器已实现该机制)。
- 区分 K3 的 X100、A100、RT24,除非平台提供拓扑和能力信息。
能否先 QEMU 单核,真板再让调度器多核?¶
能,但要区分两个目标。
目标一:先让真板单核启动¶
完全可行,也是合理的 bring-up 顺序:
此时 K3 即使有 16 个 AP hart,也只把一个 hart 加入 online 集合,调度器仍按单核工作。
目标二:真板自动使用多核¶
不能仅靠"真板多核,调度器自己处理"。需要先证明一组条件:
平台能启动所有目标 hart
→ 每个 hart 初始化完成
→ 调度器 run queue 可用
→ 跨 hart 锁正确
→ remote wake / IPI 正确
→ 驱动和 waker 没有单核假设
→ 实际 online 集合被正确报告
否则直接在真板打开多核,会把平台、内存、中断、调度器、驱动的问题混在一起,排障困难。
为什么计划看起来调整很多¶
不是因为"16 这个数字需要特殊代码",而是当前系统只取得了单核资格,成熟 SMP 内核早已具备的基础设施还没补齐:
- secondary bring-up
- per-CPU 数据
- SMP-safe 锁
- IPI reschedule
- CPU affinity
- online / offline 管理
- 中断路由
- 内存序规则
- SMP-safe 驱动
现在做的不是按核数写多套适配,而是把配置和逻辑改成:
目标正是以后不再为 4 核、8 核、16 核分别适配。
对当前计划最准确的理解¶
SMP=16 有三层作用:
- 容量检查:数组、mask、栈、PLIC 映射能否覆盖 K3 AP 域规模。
- 并发检查:锁、waker、IPI、run queue 是否真正支持跨 hart。
- 压力与边界检查:非零 boot hart、高编号 context、更多并发交错是否正常。
它不能证明 K3 真板本身,但能提前消灭与设备无关的 SMP 软件问题。
之后真板仍分两步: