第25章 多平台构建:交叉编译、不同架构(ARM、x86、RISC-V)支持、多目标构建
说到多平台构建,我脑子里第一个蹦出来的场景就是:几年前我们团队要给一款智能音箱做系统移植。硬件平台从ARM换成了RISC-V,结果构建系统直接崩了。嗯,那时候我才真正意识到——交叉编译和多目标支持,不是简单的改个参数就能搞定的事。
这一章,我们就来聊聊AOSP里怎么搞定多平台构建。说白了,就是让同一套代码,能在ARM、x86、RISC-V这些不同架构上跑起来。
25.1 交叉编译:为什么需要它?
你想想看,你的开发机大概率是x86架构的。但目标设备可能是ARM手机、RISC-V开发板,甚至是MIPS路由器。你不可能在目标设备上直接编译——性能太差,工具链也不全。
所以就有了交叉编译:在宿主机(x86)上编译出目标架构(ARM/RISC-V)的可执行文件。
核心概念:交叉编译 = 宿主机 + 目标机 + 交叉工具链
我个人习惯把交叉工具链比作“翻译官”。它知道怎么把C/C++代码翻译成目标CPU能理解的机器码。AOSP里,这个翻译官就是 prebuilts/gcc/ 或 prebuilts/clang/ 目录下的那些二进制文件。
25.2 AOSP中的架构支持
AOSP目前主要支持三种架构:ARM、x86、RISC-V。每种架构又分32位和64位。我整理了一张表,方便你对照:
| 架构 | 32位 | 64位 | 常见设备 |
|---|---|---|---|
| ARM | arm | arm64 | 手机、平板、IoT |
| x86 | x86 | x86_64 | 模拟器、Chromebook |
| RISC-V | riscv32 | riscv64 | 开发板、实验设备 |
你可能会问:为什么AOSP要支持这么多架构?其实原因很简单——Android设备已经不只是手机了。手表、电视、汽车、甚至服务器,都在用Android。不同场景需要不同的CPU架构。
25.3 多目标构建:一次配置,多架构输出
多目标构建,说白了就是让构建系统知道:你要为哪个架构编译,用哪个工具链,链接哪个库。
在AOSP里,这个逻辑是通过 BoardConfig.mk 和 AndroidProducts.mk 来控制的。我举个例子:
# device/mycompany/mydevice/BoardConfig.mk
TARGET_ARCH := arm64
TARGET_ARCH_VARIANT := armv8-a
TARGET_CPU_VARIANT := cortex-a76
TARGET_2ND_ARCH := arm
TARGET_2ND_ARCH_VARIANT := armv7-a-neon
TARGET_2ND_CPU_VARIANT := cortex-a55
这里 TARGET_ARCH 指定了主架构是arm64,TARGET_2ND_ARCH 指定了次架构是arm(32位)。这样编译出来的系统镜像,就能同时运行64位和32位的应用。
我的经验:如果你在做产品移植,建议先确认目标CPU的型号。比如高通骁龙8 Gen 2用的是Cortex-X3和Cortex-A715,那CPU_VARIANT就要填对。填错了虽然也能编译通过,但性能会打折扣。
25.4 交叉编译工具链的配置
AOSP从Android 7.0开始,默认使用Clang作为编译器。GCC虽然还在,但已经逐渐被淘汰了。我个人建议你直接用Clang,省心。
工具链的路径一般在:
prebuilts/clang/host/linux-x86/clang-r487747/bin/
prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/
你可以在 build/soong/cc/config/ 目录下找到具体的配置。比如 arm_device.go 里定义了ARM架构的编译参数:
// build/soong/cc/config/arm_device.go
var (
arm64Cflags = []string{
"-march=armv8-a",
"-mtune=cortex-a76",
}
)
我曾经踩过一个坑:在移植RISC-V时,忘了修改 toolchain.go 里的链接器路径。结果编译出来的二进制文件,在目标板上直接段错误。排查了整整两天才发现是工具链版本不匹配。
避坑指南:交叉编译时,一定要确保宿主机上的工具链版本和目标设备的内核版本兼容。比如Linux 5.10内核的设备,就别用太老的工具链(比如4.9系列的GCC)。
25.5 多架构构建的实战流程
假设你要为三个架构构建系统镜像:ARM64、x86_64、RISC-V64。流程是这样的:
- 准备产品配置:为每个架构创建独立的
device/目录,比如device/mycompany/arm64/、device/mycompany/x86_64/、device/mycompany/riscv64/。 - 配置BoardConfig.mk:每个目录下放对应的架构参数。
- 配置AndroidProducts.mk:定义产品名称和构建变体。
- 执行构建:用
lunch选择目标,然后make。
具体命令如下:
# 构建ARM64版本
source build/envsetup.sh
lunch myproduct_arm64-userdebug
make -j16
# 构建x86_64版本
lunch myproduct_x86_64-userdebug
make -j16
# 构建RISC-V64版本
lunch myproduct_riscv64-userdebug
make -j16
你可能会觉得这样重复构建很慢。嗯,确实。所以AOSP提供了 dist 目标,可以一次构建多个架构:
make -j16 dist TARGET_PRODUCT=myproduct_arm64 TARGET_PRODUCT=myproduct_x86_64
不过说实话,这个功能我用的不多。因为不同架构的构建依赖可能冲突,我更喜欢分开构建,稳一点。
25.6 多平台构建的核心逻辑图
下面这张图,是我根据多年经验总结的多平台构建流程。你可以看到,从源码到最终镜像,中间经过了工具链选择、架构配置、编译链接等多个环节。
从这张图你可以看到,整个流程是分层的。每一层都有对应的配置文件和构建逻辑。我个人觉得,最关键的其实是第二层——产品配置层。因为这里决定了你的设备用哪个架构、哪个工具链、哪个内核版本。
25.7 常见问题与避坑
我在多平台构建上栽过不少跟头,挑几个典型的说说:
- 工具链版本不匹配:我曾经用Clang r450000编译了一个ARM64的二进制,结果在Linux 4.9内核的设备上跑不起来。后来发现是Clang版本太新,生成的指令集内核不支持。解决办法:用
prebuilts/clang/里和内核版本匹配的Clang。 - 32位兼容库缺失:如果你构建的是64位系统,但需要运行32位应用,记得在
BoardConfig.mk里配置TARGET_2ND_ARCH。否则编译出来的系统没有32位库,老应用直接闪退。 - RISC-V的坑:RISC-V还在快速演进中,指令集扩展(比如向量扩展V扩展)不同厂商支持程度不一样。我建议你在
BoardConfig.mk里明确指定TARGET_RISCV64_ARCH_VARIANT,不要用默认值。
我的习惯:每次开始一个新平台移植,我都会先跑一遍 make checkbuild,确保基础工具链没问题。然后再逐步添加模块。这样出了问题,能快速定位是哪个环节的锅。
25.8 小结
多平台构建,说白了就是让AOSP能灵活适配不同的硬件。交叉编译是基础,架构配置是关键,工具链选择是细节。这三者缺一不可。
我记得有一次,一个同事问我:“为什么我的代码在模拟器上跑得好好的,到了真机上就崩?”我一看,他忘了配置 TARGET_CPU_VARIANT。模拟器用的是通用的x86_64,真机是ARM64的Cortex-X3,指令集优化不一样,能不崩吗?
所以,做多平台构建时,别嫌麻烦。每个架构的配置参数,都值得你花时间去确认。毕竟,一次正确的配置,能省下后面无数个debug的夜晚。