2. 系统移植环境搭建:交叉编译工具链配置、QEMU模拟器搭建、FVP平台使用入门

做ARMv9的系统移植,第一件事不是写代码,而是先把环境搭好。我见过太多人上来就啃文档,结果编译链装错版本,QEMU跑不起来,折腾两天还在原地打转。说白了,环境就是你的作战阵地,阵地没修好,仗没法打。

这一章,我会带你一步步把三样东西搞定:交叉编译工具链、QEMU模拟器、FVP平台。这三者分别对应开发、调试、验证三个环节。你跟着我的习惯走,能少踩不少坑。

本章核心目标: 搭建一个可复用的ARMv9开发环境,确保从编译到模拟运行的全链路打通。
ARMv9 系统移植环境架构 x86_64 主机 交叉编译工具链 QEMU / FVP ARMv9 目标系统 编译生成ARMv9二进制 模拟执行ARMv9指令

2.1 交叉编译工具链配置

交叉编译,说白了就是在x86的电脑上编译出ARM能跑的程序。你想想看,ARMv9的开发板通常资源有限,不可能在上面跑完整的GCC。所以我们需要一套交叉编译工具链。

2.1.1 工具链选择

目前主流的ARM交叉编译链有这几家:

工具链 提供商 适用场景 我的评价
aarch64-none-elf- ARM官方 裸机、UEFI、早期启动 干净,适合底层开发
aarch64-linux-gnu- Linaro / 发行版 Linux内核、用户态程序 最常用,生态好
armclang ARM (Arm Compiler) 商业项目、性能优化 贵,但优化确实猛

我个人习惯用aarch64-linux-gnu-这套。为什么?因为我们的目标是跑Linux系统,这套链子跟内核、根文件系统配合得最好。而且Linaro的版本更新及时,bug修得快。

小提示: 如果你做的是安全启动或TrustZone相关开发,建议额外准备一套aarch64-none-elf-,因为有些固件代码不依赖Linux的C库。

2.1.2 安装步骤

以Ubuntu 22.04为例,安装其实就一行命令:

sudo apt-get install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu

但这里有个坑——版本问题。Ubuntu仓库里的版本往往比较老。我记得有一次项目赶进度,用apt装的gcc 9.3,结果编译ARMv9的SVE指令时,编译器直接报不认识。后来换成Linaro的gcc 11.2才搞定。

所以我建议你从Linaro官网下载预编译包:

# 下载Linaro gcc 11.2
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-11.2.1-2021.12-x86_64_aarch64-linux-gnu.tar.xz

# 解压到 /opt
sudo tar -xvf gcc-linaro-*.tar.xz -C /opt/

# 配置环境变量
export PATH=/opt/gcc-linaro-11.2.1-2021.12-x86_64_aarch64-linux-gnu/bin:$PATH
注意: 环境变量要写到 ~/.bashrc 或 ~/.zshrc 里,否则每次开新终端都要重新export。我曾经因为这个浪费了半小时排查"怎么编译链找不到了"。

2.1.3 验证工具链

装完之后,跑一下这个命令确认:

aarch64-linux-gnu-gcc --version
aarch64-linux-gnu-gcc -v 2>&1 | grep Target

如果输出显示 Target: aarch64-linux-gnu,那就对了。再写个简单的Hello World测试:

# test.c
#include <stdio.h>
int main() {
    printf("ARMv9 Hello!\n");
    return 0;
}

# 编译
aarch64-linux-gnu-gcc -o test.elf test.c

# 查看文件类型
file test.elf
# 输出: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV)

看到ARM aarch64这几个字,说明工具链已经能正常生成ARMv8/v9的二进制了。ARMv9在指令集上兼容ARMv8,所以这个工具链完全够用。

2.2 QEMU模拟器搭建

QEMU这东西,说白了就是个硬件模拟器。你可以在你的x86电脑上模拟出一块ARMv9的开发板,跑内核、跑应用。我刚开始做ARM开发时,就是靠QEMU练手的——毕竟买一块开发板要几千块,而QEMU免费。

2.2.1 安装QEMU

同样,Ubuntu下安装很简单:

sudo apt-get install qemu-system-arm qemu-efi-aarch64

但这里我要提醒你:QEMU的版本很重要。ARMv9的一些新特性(比如FEAT_SVE2、FEAT_RME)需要QEMU 7.0以上才支持。Ubuntu 22.04自带的QEMU是6.2,差一点。我建议你从源码编译最新版:

# 下载QEMU 8.0
wget https://download.qemu.org/qemu-8.0.0.tar.xz
tar -xvf qemu-8.0.0.tar.xz
cd qemu-8.0.0

# 配置,只编译ARM相关
./configure --target-list=aarch64-softmmu --enable-kvm --enable-virtfs

# 编译(-j后面跟你的CPU核心数)
make -j$(nproc)
sudo make install

编译大概需要10-15分钟,喝杯咖啡的功夫。装完之后验证:

qemu-system-aarch64 --version

2.2.2 启动一个最小系统

有了QEMU,我们来跑一个ARMv9的Linux内核。你需要准备三样东西:

  • 内核镜像:Image或zImage
  • 设备树:.dtb文件
  • 根文件系统:rootfs.ext4或initramfs

这里我用一个现成的测试镜像做演示:

# 下载ARM64测试内核和文件系统
wget https://example.com/armv9-test/Image
wget https://example.com/armv9-test/virt.dtb
wget https://example.com/armv9-test/rootfs.ext4

# 启动QEMU
qemu-system-aarch64 \
  -machine virt,virtualization=on,gic-version=3 \
  -cpu max,x-rme=on \
  -smp 4 \
  -m 2G \
  -kernel Image \
  -dtb virt.dtb \
  -drive file=rootfs.ext4,format=raw,id=hd0 \
  -device virtio-blk-device,drive=hd0 \
  -append "root=/dev/vda console=ttyAMA0" \
  -nographic
关键参数说明:
-cpu max,x-rme=on 启用ARMv9的RME扩展,这是v9的核心特性之一。
-machine virt,virtualization=on 开启虚拟化支持。
-nographic 不使用图形界面,纯串口输出。

启动后你会看到内核启动日志,最后出现登录提示符。输入root登录,你就拥有了一个运行在ARMv9模拟器上的Linux系统。

避坑指南: 如果启动卡在"Waiting for root device",多半是根文件系统设备名不对。检查一下内核配置里有没有打开VIRTIO_BLK,或者改用initramfs方式加载。

2.3 FVP平台使用入门

QEMU虽然好用,但它毕竟是模拟器,有些硬件细节模拟得不够精确。这时候就需要FVP(Fixed Virtual Platform)出场了。FVP是ARM官方提供的虚拟平台,它模拟的是ARM芯片的行为级模型,比QEMU更接近真实硬件。

我个人的经验是:QEMU用来做功能验证,FVP用来做性能分析和硬件特性调试。比如你要调试SVE指令的执行周期,QEMU给不了你精确数据,但FVP可以。

2.3.1 获取FVP

FVP需要从ARM官网下载,地址是:https://developer.arm.com/tools-and-software/simulation-models/fixed-virtual-platforms。你需要注册一个ARM账号,然后下载Armv9 Base RevC FVP模型包。

下载后是一个.bin文件,执行安装:

chmod +x FVP_Base_RevC-2xAEMvA_11.22_linux64.bin
./FVP_Base_RevC-2xAEMvA_11.22_linux64.bin --i-agree-to-the-contained-eula

安装过程中会问你安装路径,我习惯放在 /opt/arm/fvp 下。

2.3.2 运行FVP

FVP的启动命令比QEMU复杂一些,但结构很清晰:

# 设置FVP路径
export FVP_PATH=/opt/arm/fvp/Base_RevC_AEMvA_pkg/models/Linux64_GCC-6.4

# 启动FVP
${FVP_PATH}/FVP_Base_RevC-2xAEMvA \
  -C pctl.startup=0.0.0.0 \
  -C cluster0.NUM_CORES=4 \
  -C cluster1.NUM_CORES=4 \
  -C cache_state_modelled=0 \
  -C bp.secure_memory=0 \
  -C bp.virtio_block_device.image=./rootfs.ext4 \
  --data ./Image@0x80080000 \
  --data ./virt.dtb@0x83000000

这里要注意,FVP的参数都是用 -C 来配置的,每个参数对应模型里的一个寄存器或配置项。刚开始用的时候会觉得参数太多,但其实常用的就那几个。

参数 说明 常用值
pctl.startup 启动核心 0.0.0.0(第一个集群第一个核心)
cluster0.NUM_CORES 集群0的核心数 1-4
cache_state_modelled 是否模拟缓存状态 0(快速启动)或1(精确模拟)
bp.secure_memory 安全内存支持 0或1
重要提醒: FVP启动时如果报错"Unable to find license",说明你没有配置license。FVP需要license文件,安装时一般会自动生成。如果找不到,检查 /opt/arm/fvp/license 目录下是否有 .lic 文件。

2.3.3 QEMU vs FVP:怎么选?

很多初学者会问:到底用哪个?我画个对比表你就明白了:

对比维度 QEMU FVP
启动速度 快(秒级) 慢(分钟级)
模拟精度 功能级 行为级(接近硬件)
ARMv9特性支持 大部分支持 全部支持(官方模型)
性能分析 不精确 可统计指令周期
价格 免费 免费(但需注册)
适用阶段 日常开发、调试 特性验证、性能调优

我的建议是:日常开发用QEMU,做ARMv9新特性验证和性能分析时切到FVP。两个都装上,不冲突。

2.4 环境验证:跑一个ARMv9测试程序

环境都搭好了,我们来写一个简单的ARMv9汇编程序,验证一下整个链路是否通畅。这个程序会读取系统寄存器ID_AA64ISAR2_EL1,检查芯片是否支持SVE2(ARMv9的关键特性之一)。

// test_sve2.c
#include <stdio.h>
#include <stdint.h>

static inline uint64_t read_id_aa64isar2(void) {
    uint64_t val;
    asm volatile("mrs %0, id_aa64isar2_el1" : "=r" (val));
    return val;
}

int main() {
    uint64_t isar2 = read_id_aa64isar2();
    // SVE2 字段在 bits[23:20]
    int sve2 = (isar2 >> 20) & 0xf;
    
    printf("ID_AA64ISAR2_EL1 = 0x%lx\n", isar2);
    printf("SVE2 field = %d\n", sve2);
    
    if (sve2 >= 1) {
        printf("This CPU supports SVE2! (ARMv9 feature)\n");
    } else {
        printf("SVE2 not supported.\n");
    }
    return 0;
}

编译并在QEMU上运行:

# 交叉编译
aarch64-linux-gnu-gcc -o test_sve2.elf test_sve2.c

# 复制到根文件系统(假设挂载到 /mnt/rootfs)
sudo cp test_sve2.elf /mnt/rootfs/root/

# 启动QEMU,登录后运行
./test_sve2.elf

如果输出显示 SVE2 field = 1,说明你的环境已经正确模拟了ARMv9的SVE2特性。整个工具链、模拟器、目标系统的链路就全部打通了。

环境搭建完成标志:
✅ 交叉编译链能生成ARMv9二进制
✅ QEMU能启动ARMv9 Linux系统
✅ FVP能运行并识别ARMv9特性
✅ 测试程序能读取并显示SVE2支持状态

嗯,到这里环境就全部就绪了。接下来你就可以放心地做系统移植和性能分析了。记住,环境搭建是基础中的基础,花点时间把它搞扎实,后面会省很多事。