4、分区调度算法:P-EDF、P-RM与任务到核的静态分配

好,咱们接着聊分区调度。前面几章我们把多核调度的整体框架搭起来了,也聊了全局调度那些事儿。这一章,我重点讲讲分区调度里的三个核心问题:P-EDFP-RM,还有任务到核的静态分配

说实话,我在实际项目中,分区调度用得比全局调度多得多。为什么?因为它的确定性好,分析起来心里有底。你想想看,每个核跑自己的任务集,互不干扰,出了问题也好定位。嗯,这里有个前提——你得把任务分对地方。

4.1 分区调度的基本思想

分区调度,说白了就是「分而治之」。我们把整个系统的任务,按照某种规则,提前分配到各个CPU核心上。每个核心独立运行自己的调度器,核心之间没有任务迁移。

这样做的好处很明显:

  • 隔离性强:一个核上的任务出问题,不会波及到其他核
  • 可分析性好:每个核可以当作单核系统来分析可调度性
  • 实现简单:不需要复杂的负载均衡机制

但代价呢?就是灵活性差。一旦分配好了,运行期间不能动态调整。我曾经在一个项目中吃过这个亏——任务分配时没考虑周全,结果某个核负载过高,另一个核却闲着。那叫一个憋屈。

4.2 分区最早截止时间优先(P-EDF)

P-EDF,就是把EDF调度算法用在每个核上。每个核上的任务,按照绝对截止时间排序,截止时间越早,优先级越高。

我个人习惯在软实时系统中用P-EDF。为什么?因为EDF的CPU利用率可以跑到100%(理论上),比RM高出一截。但要注意,EDF有个「多米诺骨牌效应」——一旦一个任务超期,后面的任务可能跟着全崩。

核心要点:P-EDF的可调度性条件

对于每个核上的n个任务,如果满足:

∑(Cᵢ / Tᵢ) ≤ 1

那么该核上的所有任务都是可调度的。其中Cᵢ是任务i的最坏执行时间,Tᵢ是任务i的周期。

这里有个坑——上面的条件只是充分条件,不是必要条件。也就是说,即使利用率超过1,也不一定就不可调度。但工程上,我建议你老老实实按这个条件来,别去赌那个「不一定」。

4.3 分区速率单调(P-RM)

P-RM就更有意思了。它是固定优先级调度,任务的优先级由周期决定——周期越短,优先级越高。

我在做硬实时系统时,更倾向于用P-RM。原因有三:

  1. 实现简单:优先级固定,运行时开销小
  2. 可预测性强:高优先级任务不会被低优先级任务阻塞太久
  3. 分析工具成熟:响应时间分析(RTA)方法很完善

但P-RM有个著名的「利用率天花板」问题。对于n个任务,可调度性的充分条件是:

∑(Cᵢ / Tᵢ) ≤ n × (2^(1/n) - 1)

当n→∞时,这个值趋近于ln2 ≈ 0.693。也就是说,任务越多,你能用的CPU资源越少。我曾经在一个8核系统上,每个核跑了6个任务,结果利用率只能用到70%左右,剩下的30%就这么浪费了。心疼啊。

我的建议:如果任务周期差异很大(比如有的1ms,有的100ms),用P-RM比较合适。如果任务周期都差不多,P-EDF可能更好。

4.4 任务到核的静态分配

这才是分区调度的精髓所在。任务分得好不好,直接决定了系统能不能跑起来。

静态分配,就是在系统启动前,把每个任务固定到某个核上。分配策略有很多种,我挑几个常用的说说:

分配策略 核心思想 适用场景
首次适应(First-Fit) 按顺序找第一个能放下的核 任务数量少,快速原型
最佳适应(Best-Fit) 找剩余容量最接近任务需求的核 追求高利用率
最差适应(Worst-Fit) 找剩余容量最大的核 平衡负载
分组分配(Partitioning) 按任务类型分组,每组一个核 功能模块化设计

我个人比较喜欢最佳适应策略。虽然计算量稍大,但能最大化利用CPU资源。不过要注意,最佳适应容易造成「碎片化」——每个核上都剩一点零碎容量,但放不下新任务。

避坑指南:我曾经在一个项目中,用首次适应分配了20个任务到4个核上。结果跑起来才发现,有两个核的利用率超过95%,另外两个核才50%。后来改用最佳适应,重新分配了一遍,利用率均衡多了。所以,分配策略一定要根据实际负载来选,别偷懒。

4.5 一个简单的分配示例

假设我们有4个任务,要分配到2个核上。任务参数如下:

任务 周期T (ms) 执行时间C (ms) 利用率U = C/T
Task1 10 3 0.30
Task2 20 5 0.25
Task3 15 4 0.27
Task4 50 8 0.16

用最佳适应策略,分配过程是这样的:

  1. 先排个序,按利用率从大到小:Task1(0.30), Task3(0.27), Task2(0.25), Task4(0.16)
  2. Task1放到Core0,Core0剩余容量:1 - 0.30 = 0.70
  3. Task3放到Core0?0.27 ≤ 0.70,可以。Core0剩余:0.70 - 0.27 = 0.43
  4. Task2放到Core0?0.25 ≤ 0.43,可以。Core0剩余:0.43 - 0.25 = 0.18
  5. Task4放到Core0?0.16 ≤ 0.18,可以。Core0剩余:0.18 - 0.16 = 0.02

最终,所有任务都放到了Core0上,Core1空闲。这显然不是最优解。如果手动调整一下:

// 更好的分配方案
Core0: Task1(0.30) + Task2(0.25) = 0.55
Core1: Task3(0.27) + Task4(0.16) = 0.43

这样两个核的负载就均衡多了。所以,自动分配算法只是起点,人工调优才是关键

4.6 总结与建议

好了,这一章的内容就这些。我最后给你几个实用建议:

  • 选P-EDF还是P-RM? 看你的实时性要求。硬实时选P-RM,软实时选P-EDF。
  • 任务怎么分? 先用自动分配算法打个底,再根据实际运行数据手动调优。
  • 别忘了留余量:别把CPU利用率推到100%,留个10%-20%的余量,给未来扩展和突发负载。

下一章,我们会聊聊混合调度策略——把分区调度和全局调度结合起来。嗯,那才是真正考验架构师功底的地方。