2. VxWorks SMP内核初始化与CPU亲和性

VxWorks SMP 的启动并不是“所有核一起跑 main”这么简单。系统上电后,只有引导核(通常为 CPU0)执行完整的 BSP 初始化、内核初始化和驱动初始化;其余从核(secondary CPU)在早期被“停住”,等待引导核完成关键数据结构准备后,再通过核间中断或硬件 mailbox 被逐个唤醒,进入内核调度器。理解这条启动链,是理解后续 CPU 亲和性配置的前提。

启动流程:从引导核到从核唤醒

典型流程可以概括为:引导核执行 usrInit() → kernelInit() 建立内核基础环境 → 调用 vxCpuInit() 完成 CPU 集合初始化 → 通过 vxCpuEnable() 或 BSP 提供的 sysCpuEnable() 唤醒从核 → 从核进入 vxCpuSecondaryInit() 并挂入调度器。其中最关键的是“CPU 集合”这一抽象:VxWorks 用 cpuset_t 表示一组 CPU,内核据此决定任务和中断可以落在哪些核上。

从核唤醒通常依赖 BSP 提供的启动地址和 mailbox 寄存器。引导核把从核入口地址写入约定寄存器,再触发从核复位释放;从核启动后先建立自己的异常向量、MMU 和栈,然后调用内核提供的从核初始化钩子,最后进入空闲任务等待调度。若从核长时间未上线,常见原因是入口地址未正确写入、缓存一致性未处理、或从核时钟/电源未使能。

VxWorks SMP 启动与从核唤醒流程 引导核 CPU0 BSP 初始化:时钟、内存、串口 usrInit() → kernelInit() vxCpuInit():建立 CPU 集合 写入从核入口地址 + 释放复位 创建任务并设置亲和性 进入多核调度 从核 CPU1..N 复位释放前:等待 mailbox 读取入口地址,建立异常向量 MMU / 栈 / 缓存初始化 vxCpuSecondaryInit() 加入调度器,进入空闲任务 等待被调度 唤醒信号 就绪确认 关键点:CPU 集合初始化必须在从核唤醒前完成,否则从核无法正确加入调度

CPU 集合管理

VxWorks 提供了一组 CPU 集合操作接口,用于描述“哪些核可用、哪些核被隔离”。常用接口包括 vxCpuEnabledGet() 获取当前使能核集合、vxCpuEnabledSet() 设置使能集合、vxCpuAvailableGet() 获取系统可用核集合,以及 CPUSET_ISSET()、CPUSET_SET()、CPUSET_CLR() 等位操作宏。集合中的每一位对应一个 CPU 编号,例如 bit0 对应 CPU0。

在工程实践中,CPU 集合常用于三种场景:一是启动阶段只使能部分核,降低功耗或满足实时性隔离需求;二是运行阶段动态隔离某个核,用于独占式实时任务;三是故障排查时把可疑核从集合中移除,观察系统是否恢复稳定。需要注意的是,修改使能集合通常要求目标核处于空闲或已停止状态,否则可能破坏正在运行任务的上下文。

任务亲和性配置

任务亲和性决定任务可以在哪些 CPU 上运行。VxWorks 提供 taskCpuAffinitySet() 设置任务亲和性,taskCpuAffinityGet() 查询当前亲和性。亲和性同样用 cpuset_t 表示,若设置为仅包含 CPU2,则该任务只在 CPU2 上被调度;若设置为 CPU0 和 CPU1,则调度器可在两者间迁移。

#include <taskLib.h>
#include <cpusetLib.h>

cpuset_t affinity;
CPUSET_ZERO(affinity);
CPUSET_SET(affinity, 2);          /* 仅允许 CPU2 */

taskCpuAffinitySet(taskId, affinity);

/* 查询确认 */
cpuset_t cur;
taskCpuAffinityGet(taskId, &cur);
if (CPUSET_ISSET(cur, 2))
    printf("task bound to CPU2\n");

设置亲和性时要注意:任务创建后、首次运行前设置最稳妥;若任务已在运行,迁移可能带来缓存失效和调度延迟。对于实时任务,建议绑定到隔离核,并配合中断亲和性一起规划,避免中断在绑定核上频繁打断任务。

中断亲和性配置

中断亲和性决定某个中断源由哪个 CPU 响应。VxWorks 通过 intCpuAffinitySet() 或 BSP 提供的 sysIntCpuSet() 设置中断到 CPU 的映射。典型做法是把网络中断绑定到 CPU0,把实时控制中断绑定到 CPU1,从而减少跨核干扰。

/* 示例:将中断向量 0x20 绑定到 CPU1 */
cpuset_t intAff;
CPUSET_ZERO(intAff);
CPUSET_SET(intAff, 1);

intCpuAffinitySet(0x20, intAff);

中断亲和性与任务亲和性需要协同设计。若任务绑定在 CPU2,而其中断却落在 CPU0,则中断处理与任务执行分离,可能增加唤醒延迟;反之,若中断和任务同核,则中断会直接抢占任务,影响实时性。通常建议:高实时任务与其关键中断同核,非关键中断分散到其他核。

典型初始化代码分析

下面是一段典型的 SMP 初始化片段,展示了从 CPU 集合初始化到任务亲和性设置的完整顺序:

void usrInit(int startType)
{
    sysStartType = startType;
    vxCpuInit();                     /* 初始化 CPU 集合 */

    cpuset_t enabled;
    CPUSET_ZERO(enabled);
    CPUSET_SET(enabled, 0);
    CPUSET_SET(enabled, 1);          /* 使能 CPU0、CPU1 */
    vxCpuEnabledSet(enabled);

    kernelInit(...);                 /* 启动内核 */

    /* 创建实时任务并绑定到 CPU1 */
    cpuset_t aff;
    CPUSET_ZERO(aff);
    CPUSET_SET(aff, 1);
    taskCpuAffinitySet(taskId, aff);
}

这段代码的关键顺序是:先 vxCpuInit() 建立集合,再 vxCpuEnabledSet() 使能目标核,最后才创建任务并设置亲和性。若顺序颠倒,任务可能被调度到尚未初始化的核上,导致异常。实际项目中,还应结合 vxCpuAvailableGet() 检查硬件实际可用核数,避免对不存在的核设置亲和性。

调试建议:使用 vxCpuEnabledGet() 和 taskCpuAffinityGet() 在启动日志中打印集合状态,可快速确认从核是否上线、任务是否绑定成功。若从核未上线,优先检查 mailbox 寄存器写入值和从核时钟使能位。