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 和栈,然后调用内核提供的从核初始化钩子,最后进入空闲任务等待调度。若从核长时间未上线,常见原因是入口地址未正确写入、缓存一致性未处理、或从核时钟/电源未使能。
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 寄存器写入值和从核时钟使能位。