2. ARMv9架构基础
做ARMv9的系统移植,第一件事不是写代码,而是先把环境搭好。我见过太多人上来就啃文档,结果编译链装错版本,QEMU跑不起来,折腾两天还在原地打转。说白了,环境就是你的作战阵地,阵地没修好,仗没法打。
这一章,我会带你一步步把三样东西搞定:交叉编译工具链、QEMU模拟器、FVP平台。这三者分别对应开发、调试、验证三个环节。你跟着我的习惯走,能少踩不少坑。
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修得快。
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
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系统。
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 |
/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支持状态
嗯,到这里环境就全部就绪了。接下来你就可以放心地做系统移植和性能分析了。记住,环境搭建是基础中的基础,花点时间把它搞扎实,后面会省很多事。