4. VxWorks SMP调度策略与优先级模型
VxWorks SMP 内核在单核调度语义之上叠加了多核并行执行这一维度,使得“优先级”不再只是决定谁先运行,还决定了任务可能同时运行在几个核上、锁竞争时谁被阻塞、以及阻塞后优先级如何被临时抬升。理解这一章的关键,是把调度策略拆成两条正交的线:一条是就绪队列的选择规则(优先级抢占 + 同优先级时间片轮转),另一条是共享资源访问时的优先级保护规则(优先级继承与优先级天花板)。两条线在单核上表现接近,在多核上却会出现明显差异。
一、基于优先级的抢占调度在多核下的语义
VxWorks 的优先级数值约定为 0 最高、255 最低,数值越小优先级越高。调度器始终从就绪任务中选出优先级最高的任务投入运行。在单核上,这意味着任一时刻只有一个任务在跑;在 SMP 下,内核维护的是全局就绪队列,每个核在调度点从全局队列中取走当前最高优先级的就绪任务。因此,如果系统中有 3 个优先级为 10 的任务、2 个核,那么这 3 个任务中的 2 个可以同时运行,第 3 个等待;一旦某个核空闲,它立即从全局队列取下一个最高优先级任务。
这里有一个容易被忽略的行为差异:高优先级任务并不保证“独占”所有核。假设核 0 正在运行优先级 5 的任务 A,核 1 正在运行优先级 20 的任务 B。此时优先级 8 的任务 C 就绪,它只能抢占核 1 上的 B,而不能抢占核 0 上的 A,因为 A 的优先级 5 高于 C 的 8。也就是说,抢占是“逐核比较”的:新就绪任务只会抢占那些当前运行任务优先级低于它的核。这一点在单核上不存在歧义,在多核上却是性能分析的核心。
二、同优先级时间片轮转的多核行为
当多个任务优先级相同时,VxWorks 使用时间片轮转(round-robin)避免饥饿。每个任务有一个时间片余量,耗尽后放回同优先级就绪队列尾部,让同优先级的下一个任务运行。单核上这表现为“轮流执行”;多核上则表现为同优先级任务可能被分散到多个核上并行执行。
举例说明:4 个优先级均为 30 的任务 T1~T4,时间片均设为 10ms,系统有 2 个核。初始时 T1、T2 分别被核 0、核 1 取走运行,T3、T4 在同优先级队列中等待。10ms 后 T1、T2 时间片耗尽,被放回队列尾部,核 0、核 1 再取 T3、T4。整体上 4 个任务在 2 个核上以约 20ms 为一个轮转周期推进。若把核数改为 4,则 T1~T4 可同时运行,轮转退化为并行,时间片只影响它们彼此之间的相对推进节奏,而不再决定“谁先谁后”。
需要强调的是,时间片轮转只在同优先级任务之间生效。不同优先级任务之间仍然是严格抢占关系,时间片不会让低优先级任务“熬到”高优先级任务让出 CPU。因此在多核上,如果高优先级任务数量少于核数,低优先级任务仍可能长期得不到执行;如果高优先级任务数量多于核数,则低优先级任务会被持续压制。
三、优先级继承与优先级天花板在多核下的行为差异
当任务通过互斥量(mutex)访问共享资源时,会出现优先级反转:低优先级任务持锁,高优先级任务等待,而中优先级任务抢占了持锁者,导致高优先级任务被间接延迟。VxWorks 提供两种抑制手段。
优先级继承(priority inheritance):当高优先级任务因等待某互斥量而阻塞时,持锁任务的优先级被临时提升到等待者中的最高优先级,直到释放锁后恢复。它解决的是“已经发生”的阻塞。在单核上,提升后持锁者能立即抢占中优先级任务,反转被消除。在多核上,提升只改变持锁任务在其所在核上的抢占地位;如果中优先级任务运行在另一个核上,它本来就不与持锁者争同一个核,因此继承对它的约束有限。真正需要关注的是:多个核上的任务同时竞争同一把锁时,持锁者被提升后可能同时压制本核上的其他任务,而其他核上的等待者仍在自旋或阻塞。
优先级天花板(priority ceiling):为每把互斥量预先指定一个天花板优先级(通常不低于所有可能使用该锁的任务的最高优先级)。任务一旦获得该锁,其优先级立即被提升到天花板值,释放后恢复。它解决的是“可能发生”的阻塞,且能防止死锁。在多核上,天花板协议的一个实际影响是:持锁任务被提升后,会以较高优先级占用其所在核,可能推迟本核上其他中等优先级任务的执行;而其他核上的任务若也试图获取同一把锁,仍会进入等待。因此天花板值的选择需要结合“哪些核上的哪些任务会访问该锁”来定,不能只按单核视角设置。
| 对比维度 | 优先级继承 | 优先级天花板 |
|---|---|---|
| 提升时机 | 高优先级任务阻塞后 | 任务获得锁的瞬间 |
| 提升目标 | 等待者中的最高优先级 | 锁预设的天花板优先级 |
| 单核效果 | 消除已发生的优先级反转 | 预防反转,并可避免死锁 |
| 多核差异 | 只约束持锁者所在核的抢占关系 | 持锁者所在核被高优先级占用,其他核等待者仍阻塞 |
| 配置要点 | 无需预设,运行时动态计算 | 需为每把锁设定合理天花板值 |
四、多核下的实践判断与配置示例
下面给出一段典型的互斥量创建与属性设置示例,用于说明继承与天花板在代码层面的差别。示例参数为教学假设,实际项目需按任务优先级分布调整。
/* 假设任务优先级:采集任务 P10,处理任务 P20,日志任务 P60 */
/* 共享资源:数据缓冲区锁 dataMutex */
/* 方式一:优先级继承 */
SEM_ID dataMutex = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
/* SEM_INVERSION_SAFE 启用优先级继承 */
/* 方式二:优先级天花板(VxWorks 中通过 semMGive/semTake 配合
天花板属性或使用 semMCreate 的扩展属性实现,此处示意天花板值) */
/* 天花板值取所有访问者中的最高优先级,即 P10 */
/* 则任何任务持锁期间优先级不低于 P10 */
/* 任务访问模式 */
semTake(dataMutex, WAIT_FOREVER);
/* 临界区:读写共享缓冲区 */
semGive(dataMutex);
在多核系统上评估这段代码时,需要额外回答三个问题:第一,这把锁会被几个核上的任务访问?如果只有单核访问,继承与天花板的行为与单核基本一致。第二,持锁临界区有多长?临界区越长,其他核上的等待者被阻塞越久,天花板提升带来的本核压制也越明显。第三,是否存在嵌套锁?嵌套时继承会沿等待链传播,而天花板需要保证外层锁的天花板不低于内层,否则可能破坏协议约束。
综合来看,VxWorks SMP 的调度策略可以归纳为:就绪选择看全局最高优先级,抢占逐核发生,同优先级靠时间片轮转分散到多核;资源保护靠继承或天花板调整持锁者优先级,但作用范围受限于持锁者所在核。分析多核性能问题时,先确认任务优先级分布与核数关系,再检查共享锁的访问路径与临界区长度,最后判断是调度饥饿、锁竞争还是优先级反转导致的延迟,才能定位到正确的调优方向。