3. SMP调度器核心数据结构

在VxWorks的SMP(对称多处理)调度实现中,调度器并不是一个“大锁保护下的单一队列”,而是由一组分层、分域的数据结构共同支撑:每个CPU有自己高频访问的本地就绪队列和运行队列,任务控制块(TCB)中保存着决定“谁能被选中、在哪个CPU上运行”的调度字段,同时还有全局结构负责跨CPU的负载均衡与迁移决策。理解这些结构以及它们之间的协同关系,是分析SMP调度延迟、负载不均和任务迁移行为的基础。

一、就绪队列与运行队列:两个不同语义的集合

就绪队列(ready queue)保存的是“已经具备运行条件、等待被调度器选中”的任务。在SMP中,就绪队列通常按优先级组织,同一优先级内采用轮转(round-robin)或FIFO策略。运行队列(run queue)则更强调“当前正在某个CPU上执行”的任务集合,在VxWorks的每CPU调度实体中,运行队列往往与就绪队列共享同一套优先级位图结构,但通过“当前任务指针”区分出正在运行的那一个。

需要特别注意的是:在SMP语境下,“就绪”与“运行”并不是互斥的两个全局集合,而是每个CPU视角下的状态。一个任务在CPU0上处于运行态,在CPU1的本地队列中可能根本不存在;只有当它被迁移或重新入队时,才会出现在另一个CPU的队列结构中。因此,分析调度行为时必须先明确“站在哪个CPU的视角看”。

二、每CPU调度实体:本地化的核心

VxWorks为每个CPU维护一个调度实体(per-CPU scheduler entity),其中包含:

  • 优先级位图(ready bitmap):用位表示哪些优先级当前有就绪任务,调度器通过查找最高有效位快速定位最高优先级,避免遍历整个队列。
  • 各优先级就绪链表头:每个优先级对应一个任务链表,同优先级任务按策略排队。
  • 当前任务指针(current task):指向该CPU正在运行的任务,是运行队列语义的体现。
  • 空闲任务(idle task):当本地无就绪任务时运行,同时触发负载均衡检查。
  • 调度锁与中断屏蔽状态:影响本地调度决策能否立即生效。

这种“每CPU一份”的设计,使得大多数调度决策可以在本地完成,减少跨CPU争用。但代价是:全局负载信息不再集中,必须通过额外的全局结构来协调。

三、任务控制块中的调度字段

TCB是任务的静态描述与动态状态载体。与SMP调度直接相关的字段通常包括:

字段作用
priority任务优先级,决定在就绪队列中的位置
status任务状态(READY、RUNNING、PEND、DELAY等)
cpuAffinityCPU亲和性掩码,限制任务可运行的CPU集合
currentCpu当前所在CPU编号,迁移时更新
readyLink挂入某CPU就绪链表的链接字段
lockCnt / schedLock调度锁计数,影响任务能否被抢占或迁移

其中,cpuAffinity与currentCpu是SMP特有的关键字段。亲和性决定了任务“允许去哪”,currentCpu决定了任务“现在在哪”。当两者不一致时,调度器可能触发迁移。调度锁计数则是一个容易被忽视但影响很大的字段:如果任务持有调度锁,即使更高优先级任务就绪,本地调度也可能被延迟。

四、全局与本地队列的协同关系

全局结构通常不直接保存每个任务的完整队列,而是保存“每个CPU的负载摘要”或“可迁移任务列表”。其协同逻辑可以概括为:

  1. 任务创建或唤醒时,优先放入其亲和性允许的某个CPU的本地就绪队列;
  2. 本地调度器在每次调度点检查本地最高优先级就绪任务;
  3. 若本地空闲或负载明显偏低,空闲任务触发负载均衡,查询全局负载摘要;
  4. 若发现其他CPU有可迁移任务且亲和性允许,则将该任务从源CPU本地队列摘除,挂入目标CPU本地队列;
  5. 迁移过程中更新TCB的currentCpu字段,并可能触发目标CPU的重新调度。

这一协同关系的关键在于“本地优先、全局兜底”:绝大多数调度在本地完成,只有负载不均时才引入跨CPU操作。因此,SMP调度性能既取决于本地队列的查找效率,也取决于负载均衡的触发频率和迁移开销。

VxWorks SMP 调度器核心数据结构协同关系 CPU0 本地调度实体 优先级位图 ready bitmap 快速定位最高优先级 各优先级就绪链表 同优先级 RR / FIFO 当前任务指针 current 运行队列语义 空闲任务 idle task 触发负载均衡检查 CPU1 本地调度实体 优先级位图 ready bitmap 快速定位最高优先级 各优先级就绪链表 同优先级 RR / FIFO 当前任务指针 current 运行队列语义 空闲任务 idle task 触发负载均衡检查 全局负载摘要 / 迁移协调 各CPU负载计数 可迁移任务候选 亲和性过滤 任务控制块 TCB priority status cpuAffinity currentCpu readyLink lockCnt / schedLock 负载上报 负载上报 迁移入队 迁移入队 挂入 挂入 协同要点: 1. 本地调度优先:CPU0/CPU1 各自通过位图+链表完成最高优先级选择,无需全局锁。 2. 全局兜底:空闲任务或负载不均时,查询全局摘要,筛选可迁移任务。 3. 迁移更新:TCB 的 currentCpu 与 readyLink 随迁移更新,亲和性决定迁移是否允许。 4. 调度锁影响:lockCnt 非零时,本地抢占与迁移均可能被延迟。

五、从结构到行为:一个可观察的示例

假设系统有两个CPU,任务T1优先级10、亲和性仅允许CPU0,任务T2优先级10、亲和性允许CPU0和CPU1,任务T3优先级20、亲和性允许CPU0和CPU1。初始时T1和T2都在CPU0就绪队列,T3在CPU1运行。此时CPU0的位图在优先级10置位,CPU1位图在优先级20置位。

当T3阻塞后,CPU1空闲任务运行,触发负载均衡。全局摘要显示CPU0在优先级10有两个就绪任务,且T2亲和性允许CPU1。于是T2从CPU0就绪链表摘除,挂入CPU1就绪链表,TCB的currentCpu更新为1。此后CPU0运行T1,CPU1运行T2,负载得到均衡。

如果T2的cpuAffinity只允许CPU0,则迁移被拒绝,CPU1继续空闲,CPU0继续运行T1和T2。这说明亲和性字段直接决定了全局队列协同的可行边界。

在实际分析中,可以通过VxWorks的taskShow、spy或系统查看器观察任务当前所在CPU、优先级和状态。若发现某CPU长期空闲而另一CPU就绪队列堆积,应优先检查可迁移任务的亲和性设置和调度锁持有情况,而不是盲目调整优先级。

六、关键判断依据与常见误区

判断SMP调度结构是否健康,可以关注三个信号:一是各CPU空闲任务运行时间占比是否接近;二是高优先级就绪任务是否长期集中在单个CPU;三是任务迁移频率是否异常偏高。迁移本身有开销,频繁迁移会抵消负载均衡带来的收益。

常见误区是把“就绪队列”当成全局唯一队列。在VxWorks SMP中,就绪队列本质上是每CPU本地的,全局结构只做摘要和协调。另一个误区是忽略调度锁:即使任务已就绪且亲和性允许,若目标CPU或源CPU处于调度锁保护区间,迁移和抢占都可能被推迟,表现为“看起来应该调度却没有调度”。

因此,理解这些数据结构的正确方式,不是孤立记忆字段,而是建立“本地队列快速决策 + 全局摘要兜底协调 + TCB字段约束边界”的整体模型。后续分析调度延迟、CPU占用不均或任务迁移异常时,都可以回到这个模型逐层排查。