第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.mkAndroidProducts.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。流程是这样的:

  1. 准备产品配置:为每个架构创建独立的 device/ 目录,比如 device/mycompany/arm64/device/mycompany/x86_64/device/mycompany/riscv64/
  2. 配置BoardConfig.mk:每个目录下放对应的架构参数。
  3. 配置AndroidProducts.mk:定义产品名称和构建变体。
  4. 执行构建:用 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 多平台构建的核心逻辑图

下面这张图,是我根据多年经验总结的多平台构建流程。你可以看到,从源码到最终镜像,中间经过了工具链选择、架构配置、编译链接等多个环节。

AOSP多平台构建核心逻辑 AOSP 源码 产品配置层 BoardConfig.mk / AndroidProducts.mk 架构选择与工具链配置 TARGET_ARCH / TARGET_2ND_ARCH / 交叉工具链路径 ARM / ARM64 手机 / IoT x86 / x86_64 模拟器 / Chromebook RISC-V 64 开发板 / 实验设备 多架构系统镜像输出

从这张图你可以看到,整个流程是分层的。每一层都有对应的配置文件和构建逻辑。我个人觉得,最关键的其实是第二层——产品配置层。因为这里决定了你的设备用哪个架构、哪个工具链、哪个内核版本。

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的夜晚。


公众号:蓝海资料掘金营,微信 deep3321