在异构计算架构日益普及的今天,如何充分发挥多核处理器的性能优势成为嵌入式开发者的必修课。创龙TLZ7x系列开发板搭载的ZYNQ7020芯片,凭借其双核Cortex-A9架构和可编程逻辑资源,为实时性要求高的嵌入式场景提供了理想的硬件平台。本文将深入探讨在该平台上实现AMP(Asymmetric Multi-Processing)架构的完整技术路径,特别针对Linux与裸机双核协同开发中的关键难点提供可落地的解决方案。
AMP架构允许两个CPU核心独立运行不同的操作系统或裸机程序,这种异构特性使其特别适合同时需要高实时性和复杂功能的应用场景。在ZYNQ7020平台上实现这一架构,开发者需要跨越三个主要技术障碍:
ZYNQ的启动过程分为三个阶段,每个阶段都有特定的技术考量:
| 启动阶段 | 执行内容 | AMP架构特殊要求 |
|---|---|---|
| BootROM | 硬件初始化,加载FSBL | 需识别多镜像结构 |
| FSBL | 外设初始化,加载应用镜像 | 支持多镜像加载 |
| 应用阶段 | 执行主程序 | 双核协同启动 |
在传统单系统启动流程中,FSBL只需加载单个镜像文件(如u-boot或裸机程序)。而要实现AMP架构,必须对FSBL进行深度定制,使其能够:
c复制// 伪代码展示多镜像加载逻辑
void LoadAMPImages() {
ImageInfo uboot = LoadImage("u-boot.bin", DDR_ADDR_0);
ImageInfo baremetal = LoadImage("baremetal.bin", DDR_ADDR_1);
WriteCPU1Entry(baremetal.entry); // 设置CPU1启动地址
JumpTo(uboot.entry); // CPU0跳转到u-boot
}
合理的内存划分是双核稳定运行的基础。以下是一个典型的内存分配方案:
code复制0x00000000 - 0x17FFFFFF: Linux系统及应用(CPU0)
0x18000000 - 0x18FFFFFF: PL端MicroBlaze专用
0x19000000 - 0x19FFFFFF: 裸机程序(CPU1)
0x1A000000 - 0x1FFFFFFF: PL端专用区域
注意:实际分配应根据具体应用调整,务必确保两个核心的访问区域无重叠,特别是堆栈空间的设置需要额外留意。
默认情况下,Linux内核会尝试管理所有可用CPU核心,这与AMP架构的设计目标直接冲突。通过以下步骤可强制Linux仅使用CPU0:
关闭SMP支持:
bash复制make ARCH=arm menuconfig
# 取消选择 Kernel Features → Symmetric Multi-Processing
验证配置结果:
bash复制# 在Linux系统中查看CPU在线状态
cat /sys/devices/system/cpu/online
# 应显示"0"表示仅CPU0在线
在设备树中明确保留裸机核心使用的内存区域:
dts复制reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
baremetal_reserved: baremetal@19000000 {
reg = <0x19000000 0x01000000>;
no-map;
};
};
标准FSBL无法满足AMP需求,需要进行以下关键修改:
修改LoadBootImage函数逻辑,使其支持连续加载多个镜像:
c复制int LoadMultiImages() {
// 加载u-boot镜像
PartitionHeader *uboot = GetPartitionHeader(0);
LoadImage(uboot);
// 加载裸机镜像
PartitionHeader *baremetal = GetPartitionHeader(1);
LoadImage(baremetal);
// 设置CPU1启动地址
Xil_Out32(0xFFFFFFF0, baremetal->ExecutionAddress);
return SUCCESS;
}
由于CPU1需要通过OCM启动,必须关闭相关缓存以避免一致性问题:
c复制void DisableOCMCache() {
Xil_SetTlbAttributes(0xFFFF0000, NORM_NONCACHE);
Xil_SetTlbAttributes(0xFFFFFFF0, NORM_NONCACHE);
}
建立双核间的数据交换通道:
定义共享内存结构:
c复制typedef struct {
volatile uint32_t flag;
uint8_t data[256];
} SharedMemory;
初始化共享区域:
c复制// CPU0 (Linux)端
void* shm = ioremap(SHARED_MEM_BASE, sizeof(SharedMemory));
// CPU1 (裸机)端
SharedMemory* shm = (SharedMemory*)SHARED_MEM_BASE;
对于UART等共享外设,建议采用以下策略之一:
| 故障现象 | 可能原因 | 解决方案 |
|---|---|---|
| CPU1未启动 | 启动地址写入失败 | 检查OCM缓存设置 |
| 系统随机崩溃 | 内存区域重叠 | 重新规划内存映射 |
| UART输出乱码 | 双核同时访问冲突 | 实现互斥锁机制 |
在FSBL中添加详细调试输出:
c复制#ifdef FSBL_DEBUG
xil_printf("Loading u-boot to 0x%08x\r\n", ubootAddr);
xil_printf("CPU1 entry set to 0x%08x\r\n", baremetalAddr);
#endif
缓存优化策略:
Xil_DCacheFlushRange实时性保障措施:
c复制// 在裸机核心设置最高优先级
XScuGic_SetPriorityTrigger(IntcInstance, TimerIntId, 0, 0x3);
启动时间优化:
在实际工业控制项目中,我们采用这种架构实现了运动控制器开发——Linux核心处理HMI和网络通信,裸机核心精确控制脉冲输出。测试表明,裸机核心的定时器中断响应抖动小于1μs,完全满足高精度控制需求。