4. 多核调度策略:全局调度与分区调度、静态优先级与动态优先级

好,咱们进入正题。多核调度策略,说白了就是决定「哪个核在什么时候跑哪个任务」。这个问题,我在做第一个多核项目时就踩过坑。当时想得简单,觉得多核嘛,任务扔上去就行。结果呢?优先级反转、核间干扰、Cache 命中率暴跌……嗯,都是血泪教训。

今天咱们把两种核心策略讲透:全局调度 vs 分区调度,以及静态优先级 vs 动态优先级。这四者组合起来,基本覆盖了市面上 90% 的嵌入式多核方案。

4.1 全局调度:任务可以在任何核上跑

全局调度,就是系统里有一个全局的调度器。它维护一个统一的任务就绪队列。哪个核空闲了,就从队列里抓一个优先级最高的任务去跑。

优点很明显:

  • CPU 利用率高。不会出现一个核忙死、另一个核闲死的情况。
  • 负载均衡是自动的。你不需要手动分配任务。

但坑也不少:

  • 调度开销大。所有核抢同一个锁,你想想看,核多了以后锁竞争有多恐怖。
  • Cache 亲和性差。任务今天在核0跑,明天在核3跑,Cache 里的数据全得重载。我见过一个项目,因为频繁迁移任务,性能反而比单核还差。
  • 可预测性低。你很难精确算出最坏执行时间(WCET)。

我的建议:全局调度适合非实时或软实时系统。比如车载信息娱乐系统,偶尔卡一下没关系。但如果你做的是刹车控制、安全气囊——千万别用纯全局调度。

4.2 分区调度:任务绑定到固定核

分区调度就简单粗暴了。每个任务在编译时或启动时就绑定到一个核上。核与核之间,任务不迁移。

优点:

  • 可预测性极强。每个核上的任务集是固定的,WCET 分析容易得多。
  • Cache 命中率高。任务一直在一个核上跑,数据局部性好。
  • 调度器实现简单。每个核可以独立跑一个单核调度器(比如 OSEK 或 AUTOSAR 的 SC1/SC2/SC3/SC4)。

缺点:

  • 负载可能不均。你手动分配任务,很难做到完美平衡。
  • 灵活性差。任务多了、少了,都得重新分区。

避坑指南:我曾经在一个项目中,把两个高负载的通信任务分到了同一个核上。结果那个核的利用率飙到 95%,其他核才 30%。后来我学乖了——分区时先用工具做负载仿真,别凭感觉分。

4.3 静态优先级:简单可靠,但不够灵活

静态优先级,就是每个任务在系统启动前就定好了优先级。运行期间不变。这是 AUTOSAR 里最常用的方式。

优点:

  • 实现简单。调度器只需要比较优先级,不需要动态计算。
  • 可预测性好。优先级固定,响应时间分析可以用经典公式算出来。
  • 适合硬实时系统。比如 10ms 周期任务必须跑完,优先级设高就行。

缺点:

  • 不灵活。如果运行时出现突发高优先级任务,你没法临时提权。
  • 优先级反转问题。低优先级任务占着资源,高优先级任务等——嗯,经典问题,需要靠优先级继承或天花板协议解决。

我个人习惯:在 AUTOSAR 的 SC3(带时间保护的静态调度)中,我通常把周期任务按 Deadline Monotonic 分配优先级。周期越短,优先级越高。这个规则简单,而且经过数学证明是次优的。

4.4 动态优先级:灵活但代价高

动态优先级,就是任务的优先级可以在运行时改变。最经典的算法是 EDF(最早截止时间优先)。谁离截止时间最近,谁优先级最高。

优点:

  • 理论上 CPU 利用率可以做到 100%(对于可调度任务集)。
  • 适应性强。任务负载变化时,调度器自动调整。

缺点:

  • 实现复杂。每次调度都要计算所有任务的截止时间。
  • 可预测性差。WCET 分析困难,因为优先级一直在变。
  • 过载时性能崩溃。任务一多,系统可能直接挂掉。

注意:在 AUTOSAR 多核系统中,动态优先级很少用于安全关键任务。我见过一个团队尝试在 BSW 调度器里用 EDF,结果测试时发现中断延迟不可控。后来老老实实改回了静态优先级。

4.5 四种组合,怎么选?

咱们把上面两个维度组合一下,得到四种典型策略:

组合 适用场景 典型例子
分区 + 静态优先级 硬实时、安全关键 AUTOSAR SC3/SC4,刹车、转向
分区 + 动态优先级 软实时、负载变化大 多媒体处理,自适应巡航
全局 + 静态优先级 非实时、负载均衡 后台诊断、日志记录
全局 + 动态优先级 实验性、研究用途 学术原型、Linux CFS 调度

我个人最推荐的是分区 + 静态优先级。为什么?因为它在可预测性和实现复杂度之间取得了最好的平衡。AUTOSAR 的 SC3 和 SC4 就是基于这个思路设计的。

4.6 一个实际例子:AUTOSAR 多核调度配置

假设我们有一个双核 ECU,跑 4 个任务:

  • Task_A:10ms 周期,安全关键,绑定核0,优先级 10
  • Task_B:20ms 周期,安全关键,绑定核0,优先级 8
  • Task_C:50ms 周期,非安全,绑定核1,优先级 6
  • Task_D:100ms 周期,非安全,绑定核1,优先级 4

在 AUTOSAR 的配置工具里,你会这样设置:

// 伪代码示例
OsTask Task_A = {
  .name = "Task_A",
  .period = 10,
  .core = 0,
  .priority = 10,
  .schedule = SCHEDULE_FULL  // 抢占式
};

OsTask Task_B = {
  .name = "Task_B",
  .period = 20,
  .core = 0,
  .priority = 8,
  .schedule = SCHEDULE_FULL
};

// 核0上,Task_A 优先级高于 Task_B,所以 Task_A 会抢占 Task_B
// 核1上,Task_C 和 Task_D 独立运行,互不干扰

一个小技巧:分区时,尽量把有数据依赖的任务分到同一个核上。这样可以减少核间通信(IOC)的开销。我做过一个项目,把传感器采集和信号处理分到不同核,结果 IOC 延迟导致控制周期超时。后来合并到一个核,问题解决。

4.7 总结一下

多核调度没有银弹。全局调度灵活但不可控,分区调度可控但不灵活。静态优先级简单可靠,动态优先级灵活但复杂。

我的经验是:先选分区调度,再选静态优先级。如果性能不够,再考虑局部优化——比如把某个高负载任务单独分一个核,或者用优先级继承解决反转问题。别一上来就搞全局+动态,那是在给自己挖坑。

嗯,下一章咱们聊聊核间同步与通信。那又是一个容易翻车的地方。