3. VxWorks AMP镜像构建与加载

AMP(Asymmetric Multiprocessing,非对称多处理)与SMP最大的区别在于:每个CPU核运行独立的操作系统镜像,拥有各自的地址空间、任务调度器和中断控制器视图。因此,AMP系统的镜像构建不是“一个镜像跑遍所有核”,而是“为每个核定制一份镜像,再通过统一的加载机制分发到各自核上”。本节围绕VIP工程配置、多核镜像生成、加载方式选择、共享内存定义和启动参数传递五个环节展开。

一、VIP工程配置:从单核到多核的工程组织

VxWorks Image Project(VIP)是Workbench中管理内核镜像的工程类型。在AMP场景下,推荐的做法是:一个VIP工程对应一个核的镜像,而不是在一个工程里通过宏切换来生成多个镜像。原因是每个核的镜像在组件配置、内存布局、启动参数上往往存在实质差异,混在一个工程里容易导致配置污染。

创建VIP时,关键配置项包括:

  • BSP选择:必须与目标板一致。AMP场景下,同一块板卡上所有核通常共用同一个BSP,但需要在BSP中区分核编号。
  • 内核组件:每个核都需要独立的INCLUDE_VX_KERNEL、任务调度、信号量等基础组件。若某个核只做裸机式轮询,可裁剪掉不必要的组件以减小镜像体积。
  • 内存配置:通过LOCAL_MEM_LOCAL_ADRS、LOCAL_MEM_SIZE等宏定义该核可用的本地内存范围。AMP下各核的本地内存必须互不重叠。
  • 启动参数:通过BOOT_LINE或自定义宏传递核编号、共享内存基址、同步标志地址等。

一个典型的AMP VIP配置片段如下(以双核为例,核0和核1各一份):

/* 核0的config.h片段 */
#define AMP_CORE_ID         0
#define SHM_BASE_ADRS       0x80000000
#define SHM_SIZE            0x00100000
#define CORE0_LOCAL_ADRS    0x00100000
#define CORE0_LOCAL_SIZE    0x03F00000

/* 核1的config.h片段 */
#define AMP_CORE_ID         1
#define SHM_BASE_ADRS       0x80000000
#define SHM_SIZE            0x00100000
#define CORE1_LOCAL_ADRS    0x04000000
#define CORE1_LOCAL_SIZE    0x03F00000

注意:SHM_BASE_ADRS和SHM_SIZE在两个核中必须完全一致,这是核间通信的物理基础;而CORE*_LOCAL_ADRS必须不同,避免地址空间冲突。

二、多核镜像生成

在Workbench中,每个VIP工程独立编译,生成各自的vxWorks镜像文件。若目标板支持多镜像打包,也可以将多个核的镜像合并为一个vxWorks_amp.bin,由BootROM统一加载后按核分发。合并时需要注意镜像头部信息中记录各核镜像的偏移和长度。

生成过程中常见的两个问题:一是链接脚本中MEMORY区域定义与VIP中的内存宏不一致,导致镜像加载后跑飞;二是各核镜像的入口地址sysInit或usrInit被错误地设置为同一地址。建议在编译后通过readelf -h或Workbench的镜像信息视图核对入口地址和段布局。

三、核间镜像加载方式

AMP系统的加载方式取决于硬件启动架构。常见三种:

  • BootROM加载:主核(通常是核0)从BootROM启动后,由BootROM代码将其他核的镜像从Flash或网络搬运到各自内存,再通过核间中断或写寄存器的方式释放从核。这种方式不依赖外部引导程序,适合裸板或定制板卡。
  • U-Boot加载:U-Boot作为一级引导,通过go命令或自定义命令将各核镜像加载到指定地址。对于支持多核启动的ARM/RISC-V平台,U-Boot可通过bootm或spin-table机制释放从核。这种方式灵活,便于调试和更新镜像。
  • TFTP加载:开发阶段常用。主核通过TFTP从主机下载各核镜像到内存,再释放从核。优点是无需烧写Flash,迭代快;缺点是依赖网络环境,量产不适用。

选择加载方式时,核心判断依据是:谁负责搬运镜像、谁负责释放从核。BootROM方式下搬运和释放都由主核固件完成;U-Boot方式下搬运由U-Boot完成、释放由主核VxWorks完成;TFTP方式下搬运由主核VxWorks的TFTP客户端完成。

四、共享内存区域定义

AMP系统中,共享内存是核间通信的唯一物理通道。定义共享内存区域时,需要明确以下参数:

参数含义示例值
SHM_BASE_ADRS共享内存物理基址0x80000000
SHM_SIZE共享内存总大小0x00100000(1MB)
SHM_CTRL_OFFSET控制区偏移(信号量、标志位)0x00000000
SHM_DATA_OFFSET数据区偏移(环形缓冲区)0x00010000
SHM_MAGIC初始化校验魔数0x414D5001

共享内存必须映射为非缓存(uncached)或写穿透(write-through)属性,否则各核的缓存不一致会导致数据错乱。在VxWorks中,通常通过sysMmuMapAdd或BSP中的MMU表项将该区域配置为VM_STATE_MASK_CACHEABLE关闭状态。若必须使用缓存,则需配合硬件缓存一致性协议或软件显式刷新。

初始化流程建议:主核在启动后首先检查SHM_MAGIC,若未初始化则清零控制区并写入魔数;从核启动后轮询该魔数,确认共享内存就绪后再进入通信循环。这样可避免从核在共享内存未初始化时误读脏数据。

五、启动参数传递

启动参数传递是AMP启动的“最后一公里”。常见方式有三种:

  1. 编译期宏定义:如前述AMP_CORE_ID,直接编入镜像。优点是简单可靠,缺点是修改参数需重新编译。
  2. 寄存器传递:主核在释放从核前,将核编号、共享内存基址等写入约定寄存器(如ARM的x0/x1或RISC-V的a0/a1),从核启动汇编阶段读取并保存到全局变量。这种方式灵活,适合U-Boot释放从核的场景。
  3. 共享内存参数区:主核将参数结构体写入共享内存固定偏移,从核启动后从该偏移读取。适合参数较多或需要动态调整的场景。

无论哪种方式,都建议在从核启动早期打印或记录实际读取到的参数值,便于排查“从核跑飞”类问题。一个实用的调试手段是:从核启动后先将核编号写入共享内存的调试区,主核轮询该区域即可确认从核是否成功启动。

VxWorks AMP 镜像构建与加载流程 VIP 工程配置 核0:CORE_ID=0 LOCAL_ADRS=0x00100000 SHM_BASE=0x80000000 核1:CORE_ID=1 LOCAL_ADRS=0x04000000 SHM_BASE=0x80000000 编译 多核镜像生成 vxWorks_core0.bin vxWorks_core1.bin 入口地址 / 段布局核对 readelf -h 验证 分发 核间镜像加载方式 BootROM:主核搬运 + 释放从核 U-Boot:go/bootm 释放从核 TFTP:开发阶段快速迭代 共享内存区域定义(SHM_BASE = 0x80000000,SHM_SIZE = 1MB) 控制区 信号量 / 标志位 / MAGIC 数据区 环形缓冲区 / 消息队列 调试区 核启动状态 / 心跳计数 参数区 启动参数结构体 启动参数传递方式 ① 编译期宏定义 ② 寄存器传递(x0/x1 或 a0/a1) ③ 共享内存参数区 映射 读取

上图将本节五个环节串联为一条完整的构建—加载—通信链路:左侧VIP工程为每个核生成独立配置,中间编译产出多核镜像,右侧根据平台选择加载方式,下方共享内存区域为核间通信提供物理载体,底部启动参数传递则确保从核能正确获知自身身份和通信地址。实际项目中,建议先以TFTP方式快速验证双核启动和共享内存读写,确认无误后再切换到BootROM或U-Boot方式固化到产品中。

调试建议:从核启动后立即向共享内存调试区写入核编号和心跳计数,主核通过轮询该区域即可判断从核是否成功启动、是否卡在某个初始化阶段。这比单纯依赖串口打印更可靠,因为从核可能尚未初始化串口。