3. 搭建交叉编译环境:工具链选择与安装
交叉编译环境,说白了就是让一台 x86 电脑给 ARM 板子编译程序。你想想看,嵌入式设备资源有限,跑个编译器都费劲,更别说编译整个 Linux 内核了。所以我们在 PC 上装一套交叉工具链,生成目标板能跑的二进制文件。
我刚开始做 BSP 移植时,觉得这步很简单——不就是装个 gcc 吗?结果第一次编译内核就报了一堆莫名其妙的链接错误。后来才发现,工具链版本不对,跟内核要求的 glibc 版本不匹配。嗯,这里面的坑不少。
3.1 工具链的选择策略
工具链不是越新越好,也不是越全越好。我个人的经验是:匹配优先。内核版本、glibc 版本、编译器版本,这三者必须对齐。
| 工具链类型 | 适用场景 | 我的建议 |
|---|---|---|
| Linaro GCC | ARM Cortex-A 系列,主流 BSP | 首选,社区活跃,更新及时 |
| Buildroot 生成 | 定制化需求,需要控制 glibc 版本 | 适合深度定制,但编译耗时 |
| 芯片厂商 SDK | 特定 SoC,如 NXP、TI、Rockchip | 最省事,但版本可能老旧 |
| Yocto/OpenEmbedded | 企业级项目,需要完整 Linux 发行版 | 功能强大,学习曲线陡峭 |
3.2 安装交叉工具链
以 ARM 64 位平台为例,我通常用 Linaro 的 aarch64-linux-gnu 工具链。安装步骤其实就三步:下载、解压、配置路径。
# 下载 Linaro 工具链(以 gcc-13.2 为例)
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-13.2.1-2023.11-x86_64_aarch64-linux-gnu.tar.xz
# 解压到 /opt 目录
sudo tar -xvf gcc-linaro-13.2.1-2023.11-x86_64_aarch64-linux-gnu.tar.xz -C /opt/
# 重命名方便管理
sudo mv /opt/gcc-linaro-13.2.1-2023.11-x86_64_aarch64-linux-gnu /opt/gcc-aarch64
3.3 配置环境变量
环境变量配置是新手最容易出错的地方。我个人习惯把工具链路径加到 ~/.bashrc 或 ~/.zshrc 里,而不是每次手动 export。
# 编辑 ~/.bashrc,在末尾添加
export PATH=/opt/gcc-aarch64/bin:$PATH
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
# 使配置生效
source ~/.bashrc
这里解释一下三个变量的作用:
- PATH:让系统能找到交叉编译器
- CROSS_COMPILE:内核编译时自动加上这个前缀,比如调用 aarch64-linux-gnu-gcc
- ARCH:指定目标架构,内核会根据这个选择对应的配置和头文件
避坑指南: 我曾经遇到过一个问题——明明配置了 PATH,但编译内核时还是调用了本机的 gcc。后来发现是因为 CROSS_COMPILE 变量没导出。记住,export 关键字不能省!另外,如果你同时装了多个工具链,PATH 的顺序决定了用哪个版本。
3.4 验证工具链
工具链装好没?跑几个命令验证一下。我一般做三步检查:
- 检查编译器版本:确认是交叉编译器,不是本机 gcc
- 编译测试程序:生成一个简单的 hello world,验证链接正常
- 检查文件类型:用 file 命令确认生成的是 ARM 架构的 ELF
# 第一步:检查版本
aarch64-linux-gnu-gcc --version
# 输出应该包含 "aarch64-linux-gnu-gcc (Linaro GCC 13.2.1)"
# 第二步:编译测试程序
echo '#include <stdio.h>
int main() {
printf("Hello, BSP World!\n");
return 0;
}' > test.c
aarch64-linux-gnu-gcc -o test test.c
# 第三步:检查文件类型
file test
# 输出应该包含 "ELF 64-bit LSB executable, ARM aarch64"
如果 file 命令显示的是 x86-64,说明你用的是本机编译器。嗯,这个问题我见过不下十次了。
3.5 工具链验证的完整流程
下面这张图展示了从下载到验证的完整流程,我建议你把它打印出来贴在工位上。
3.6 多版本工具链管理
实际项目中,你可能需要同时维护多个内核版本。比如我手头有个项目,内核 4.19 用 gcc-7,内核 5.10 用 gcc-9,内核 6.1 用 gcc-13。怎么管理?
我的做法是用 环境变量切换脚本,而不是改全局 PATH。
# 创建切换脚本 set-env.sh
#!/bin/bash
case "$1" in
"4.19")
export PATH=/opt/gcc-aarch64-7/bin:$PATH
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
echo "Switched to gcc-7 for kernel 4.19"
;;
"5.10")
export PATH=/opt/gcc-aarch64-9/bin:$PATH
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
echo "Switched to gcc-9 for kernel 5.10"
;;
"6.1")
export PATH=/opt/gcc-aarch64-13/bin:$PATH
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
echo "Switched to gcc-13 for kernel 6.1"
;;
*)
echo "Usage: source set-env.sh [4.19|5.10|6.1]"
;;
esac
# 使用方式
source set-env.sh 6.1
source 而不是直接执行脚本。因为 source 会在当前 shell 生效,而直接执行会开子 shell,环境变量传不回来。这个坑我踩过好几次。
3.7 验证工具链的完整测试
除了简单的 hello world,我建议你做一个更全面的测试,验证工具链的各个组件是否正常。
# 测试汇编器
aarch64-linux-gnu-as --version
# 测试链接器
aarch64-linux-gnu-ld --version
# 测试库管理器
aarch64-linux-gnu-ar --version
# 测试反汇编器
aarch64-linux-gnu-objdump --version
# 测试二进制工具
aarch64-linux-gnu-objcopy --version
# 编译一个带数学库的程序
echo '#include <stdio.h>
#include <math.h>
int main() {
double x = sin(3.14159/4);
printf("sin(45°) = %f\n", x);
return 0;
}' > math_test.c
aarch64-linux-gnu-gcc -o math_test math_test.c -lm
# 检查动态链接库依赖
aarch64-linux-gnu-readelf -d math_test | grep NEEDED
如果所有命令都正常输出,说明你的交叉编译环境已经搭建好了。接下来就可以开始真正的 BSP 移植工作了。