第二章 交叉编译环境搭建:宿主机与目标机、交叉编译工具链的安装与配置、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检查依赖,确保目标板上都有。
好了,这一章就到这里。交叉编译环境搭好了,下一章咱们就可以真正开始写代码了。记住,环境搭得好,开发没烦恼。别嫌麻烦,这一步值得花时间。