1. 为什么交叉编译是RK3588开发绕不开的第一道坎
搞香橙派5的兄弟大概率都有过这样的经历:板子上跑着Ubuntu或者Debian,想编译一个最简单的C程序测试环境,结果发现板子自带的gcc编译一个hello world都要等好几秒,稍微大一点的工程直接卡到怀疑人生。这不是板子性能不行,RK3588这颗芯片本身是8核A76+A55的架构,性能放在ARM开发板里算是第一梯队了,问题出在存储和散热上——你插的那张TF卡或者eMMC的随机读写速度,跟PC上的NVMe固态完全不是一个量级,编译过程中大量的小文件读写会把IO打满,CPU反而在等IO。
交叉编译就是解决这个问题的核心手段。简单说,就是在你的x86_64电脑上,用一套专门为ARM架构准备的编译器,把代码编译成能在RK3588上运行的二进制文件,然后通过scp或者U盘拷到板子上执行。这样编译速度取决于你PC的性能,拷贝一个几KB的hello可执行文件也就是一瞬间的事。
这一节是整套教程的第六篇,前面几篇应该已经带着大家把香橙派5的系统烧录、串口登录、网络配置这些基础工作做完了。现在到了真正写代码的环节,我选择从hello world开始,不是因为它简单,而是因为它能把交叉编译的完整链路跑通——工具链安装、环境变量配置、编译参数指定、文件传输、板端执行,每一步都走一遍,后面编译YOLOv5s的推理程序时就不会手忙脚乱。
提示:交叉编译工具链的选择非常关键,选错了后面全是坑。香橙派官方和Rockchip原厂都提供了工具链,但版本和配置方式有差异,下面会详细对比。
适合阅读这篇内容的人:刚拿到香橙派5、已经能正常登录系统、准备开始写C/C++程序或者部署AI模型的开发者。如果你连板子还没点亮,建议先回去看前面的系统烧录教程。
2. 交叉编译工具链的选型与安装
2.1 三种主流工具链的对比
给RK3588做交叉编译,市面上能用的工具链主要有三类,我分别用过,踩过的坑不太一样。
第一类是Rockchip原厂SDK里自带的工具链,通常在SDK的prebuilts/gcc/linux-x86/aarch64/目录下,名字类似gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu。这套工具链跟RK3588的BSP是配套的,glibc版本、内核头文件都是对齐的,理论上兼容性最好。但问题是它藏在SDK里,如果你没下载完整的SDK,单独去搞这套工具链比较麻烦。
第二类是Linaro或者ARM官方发布的GNU工具链,比如gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu,这个跟Rockchip用的其实是同一个版本。ARM官方每季度会更新,下载方便,文档齐全。我目前用的就是这个,稳定性和兼容性都经过验证。
第三类是香橙派官方Wiki里推荐的工具链,有时候会指向一个特定的下载链接。香橙派官方文档更新频率一般,链接失效是常有的事,而且不同批次的板子可能预装不同的系统版本,工具链的glibc版本要对上。
| 工具链来源 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| Rockchip SDK自带 | 与BSP完全配套 | 需要下载完整SDK,体积大 | 深度定制系统、编译内核驱动 |
| ARM官方GNU工具链 | 下载方便、版本清晰、文档全 | 需要自己确认glibc兼容性 | 应用层开发、AI模型部署 |
| 香橙派官方推荐 | 官方验证过 | 链接易失效、更新慢 | 新手按官方教程走 |
我的建议是:如果你只是做应用层开发,比如编译YOLOv5s的推理程序、写一些测试工具,直接用ARM官方的GNU工具链就行。如果你要编译内核模块或者修改系统底层,那必须用Rockchip SDK自带的,否则内核版本对不上会出各种诡异问题。
2.2 下载与解压的实操细节
我以ARM官方10.3版本为例,这个版本对应的是glibc 2.33,香橙派5默认的Ubuntu 22.04系统glibc版本是2.35,向下兼容没问题。如果你用的是Debian 11,glibc是2.31,那就要注意了,用10.3编译出来的程序在Debian 11上可能跑不起来,因为依赖的glibc版本比系统自带的还高。
下载地址去ARM开发者官网找,搜索“GNU Toolchain for AArch64”,找到gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz这个文件。文件大概100多MB,下载完先校验一下MD5,避免下载过程中损坏。
# 下载完成后校验 md5sum gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz # 对比官网提供的MD5值解压到一个你习惯放工具的目录,我一般放在/opt/toolchain/下面:
sudo mkdir -p /opt/toolchain sudo tar -xvf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz -C /opt/toolchain/解压完检查一下目录结构,确认bin目录下有aarch64-none-linux-gnu-gcc这个可执行文件:
ls /opt/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/你应该能看到aarch64-none-linux-gnu-gcc、aarch64-none-linux-gnu-g++、aarch64-none-linux-gnu-ld这些文件。如果没有,说明解压不完整,重新下载。
2.3 环境变量配置的两种方式
配置环境变量有两种方式,临时生效和永久生效。临时生效就是直接在终端里export,关掉终端就没了,适合测试:
export PATH=/opt/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH永久生效要写进~/.bashrc或者/etc/profile。我推荐写进~/.bashrc,只对当前用户生效,不会影响系统里其他用户。在文件末尾追加:
echo 'export PATH=/opt/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH' >> ~/.bashrc source ~/.bashrc验证是否配置成功:
aarch64-none-linux-gnu-gcc -v如果输出了gcc的版本信息,说明配置成功。如果提示command not found,检查路径是不是写错了,或者用绝对路径试试。
注意:有些教程会让你把工具链的
bin目录直接加到PATH最前面,这没问题,但如果你系统里已经装了其他交叉编译工具链,比如arm-linux-gnueabihf,那PATH的顺序就很重要了。建议用which aarch64-none-linux-gnu-gcc确认一下实际调用的是哪个。
3. 从hello.c到板端运行的完整实操
3.1 编写一个“不那么简单”的hello程序
很多人觉得hello world太简单,随便写两行就行。但我要用这个程序验证的东西不少:编译器能不能正常工作、标准库链接有没有问题、生成的可执行文件架构对不对、板子上能不能跑起来。所以我写的hello.c会稍微加一点东西:
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { printf("Hello, Orange Pi 5!\n"); printf("This binary is compiled for AArch64.\n"); printf("argc = %d\n", argc); for (int i = 0; i < argc; i++) { printf("argv[%d] = %s\n", i, argv[i]); } return 0; }加打印参数个数和参数内容,是为了验证程序在板子上运行时能正常接收命令行参数,后面调试YOLOv5s的时候经常需要传模型路径、图片路径这些参数,提前验证一下没坏处。
3.2 编译命令的每个参数都要说清楚
编译命令看起来简单,但每个参数都有讲究:
aarch64-none-linux-gnu-gcc hello.c -o hello -static-o hello指定输出文件名,这个不用多说。关键是-static这个参数,我强烈建议加上。动态链接的话,板子上必须有对应的动态库,而且版本要匹配。你编译时用的工具链glibc是2.33,板子上是2.35,动态链接可能没问题,但如果是反过来,板子上是2.31,那就会报GLIBC_2.33 not found的错误。静态链接把所有依赖都打包进可执行文件,体积会大一些,但省去了库版本匹配的麻烦。
编译完检查一下生成的文件:
file hello输出应该是类似这样的:
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, for GNU/Linux 3.7.0, not stripped重点看ARM aarch64和statically linked这两个信息。如果是x86-64,说明你调用的还是系统自带的gcc,不是交叉编译器,检查PATH。如果是dynamically linked,说明-static没生效,检查参数拼写。
还可以用readelf看一下更详细的信息:
aarch64-none-linux-gnu-readelf -h hello输出里的Machine字段应该是AArch64,Class是ELF64。
3.3 传输到板子的三种方式
编译出来的hello文件要传到板子上执行,有三种常用方式。
第一种是scp,前提是板子和电脑在同一个局域网,而且板子开了SSH服务。香橙派5默认的Ubuntu镜像一般已经装了SSH,如果没有,在板子上执行sudo apt install openssh-server。传输命令:
scp hello orangepi@192.168.1.100:/home/orangepi/把192.168.1.100换成你板子的实际IP。传输速度取决于网络,几KB的文件基本秒传。
第二种是U盘,适合板子没联网或者网络配置麻烦的情况。把hello拷到U盘,插到板子上,挂载U盘,拷贝文件。挂载命令一般是:
sudo mount /dev/sda1 /mnt cp /mnt/hello /home/orangepi/ sudo umount /mnt第三种是通过NFS或者TFTP,适合频繁传输大量文件的场景。配置稍微麻烦一点,但一次配好后面就方便了。我平时调试YOLOv5s的时候用NFS比较多,因为模型文件和测试图片经常要换。
3.4 板端执行与权限设置
文件传到板子上之后,默认是没有执行权限的,直接运行会提示Permission denied。先加权限:
chmod +x hello然后运行:
./hello如果一切正常,你会看到:
Hello, Orange Pi 5! This binary is compiled for AArch64. argc = 1 argv[0] = ./hello再试试带参数运行:
./hello test 123输出里应该能看到argc = 3,以及argv[1] = test、argv[2] = 123。
实操心得:如果运行时报
No such file or directory,但文件明明存在,大概率是动态链接器的问题。用file hello确认是不是动态链接,如果是,要么重新用-static编译,要么在板子上安装对应的库。还有一种可能是文件系统挂载时带了noexec选项,用mount | grep noexec检查一下。
4. 交叉编译常见问题与排查实录
4.1 工具链版本与系统glibc不匹配
这是最常见的问题,没有之一。现象是编译时没问题,板子上运行时报错:
./hello: /lib/aarch64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found原因是你编译时用的工具链glibc版本高于板子系统的glibc版本。解决办法有两个:一是换用更低版本的工具链,比如板子是Debian 11(glibc 2.31),就用gcc-arm-10.2或者更早的版本;二是用-static静态编译,把glibc打包进去。
怎么查板子的glibc版本:
ldd --version输出第一行就是glibc版本号。
怎么查工具链的glibc版本:
aarch64-none-linux-gnu-gcc -print-file-name=libc.so.6 # 然后用readelf查看 aarch64-none-linux-gnu-readelf -a /path/to/libc.so.6 | grep GLIBC4.2 找不到头文件或库文件
编译时报fatal error: xxx.h: No such file or directory,说明工具链的sysroot里没有这个头文件。交叉编译工具链自带一套sysroot,里面是标准的C库头文件,但如果你用了第三方库,比如OpenCV、FFmpeg,就需要自己交叉编译这些库,然后把头文件和库文件路径通过-I和-L参数指定给编译器。
比如你交叉编译了OpenCV到/opt/opencv-arm64/,编译命令要写成:
aarch64-none-linux-gnu-gcc hello.c -o hello -static \ -I/opt/opencv-arm64/include \ -L/opt/opencv-arm64/lib \ -lopencv_core -lopencv_imgproc注意-I和-L的顺序,以及-l参数要放在源文件后面,否则链接器可能找不到符号。
4.3 编译出来的程序在板子上跑不起来
除了glibc版本问题,还有几种可能。一是架构不对,用file命令确认是ARM aarch64而不是x86-64。二是文件系统权限问题,用ls -l看有没有执行权限。三是板子的内核版本太低,编译时指定的--sysroot或者内核头文件版本高于板子实际内核版本。
排查步骤可以按这个顺序来:
file hello确认架构chmod +x hello确认权限ldd hello查看动态库依赖(如果是动态链接)uname -a查看板子内核版本dmesg | tail查看内核日志有没有报错
4.4 常见问题速查表
| 现象 | 可能原因 | 排查命令 | 解决方法 |
|---|---|---|---|
command not found | PATH未配置 | which aarch64-none-linux-gnu-gcc | 重新配置环境变量 |
GLIBC_2.xx not found | glibc版本不匹配 | ldd --version | 换工具链或静态编译 |
No such file or directory | 动态链接器缺失 | file hello | 静态编译或安装库 |
Permission denied | 无执行权限 | ls -l hello | chmod +x hello |
cannot execute binary file | 架构不对 | file hello | 检查交叉编译器PATH |
| 编译时报头文件缺失 | sysroot不完整 | find / -name xxx.h | 指定-I路径或交叉编译依赖库 |
避坑技巧:我习惯在编译完第一时间用
file和readelf检查生成的文件,确认架构和链接方式正确,再传到板子上。这样能把大部分问题挡在传输之前,省得来回折腾。
5. 从hello到YOLOv5s:交叉编译的后续扩展
hello world跑通之后,交叉编译的链路就算打通了。接下来编译YOLOv5s的推理程序,本质上就是把hello.c换成更复杂的C++代码,把依赖的库从标准C库换成RKNN API和OpenCV。工具链不变,编译参数增加-I和-L指定RKNN和OpenCV的路径,链接参数增加-lrknn_api -lopencv_core这些。
我实际编译YOLOv5s推理程序时,遇到的最大问题不是编译本身,而是RKNN库的版本匹配。Rockchip的RKNN Toolkit2更新比较频繁,不同版本生成的rknn模型文件跟板子上的librknnrt.so版本必须对应,否则加载模型时会报错。这个后面讲到模型转换和部署的时候再细说。
另外,如果你要编译的C++程序用了C++17的特性,记得把编译器换成aarch64-none-linux-gnu-g++,并且加上-std=c++17参数。链接时如果用到数学库,还要加-lm。
交叉编译工具链的安装路径建议记下来,后面写CMakeLists.txt或者Makefile的时候要用。我一般会在项目根目录放一个toolchain.cmake文件,把编译器路径、sysroot路径、编译参数都写进去,这样用CMake构建的时候直接-DCMAKE_TOOLCHAIN_FILE=toolchain.cmake就行,不用每次手动敲一长串参数。
这个hello程序虽然简单,但它验证了从x86到ARM的完整工具链。后面不管编译什么程序,流程都是一样的:写代码、交叉编译、传输、板端执行。把这一步走稳了,后面的YOLOv5s部署就是水到渠成的事。