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启动的“最后一公里”。常见方式有三种:
- 编译期宏定义:如前述
AMP_CORE_ID,直接编入镜像。优点是简单可靠,缺点是修改参数需重新编译。 - 寄存器传递:主核在释放从核前,将核编号、共享内存基址等写入约定寄存器(如ARM的
x0/x1或RISC-V的a0/a1),从核启动汇编阶段读取并保存到全局变量。这种方式灵活,适合U-Boot释放从核的场景。 - 共享内存参数区:主核将参数结构体写入共享内存固定偏移,从核启动后从该偏移读取。适合参数较多或需要动态调整的场景。
无论哪种方式,都建议在从核启动早期打印或记录实际读取到的参数值,便于排查“从核跑飞”类问题。一个实用的调试手段是:从核启动后先将核编号写入共享内存的调试区,主核轮询该区域即可确认从核是否成功启动。
上图将本节五个环节串联为一条完整的构建—加载—通信链路:左侧VIP工程为每个核生成独立配置,中间编译产出多核镜像,右侧根据平台选择加载方式,下方共享内存区域为核间通信提供物理载体,底部启动参数传递则确保从核能正确获知自身身份和通信地址。实际项目中,建议先以TFTP方式快速验证双核启动和共享内存读写,确认无误后再切换到BootROM或U-Boot方式固化到产品中。