2. 嵌入式系统基础

交叉编译,说白了就是“在A机器上编译出能在B机器上跑的程序”。做嵌入式开发,这几乎是每天都要面对的事。我刚开始接触时也觉得麻烦,后来发现只要把工具链、sysroot和构建脚本这三件事理顺了,后面就一马平川。

2.1 工具链选择:GCC还是LLVM?

选工具链,是搭建环境的第一步。目前主流就两个阵营:GCC和LLVM/Clang。

对比维度 GCC LLVM/Clang
架构支持 极广,ARM/MIPS/RISC-V/AVR等全覆盖 主流架构支持好,小众架构可能缺后端
编译速度 中等 通常更快,增量编译优势明显
错误信息 有时晦涩 清晰友好,带颜色高亮
代码生成 成熟稳定,优化选项丰富 性能接近,LTO链接时优化更强
许可证 GPL Apache 2.0

我个人习惯:商业项目用GCC,因为它的工具链生态最全,第三方BSP(板级支持包)几乎都默认配GCC。LLVM我更多用在需要快速迭代、频繁调试的场景,它的错误提示确实省时间。

我的经验: 如果你用的是ARM Cortex-M系列MCU,ARM官方提供的GCC工具链(arm-none-eabi-gcc)是最稳妥的选择。RISC-V的话,我建议用SiFive或芯来科技维护的GCC分支,社区版有时会有小坑。

2.2 sysroot配置:别让链接器“迷路”

sysroot是什么?简单说,它就是目标系统在宿主机上的一个“影子”。里面放着目标板的头文件、库文件、运行时对象。没有它,链接器就找不到printf、malloc这些函数的实现。

配置sysroot,核心就三步:

  1. 获取sysroot:可以从芯片厂商的SDK里拿,也可以用buildroot或Yocto自己生成。
  2. 设置路径:通过 --sysroot=/path/to/sysroot 告诉编译器去哪里找。
  3. 验证完整性:检查 usr/include 和 usr/lib 是否存在关键文件。
我曾经踩过的坑: 有一次项目编译通过,但烧到板子上就段错误。查了两天,发现sysroot里的libc.so版本和内核头文件不匹配。后来我养成了习惯——每次拿到新sysroot,先用 readelf -V 检查库版本,再跟内核头文件对一遍。

sysroot的典型目录结构长这样:

sysroot/
├── usr/
│   ├── include/       # 头文件
│   │   ├── stdio.h
│   │   ├── stdlib.h
│   │   └── ...
│   ├── lib/           # 动态库(.so)和静态库(.a)
│   │   ├── libc.so
│   │   ├── libm.a
│   │   └── ...
│   └── bin/           # 目标板上的工具(可选)
└── lib/
    └── ld-linux.so.3  # 动态链接器

2.3 CMake交叉编译文件编写

CMake是嵌入式项目构建的事实标准。写交叉编译文件,就是告诉CMake:“别用你宿主机那套,用我指定的工具链和sysroot。”

下面是我项目里常用的一个模板,以ARM Cortex-A72为例:

# toolchain-arm64.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

# 指定工具链路径
set(TOOLCHAIN_PREFIX /opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}/bin/aarch64-none-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}/bin/aarch64-none-linux-gnu-g++)

# sysroot配置
set(CMAKE_SYSROOT ${TOOLCHAIN_PREFIX}/aarch64-none-linux-gnu/libc)

# 查找程序时只在sysroot内找
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

使用时,在项目根目录执行:

mkdir build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm64.cmake
make -j$(nproc)
关键点: CMAKE_FIND_ROOT_PATH_MODE_* 这四个变量一定要设对。设成 ONLY 能防止CMake“偷懒”去宿主机路径找库,避免链接到错误的版本。

2.4 知识体系总览

下面这张图,把交叉编译环境搭建的核心逻辑串起来了:

交叉编译环境搭建知识体系 交叉编译环境搭建 工具链选择 sysroot配置 CMake交叉编译文件 GCC LLVM/Clang 头文件 库文件 工具链文件 查找策略 核心原则 工具链决定“怎么编译”,sysroot决定“编译成什么”,CMake决定“编译流程怎么管”

2.5 避坑指南与实用技巧

  • 工具链版本要跟内核匹配:比如Linux 4.19内核,用GCC 9.x以上编译可能会有警告,但基本能过。Linux 5.10以上建议用GCC 10.x。
  • sysroot里的libc.so别乱换:我见过有人图省事,把宿主机上的libc.so直接拷进sysroot,结果链接出来的程序在目标板上跑不起来——因为glibc版本对内核有最低要求。
  • CMake缓存清理:换工具链或sysroot后,一定要删掉build目录重新cmake。CMake会缓存第一次检测到的路径,不清缓存的话,改配置等于白改。
  • 用file命令验证:编译完的可执行文件,用 file 命令看一眼,确认是目标架构的ELF格式。这一步花不了10秒,但能省下半小时的排错时间。
一个小技巧: 在CMakeLists.txt里加一行 message(STATUS "Using sysroot: ${CMAKE_SYSROOT}"),cmake配置时就能看到实际生效的sysroot路径,方便核对。

嗯,交叉编译环境搭建,说白了就是“选对工具、配好路径、写对脚本”。这三件事做好了,后面编译、链接、部署就顺了。我见过太多项目在环境上反复折腾,其实只要按上面的步骤来,一次就能跑通。