在折腾RK3588的路上,交叉编译是绕不开的一道坎。前几篇咱们把香橙派5刷好系统、连上网络、跑通了基础环境,也聊了 yolov5s 在板端推理的大致图景。现在到了衔接 PC 端和板子端的关键一步:交叉编译。很多人一听这个词就头疼,其实它没那么玄乎——就是在一台机器上编译出另一台机器能运行的程序。这篇我就拿最经典的 hello world 当试验品,把交叉编译的完整链路彻底走一遍,工具链装法、编译参数、传板方式、运行验证、常见报错,一次讲明白。这篇之后你再去看 opencv、rknn 相关库的交叉编译,思路完全一样,等于提前把最难的水下冰山摸清楚了。
1. 交叉编译到底是什么,为什么绕不开
1.1 一块板子两种"方言"
先捋一个基本概念:你的日常电脑,只要不是苹果 M 系列,大概率是 x86_64 架构;香橙派5上的 RK3588 芯片则是 ARM 架构,准确叫 AArch64。这两种芯片的机器指令互不通用,就像普通话和粤语,你说得再流利,对方听不懂也是白搭。
交叉编译的意思,就是我在 x86 电脑上,用一套"会讲 ARM 方言"的编译器,把源代码编译成 RK3588 能直接运行的可执行文件。这套编译器跟普通编译器最大的区别在于:它生成的机器码目标不是当前这台电脑,而是另一台设备。所以叫"交叉",cross compile。
这里有个容易混淆的点:SDK、交叉编译器和目标平台之间不是固定搭配。理论上任何平台都能交叉编译出任何其他平台的程序,只是工具链的获取难度和维护成本不同。对香橙派5来说,最常规的方案就是 x86_64 Linux 主机上装 aarch64-linux-gnu 工具链,输出 ARM 64 位的 ELF 文件。我们后面所有实操都基于这个组合。
1.2 为什么不在RK3588上直接编译
有人会问:既然板子能开机、能装系统,直接在板子上 gcc 编译不行吗?行,但你会很难受。
第一个问题是资源。RK3588 性能确实不错,8核A76+A55,跑推理还行,但编译大型依赖库时,CPU 长时间满载,散热跟不上的话,板子温度直接冲着 85 度以上去。内存如果只有 4GB 或 8GB,编译 opencv、boost 这类重库时 swap 疯狂读写 TF 卡,整个系统卡成幻灯片,编译一个多小时然后中途报错,这种体验我经历过一次就不想再来第二次。
第二个问题是环境。在板子上编译,你得先把开发包装在板端系统里,apt 装一堆 -dev 库,把板子原本干净的系统搞得乱七八糟。板端系统是拿来跑最终程序的,不是拿来当开发机的。开发环境留在 PC,运行环境留在板子,这是嵌入式开发的黄金分工。
第三个问题才是关键:最终部署 yolov5s 相关 C++ 推理程序时,很多依赖库你根本没法在板子上现场编译,就算能,也没有那个必要。交叉编译才是正路。
1.3 交叉编译的边界:编译链 vs 运行时
交叉编译只解决"代码变可执行文件"这一步,它不负责程序运行时的所有事情。编译时你选择了动态链接,程序运行时就要求板子上有对应的动态库;编译时用了静态链接,那运行依赖就全都打包进文件里。这个区别后面 hello 实验里会实际看到,提前有个概念:编译是"翻译",运行是"登台",翻译稿再好,舞台上缺道具照样演不了。
明白这一点,你就能理解为什么交叉编译出的程序经常会出现"放到板子上报找不到动态库"的奇怪现象。不是程序写错了,也不是架构不对,而是"翻译稿"里引用了板子上不存在的库。后面排查章节会专门细说。
2. 环境准备:先把工具链装明白
2.1 一台能上网的x86 Linux电脑
交叉编译的第一步,是准备一台 Linux 宿主机。Ubuntu 20.04 或 22.04 都可以,虚拟机也行,但注意虚拟机里要给足够的磁盘和内存,编译时别抠抠搜搜。Windows 上也可以用 WSL,不过网络、USB 透传这些偶尔有小毛病,如果目标是把 RK3588 部署玩明白,我还是建议装个原生 Ubuntu,一劳永逸。
确认一下主机架构,在终端跑:
uname -m输出 x86_64,就说明主机是 64 位 x86 架构,符合我们的前提。这步看着多余,实际排查问题的时候很有用,一多半架构错乱都是因为没确认这个。
2.2 安装aarch64交叉编译器
Ubuntu 的软件源里直接有交叉编译工具链,不需要去第三方网站下载,这点比很多人想象得省事。打开终端执行:
sudo apt update sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu装完就有了两个关键命令:aarch64-linux-gnu-gcc和aarch64-linux-gnu-g++。前者编译 C 代码,后者编译 C++ 代码。做 yolov5s 部署时,C++ 程序用得到 g++,别只装 gcc。
这里多说几句:网上很多教程会引导你去下载 Linaro 或者 ARM 官网的工具链压缩包,还要自己配环境变量,折腾半天。其实 Ubuntu 源里的工具链版本虽然未必是最新,但足够稳定,支持 glibc 动态链接,日常交叉编译完全够用。新手没必要一上来就挑战手工部署工具链,先把流程跑通再说。
2.3 验证工具链是否可用
装完不要急着写代码,先验证。执行:
aarch64-linux-gnu-gcc -v终端应该打印出包含 Target: aarch64-linux-gnu 的信息,以及 gcc 版本号。看到这两行,说明工具链生效了。
如果提示 command not found,大概率是工具链没装上,或者当前 shell 没重新加载 PATH。可以试试重启一下终端,或者直接重新执行 apt install。多数情况是前者。
这里还有一个常见的低级错误:有人会把arm-linux-gnueabihf-gcc当成 RK3588 的工具链。这个工具链是给 ARM 32 位 CPU 用的,香橙派5是 64 位,用了它编译出的程序在板子上会报 Exec format error。道理很简单但坑很真实,我在很多交流群里见过新手卡在这。
3. 第一个hello:从源码到ARM可执行文件
3.1 写一段最朴实的C代码
现在动真格的。在宿主机上建个目录,我习惯叫~/rk3588-cross/,里面放一个文件,名字就叫 hello.c:
#include <stdio.h> int main(void) { printf("Hello, RK3588!\n"); return 0; }这段代码没有任何特殊之处,就是最基础的 C 程序。但请相信我,越基础的代码越适合做工具链验证。先把最简程序跑通,再去玩复杂的,这是嵌入式开发的铁律。代码里的输出我特意写成 "Hello, RK3588!" 而不是传统的 "Hello World",这样等会儿在板子上看到输出时,能直观确认跑的就是这个交叉编译的产物。
3.2 用交叉编译器生成ARM程序
在终端进入目录,执行:
aarch64-linux-gnu-gcc hello.c -o hello命令成功执行,没有任何输出,目录里多了一个名为 hello 的文件。注意,编译过程没有任何提示就是最好的提示。如果报错,多半是代码本身问题,比如标点符号用了中文输入法,或者缺少头文件。
接下来最关键的一步验证,用 file 命令看看这个文件到底是什么:
file hello正常输出:
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=xxx, not stripped盯着 ELF 64-bit LSB executable 和 ARM aarch64 这两段。它明确告诉你:这是一个 ARM 64 位的可执行文件,不是 x86 的。交叉编译的第一步,到这里就成功了。
3.3 动态与静态:两种链接方式怎么选
file 输出里还有一行值得注意:dynamically linked, interpreter /lib/ld-linux-aarch64.so.1。这句话的意思是,这个程序依赖系统的动态加载器,运行时需要板子上的 glibc 动态库。大部分情况下没问题,因为香橙派5官方的 Ubuntu/Debian 系统里这些库都齐全。
但如果你想更保险,可以用静态编译:
aarch64-linux-gnu-gcc -static hello.c -o hello_static静态编译会把 C 库直接塞进可执行文件里,编出来的文件体积大很多,但完全不依赖板端库,扔到任何 AArch64 Linux 系统上都能跑。我对比一下两个文件大小:
ls -lh hello hello_static动态版可能只有十几 KB,静态版动辄几百 KB 甚至上 MB。这就是"带不带干粮"的区别:动态版是到了再找饭馆,静态版是把干粮全背身上。静态编译也有缺点:编译慢、文件大、而且用到的库如果涉及 license,打包分发会有合规问题。对 hello 实验来说,建议两个都编译出来,都传上板子跑一跑,体会一下区别。
4. 把程序送上香橙派跑起来
4.1 三个派送可执行文件的方式
程序编译好了,怎么弄到板子上?三个常用办法,按推荐程度排序。
第一,SCP 网络传送。前提是板子和电脑在同一局域网下,板子能上网,你知道它的 IP 地址。我习惯先在电脑上确认板子 IP,然后执行:
scp hello hello_static orangepi@192.168.1.100:/home/orangepi/把 192.168.1.100 换成你板子的实际 IP。它会提示输入密码,输入后文件就传过去了。这个方式最方便,后面传模型文件、传库文件都用它。
第二,U 盘拷贝。把 hello 文件放进 U 盘,插到板子的 USB 口上,在板子上用lsblk看一下挂载路径,一般是 /media/orangepi/xxx,然后 cp 出来。这种方式不依赖网络,适合电脑和板子不在同一网段时应急。
第三,ADB 推送。如果板子系统里开了 adb server,刚好还被电脑识别了,可以用:
adb push hello /home/orangepi/这个方法在调试阶段很有用,特别是板子屏幕上操作不方便的时候。不过香橙派5官方系统默认不一定开 adb,需要自己装或启用,对新手来说门槛略高,我更推荐前两种。
4.2 给文件执行权限并运行
文件传到板子上之后,切记先做一件事:给可执行权限。虽然编译器默认生成的文件通常带执行权限,但经过 U 盘或某些传输方式之后可能会丢。在板子的终端里运行:
chmod +x hello hello_static然后执行:
./hello正常情况下会输出:
Hello, RK3588!看到这行字,恭喜,整个交叉编译链路已经彻底闭环了:x86 代码仓库、ARM 编译器、ARM 可执行文件、板端运行成功。
再试试静态版:
./hello_static同样输出,但步骤完全不一样,你体会一下两者在板端运行时的差异。如果你够细心,还可以对比下ls -lh看两个文件大小,直观感受静态库的"体积税"。
4.3 交叉编译思维的确立
很多人以为交叉编译学会命令就完事了,其实最重要的是一种思维转换:以后你每次在电脑上写完代码,第一反应不应该是"本地跑一下试试",而是"这个是要给哪个平台用的"。如果是板子上的程序,就直接调 aarch64 工具链,别再用 gcc 编译完了发现架构不对,回头重新编。
这个思维一旦建立,后面看 opencv 交叉编译、rknn 相关库的 CMake 配置,你会觉得特别亲。因为无论多复杂的库,底层就是在做同一件事——告诉构建系统"我的目标平台是 aarch64,我的工具链在哪,我的 sysroot 在哪"。hello world 是这套思维的最小模型。
5. 常见问题与排查技巧实录
5.1 高频报错速查表
这一节是大量实操积累出来的干货,我按报错现象整理成速查表,方便你直接对号入座。
| 报错现象 | 原因分析 | 解决办法 |
|---|---|---|
| aarch64-linux-gnu-gcc: command not found | 工具链没安装或 PATH 未配置 | 重新执行 apt install;检查是否在正确的用户环境 |
| 运行报 No such file or directory | 文件是动态链接,板子上缺少依赖库 | 改用-static静态编译;或板端补装库;用 ldd 查看缺失项 |
| 运行报 Exec format error | 文件架构不对,比如编了个 x86 或 ARM 32 位 | file 看架构;确认用 aarch64-linux-gnu-gcc |
| Permission denied | 文件没有执行权限 | chmod +x hello |
| 编译远超预期,卡死或 OOM | 宿主机资源不足,或 swap 不够 | 换更高配置电脑;用静态编译时更吃内存 |
| 源码在板子上中文字符乱码 | hello.c 含中文注释或输出,编码不一致 | 保持源码用 ASCII;输出英文;文件统一 UTF-8 |
表格之外补充一个很容易撞上去的坑:代码文件是在 Windows 上编辑的,保存成了 CRLF 行尾,拿到 Linux 交叉编译时偶尔会报 stray 字符错误。解决办法是运行 dos2unix,或者干脆在 Ubuntu 上写代码。
5.2 排查三板斧:file、readelf、ldd
遇到问题别慌,记住这三个命令。
第一斧,file 看架构。任何可执行文件到了手里,先 file 一下,确认 ELF 头和架构匹配。这能过滤掉八成问题。
第二斧,readelf 深入检查。比如:
readelf -h hello可以看到 Class 是 ELF64、Machine 是 AArch64,还能看到程序入口地址。当你有多个工具链、多个版本的时候,这招能精确判断文件到底是谁编出来的。
第三斧,ldd 查依赖。在板子上对动态链接的可执行文件执行:
ldd hello它会列出程序运行需要的所有动态库,以及是否已经找到。如果某一行显示 "not found",那就是缺库,直接把问题定位到"运行时缺少哪个库"这一层。注意 ldd 是在板子上用的,不是在宿主机上,因为宿主机和板子的库环境完全不同。
这三板斧组合起来,能从"架构、入口、依赖"三个维度把一个可执行文件看透。我在实际部署中,只要遇到运行问题,基本就靠这三招定位,效率极高。
6. 这跟yolov5s到底什么关系
6.1 yolov5s在RK3588上的典型部署链路
聊完 hello world,很多人会问:我学的是 yolov5s,跟交叉编译有什么关系?
绝大多数情况下,yolov5s 的部署不是直接把 PyTorch 模型扔到板子上跑,那样速度太慢、资源也浪费。RK3588 的看家本领是内置的 NPU(6 TOPs 算力),模型要先经过转换,变成 NPU 能认的 RKNN 格式,然后才能在板端高效推理。
这条链路一般是:训练好的 yolov5s.pt 先转成 ONNX,再用 RKNN-Toolkit2 在 PC 端转成 .rknn 文件,最后把 .rknn 文件和推理脚本一起部署到板子上。如果你用的是 Python API,那确实不用交叉编译;但只要你想上 C++ API,想提高实时性、想脱离 Python 环境,就得交叉编译 RKNN runtime 和 opencv 这些依赖库。这些库体积大、源码复杂、交叉编译配置项多,比 hello world 难不止一个量级。
现在 YOLO 系列迭代很快,yolov8、yolo26 各种新模型不断出来,模型轻量化、NPU 适配是热点,但落到 RK3588 上,跑通交叉编译这条链路永远是共同的基础。模型换了一代又一代,工具链的使用逻辑没变过。
6.2 交叉编译的下一步学习路线
如果你把 hello world 完整跑通了,接下来的学习顺序我建议是这样:先学会交叉编译一个带第三方依赖的简单 C++ 程序(比如链接一个 sqlite 库),再尝试交叉编译 opencv 的一个精简模块,然后再碰 RKNN 的 C API。每一步都是在加深同一个技能:告诉构建系统"目标平台是 aarch64、工具链在哪、依赖库在哪"。
这个过程会有很多坑,比如 CMake 找不到交叉编译器、库的 sysroot 不匹配、链接时缺符号等等。但我可以负责任地告诉你:只要 hello world 这条链路通了,后面那些坑大概率只是时间问题,不会是方向问题。真正劝退人的,永远是第一步没迈过去就放弃。
我在实际项目中还发现一个规律:很多同学卡住不是卡在编译本身,而是卡在"不知道自己的程序运行到哪一步了"。建议你在板子上学会用dmesg看内核日志、用top看进程资源占用,这些调试手段配合交叉编译,能解决一大半疑难杂症。说白了,交叉编译只是链条的一端,另一端是目标机上的运行时观察能力,两手都要硬。
最后分享一点个人体会:我刚开始折腾时,也试过直接在板子上 make -j4,看着温度飙到 80 多度、风扇狂转,最后编译失败,整个人差点崩掉。后来老老实实从 hello world 开始跑交叉编译,才发现原来这条路通畅得多也轻松得多。hello world 虽小,但它像是 PC 和板子之间的一次"握手",握成功了,后面 opencv、rknn 这些大块头你才有信心继续往下碰。建议你现在就把 hello 和 hello_static 都跑一遍,感受一下动态和静态的差异,再顺手把 file、readelf、ldd 这三个命令在板子上都过一遍。这些基本功,远比背几个编译参数值钱得多。