3. 多核任务模型与调度机制
在单核时代,任务调度只需回答“下一个该谁运行”。进入多核之后,问题变成两个维度:谁运行与在哪运行。VxWorks 的 SMP 内核把这两件事拆开处理——全局调度器决定优先级顺序,负载均衡与亲和性决定 CPU 归属。理解这三者的配合关系,是写出可预测多核实时应用的前提。
一、SMP 任务模型:全局就绪队列与多核并发
VxWorks SMP 采用共享内存、单一内核镜像的对称多处理模型。所有任务共享同一地址空间,内核数据结构由自旋锁保护,任务可以被任意 CPU 核调度执行。与 AMP(非对称多处理)不同,SMP 下不存在“这个任务固定属于那个核”的静态绑定,任务的就绪状态进入一个全局视图,由调度器统一裁决。
关键机制是全局就绪队列 + 每核本地队列的混合结构。任务创建后进入全局就绪队列,调度器按优先级选取可运行任务,再通过负载均衡把任务分配到空闲核。VxWorks 提供 256 个优先级(0 最高,255 最低),数值越小优先级越高。同一优先级内默认采用时间片轮转(Round-Robin),时间片长度由 kernelTimeSlice() 设定。
需要特别注意的是任务锁(Task Lock)在 SMP 下的语义变化。单核中 taskLock() 禁止调度,任务独占 CPU;SMP 中它只禁止当前任务被抢占,其他核上的任务照常运行。因此不能用任务锁实现跨核互斥,跨核临界区必须使用信号量或互斥量。
二、任务亲和性:把任务“钉”在指定核上
亲和性(Affinity)用于约束任务可运行的 CPU 集合。VxWorks 通过 taskCpuAffinitySet() 设置亲和掩码,掩码的每一位对应一个 CPU 核。例如 4 核系统中掩码 0x5(二进制 0101)表示任务只能在 CPU0 和 CPU2 上运行。
/* 将任务绑定到 CPU0 和 CPU2 */
cpuset_t affinity;
CPUSET_ZERO(affinity);
CPUSET_SET(affinity, 0);
CPUSET_SET(affinity, 2);
taskCpuAffinitySet(taskId, affinity);
/* 查询当前亲和性 */
cpuset_t cur;
taskCpuAffinityGet(taskId, &cur);
亲和性的典型用途有三类:缓存局部性优化(让高频访问同一数据的任务共享 L2 缓存)、中断与任务协同(把处理某网卡中断的任务绑定到该中断所属核,减少跨核缓存失效)、隔离关键任务(把硬实时任务独占一个核,避免被其他任务干扰)。
但亲和性不是越多越好。过度绑定会破坏负载均衡:若 4 个核上只允许任务跑在 CPU0,其余 3 核空闲而 CPU0 过载,实时性反而恶化。经验做法是只对确有局部性或隔离需求的任务设置亲和性,其余任务保持默认全核可调度。
三、优先级继承:解决优先级反转
多核实时系统中最隐蔽的威胁是优先级反转。经典场景:低优先级任务 L 持有互斥量,高优先级任务 H 等待该互斥量而阻塞,中优先级任务 M 抢占 L 运行,导致 H 被 M 间接延迟。在单核上这已足够致命,多核下 M 可能在另一个核上运行,反转窗口更难预测。
VxWorks 的互斥量(semMCreate())支持优先级继承协议。当 H 阻塞在 L 持有的互斥量上时,L 的优先级被临时提升到 H 的优先级,使 L 能尽快执行完临界区并释放互斥量,随后 L 恢复原优先级。这样 M 无法抢占 L,H 的等待时间被压缩到临界区执行时间。
/* 创建支持优先级继承的互斥量 */
SEM_ID mutex = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
/* 任务中使用 */
semTake(mutex, WAIT_FOREVER);
/* 临界区:访问共享资源 */
semGive(mutex);
SEM_INVERSION_SAFE 是启用优先级继承的关键选项。若遗漏该选项,互斥量退化为普通二值信号量,优先级反转将不受控。此外,优先级继承只对互斥量有效,二值信号量不提供该保护——这是选型时最容易踩的坑。
四、实时调度策略与多核下的取舍
VxWorks 默认采用基于优先级的抢占式调度。任务一旦就绪且优先级高于当前运行任务,立即抢占。多核下这一语义扩展为:高优先级任务可在任意核上抢占低优先级任务,前提是亲和性允许。
调度策略的选择可归纳为三条实践原则:
- 优先级分配遵循速率单调(RM)思想:周期越短、截止期越紧的任务优先级越高。对周期任务集,RM 在单核上是最优静态优先级分配;多核下虽非严格最优,但仍是可靠的工程起点。
- 临界区尽量短:优先级继承只能压缩等待,不能消除。临界区越长,继承期间对同优先级其他任务的影响越大。
- 避免优先级反转链:多个互斥量嵌套时,继承关系可能形成链式提升,增加分析复杂度。能用无锁队列或消息传递替代共享内存互斥的,优先替代。
taskCpuAffinitySet() 固定关键任务,减少迁移不确定性。五、机制协同:一张图看清四者关系
下面这张图把 SMP 调度、亲和性、优先级继承与实时策略放在同一视图中,展示它们如何共同决定一个任务从就绪到执行再到临界区保护的完整路径。
图中左侧是全局就绪队列,任务按优先级排列;中间调度器完成优先级裁决与负载均衡;右侧亲和性掩码进一步约束任务可落到的核。下方红色区域展示优先级继承:当高优先级任务 H 阻塞在低优先级任务 L 持有的互斥量上时,L 被临时提升到 H 的优先级,使中优先级任务 M 无法抢占,从而压缩 H 的等待时间。
六、工程实践中的参数与判断依据
以一个 4 核 VxWorks 系统为例,假设有三个任务:控制任务(周期 1ms,WCET 200μs)、通信任务(周期 5ms,WCET 800μs)、日志任务(周期 20ms,WCET 2ms)。按 RM 原则分配优先级:控制任务 P1,通信任务 P5,日志任务 P10。控制任务设置亲和性到 CPU0 独占,通信任务允许 CPU1–CPU3,日志任务允许全部核。
判断调度是否满足实时性,可用利用率检验:单核上 RM 的充分条件为 U = Σ(Ci/Ti) ≤ n(2^(1/n) − 1)。本例中控制任务 U1 = 0.2,通信任务 U2 = 0.16,日志任务 U3 = 0.1,总利用率 0.46,远低于 3 任务时的理论上限约 0.78,余量充足。多核下由于任务可并行,实际余量更大,但需扣除自旋锁与缓存一致性开销,建议预留 20%–30% 余量。
SEM_INVERSION_SAFE 导致优先级反转;是否亲和性设置过窄造成单核过载;是否在临界区内调用了可能阻塞的 I/O 或 taskDelay()。这三类问题在多核 SMP 下比单核更常见。最后强调一点:SMP 调度、亲和性、优先级继承不是三个独立开关,而是一套协同机制。调度器决定顺序,亲和性决定位置,优先级继承保护临界区。任何一项配置失当,都会让另外两项的效果打折扣。实际项目中建议先用默认全核调度跑通功能,再根据性能剖析结果逐步引入亲和性绑定和互斥量保护,避免过早优化引入难以定位的时序问题。