第二章 交叉编译环境搭建:宿主机与目标机、交叉编译工具链的安装与配置、Sysroot的构建

好,咱们正式开始动手了。第一章聊了ADAS域控的整体架构,你可能觉得有点虚。这一章,咱们来点实在的——把交叉编译环境搭起来。

说实话,我见过太多新手在这个环节栽跟头。工具链版本不对,Sysroot没配好,编译出来的程序在目标板上跑不起来。嗯,我自己也踩过这些坑。今天我把经验梳理出来,你照着做,能省不少时间。

2.1 宿主机与目标机:一对“好搭档”

先搞清楚一个概念。什么是宿主机?什么是目标机?

宿主机就是你面前的开发电脑。一般是x86架构的,装个Ubuntu 20.04或22.04。我习惯用Ubuntu 20.04,LTS版本,稳定,社区支持好。

目标机就是ADAS域控板子。通常是ARM架构的,比如NVIDIA Orin、TI TDA4、高通SA8295这些。你想想看,板子上跑的是嵌入式Linux,资源有限,不可能在上面直接编译大型软件。

所以流程就是:在宿主机上写好代码,用交叉编译工具链编译成目标机可执行的二进制文件,然后传过去运行。

核心原则:宿主机负责“生产”,目标机负责“消费”。两者架构不同,必须通过交叉编译来桥接。

2.2 交叉编译工具链的安装与配置

工具链,说白了就是一套“翻译官”。它能把你的C/C++代码翻译成ARM架构的机器码。

2.2.1 选择工具链

市面上常见的工具链有几种:

  • Linaro GCC:开源,通用性强。适合快速原型验证。
  • ARM GCC:ARM官方出品,优化好。
  • 芯片厂商提供的SDK:比如NVIDIA的JetPack、TI的Processor SDK。这些是“官方认证”的,兼容性最好。

我个人建议:能用厂商SDK就用厂商SDK。为什么?因为里面不光有工具链,还有配套的驱动、库文件、示例代码。我在项目中用过一次Linaro的通用工具链,结果发现某个硬件加速库的ABI不兼容,折腾了两天才搞定。后来换成厂商SDK,半小时就解决了。

2.2.2 安装步骤

以NVIDIA Orin为例,我演示一下安装流程:

# 1. 下载JetPack SDK
wget https://developer.nvidia.com/embedded/jetpack

# 2. 解压到指定目录
tar -xvf JetPack_5.1.2_Linux_JETSON_AGX_ORIN_TARGETS.tar.gz -C ~/tools/

# 3. 设置环境变量
export CROSS_COMPILE=~/tools/jetpack_5.1.2/toolchain/aarch64--glibc--stable-2022.08-1/bin/aarch64-linux-gnu-
export CC=${CROSS_COMPILE}gcc
export CXX=${CROSS_COMPILE}g++
export AR=${CROSS_COMPILE}ar
export LD=${CROSS_COMPILE}ld

小技巧:把这些export命令写到~/.bashrc里,每次打开终端自动生效。省得每次手动敲。

2.2.3 验证工具链

装好了,怎么知道对不对?写个简单的测试程序:

// hello_adas.c
#include <stdio.h>

int main() {
    printf("Hello from ADAS Domain Controller!\n");
    return 0;
}
# 编译
aarch64-linux-gnu-gcc hello_adas.c -o hello_adas

# 查看文件信息
file hello_adas
# 输出应该类似:ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV)

看到“ARM aarch64”就对了。如果显示的是“x86-64”,说明你用的是本地编译器,不是交叉编译器。

注意:我曾经犯过一个低级错误——在宿主机上直接运行交叉编译出来的程序。结果报错“cannot execute binary file”。别笑,很多人都会犯。ARM的程序不能在x86上跑,这是常识,但忙起来就容易忘。

2.3 Sysroot的构建

工具链装好了,你以为就能愉快地编译了?还差一步——Sysroot。

Sysroot是什么?简单说,它就是目标机根文件系统的一个“快照”。里面包含了目标机上所有的头文件、库文件、配置文件。编译时,编译器需要知道目标机上有什么库、什么版本、什么接口。

没有Sysroot,你编译出来的程序可能会链接到宿主机上的库,拿到目标机上就找不到符号了。

2.3.1 获取Sysroot的几种方式

方式 适用场景 优点 缺点
从目标板拷贝 已有硬件原型 最准确,与目标机完全一致 需要硬件,拷贝过程繁琐
使用厂商提供的Sysroot 开发初期 方便,开箱即用 可能与实际板子有差异
用Yocto/ Buildroot生成 定制化系统 完全可控,可定制 学习曲线陡,构建时间长

我个人最推荐第一种——从目标板拷贝。为什么?因为最真实。你想想看,目标板上跑的是什么库版本,你拷贝下来的就是什么版本。不会有任何偏差。

2.3.2 从目标板拷贝Sysroot

假设你的目标板已经跑起来了,SSH能连上:

# 在宿主机上执行
# 创建本地sysroot目录
mkdir -p ~/sysroot

# 通过rsync同步目标板的根文件系统
rsync -avz root@192.168.1.100:/lib ~/sysroot/
rsync -avz root@192.168.1.100:/usr/lib ~/sysroot/usr/
rsync -avz root@192.168.1.100:/usr/include ~/sysroot/usr/

注意:不要同步整个根文件系统。只需要同步/lib、/usr/lib、/usr/include这几个目录就够了。同步整个系统会包含很多运行时不需要的文件,白白浪费空间。

2.3.3 配置编译器使用Sysroot

有了Sysroot,怎么让编译器知道它的存在?加一个编译选项:

# 编译时指定sysroot
aarch64-linux-gnu-gcc \
    --sysroot=/home/user/sysroot \
    -I/home/user/sysroot/usr/include \
    -L/home/user/sysroot/usr/lib \
    hello_adas.c -o hello_adas

或者,更优雅的方式——写一个CMake工具链文件:

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

set(CMAKE_SYSROOT /home/user/sysroot)
set(CMAKE_STAGING_PREFIX /home/user/staging)

set(tools /home/user/tools/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu)
set(CMAKE_C_COMPILER ${tools}/bin/aarch64-none-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER ${tools}/bin/aarch64-none-linux-gnu-g++)

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)

用的时候:

cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake ..
make

经验之谈:我建议把Sysroot和工具链放在同一个父目录下,比如~/adas_toolchain/。这样管理起来清晰,换项目时直接拷贝整个目录就行。

2.4 避坑指南

最后,分享几个我踩过的坑,你遇到了可以少走弯路:

  • 工具链版本与内核版本不匹配:我曾经用gcc 9编译的程序,放到内核4.14的板子上,结果某些系统调用不兼容。后来统一用厂商SDK里的工具链,再没出过问题。
  • Sysroot权限问题:从目标板拷贝时,有些文件权限是只读的。rsync时加个--chmod=ugo=rwX参数,省得后面编译时提示权限不足。
  • 动态库链接错误:编译时链接了libcrypto.so.1.1,但目标板上只有libcrypto.so.1.0。程序跑起来就报错。解决办法:编译前用readelf -d检查依赖,确保目标板上都有。

好了,这一章就到这里。交叉编译环境搭好了,下一章咱们就可以真正开始写代码了。记住,环境搭得好,开发没烦恼。别嫌麻烦,这一步值得花时间。