不用管什么开发板吃灰的问题了,这篇直接把香橙派RK3588上的交叉编译Hello World跑通。按系列进度来说,前五篇已经把板卡选型、系统烧录、SSH登录、开发环境这些铺垫做完了,这一篇开始进入真正的编译环节——先用一个最经典的hello.c把交叉编译链路打通,为后面在RK3588上部署yolov5s扫清最关键的障碍。
我一个搞嵌入式的老朋友说过一句话:交叉编译这关过不去,后面所有部署都是空中楼阁。这话一点不夸张。很多人在x86主机上编译得好好的程序,一放到arm板上就“Segmentation fault”或者“No such file or directory”,根子往往不是代码问题,而是工具链、架构、链接库这三件事没理顺。这篇就用hello world做一次全链路体检,把每一条报错、每一个坑都摊开来讲。
1. 为什么非要从交叉编译Hello开始?这套路的真实意图
1.1 RK3588板子上跑程序,有这么两条路线
香橙派5系列是四板卡里比较能打的,RK3588这颗芯片是8核心ARM架构,A76大核加A55小核的big.LITTLE组合。跑起Linux系统之后,你会发现板子本身性能并不弱,甚至直接在板子上用gcc编译小项目也没问题。于是就有了两个路线:
- 板载编译:直接在板子上安装gcc,把源码拷贝过去,在板上执行gcc编译。这种方式简单粗暴,适合小项目、一次性的测试代码。
- 交叉编译:在性能更强的x86主机上编译出ARM架构的可执行文件,再传输到板子上运行。这是嵌入式开发的常规姿势,也是生产环境的标准做法。
很多人会问,RK3588性能又不差,为什么非要交叉编译?咱们后面要部署yolov5s模型推理程序,那可不是一个hello.c那么简单。一个完整的yolov5s部署涉及OpenCV、NCNN或者ONNX Runtime、各种依赖库,源码编译动辄一两个小时。在x86主机上交叉编译,依赖工具链成熟、CPU强、内存大,编译速度可能是板载编译的几倍甚至十几倍。更关键的是,很多第三方库在x86上的交叉编译工具链和文档都非常完善,而板载编译一旦遇到依赖冲突,排查起来极其痛苦。
1.2 Hello World为什么是最好的“体检项目”
交叉编译的链路里,任何一个环节出问题都会导致最终产物在板子上跑不起来。而这个环节,恰好用hello world就能全部覆盖:
- 交叉编译器是否安装正确、版本是否匹配
- 编译出来的可执行文件架构是不是ARM
- 动态链接器路径和目标板子的运行库是否兼容
- 传输方式是否可靠、文件权限是否正确
这几件事,一行hello world全部能验出来。假如连hello world都在板子上跑不起来,那别急着碰yolov5s,先把这条链路修好再说。我在实际项目里见过太多人,OpenCV编译包搞了好几个G,传到板子上执行直接报“cannot execute binary file”,结果回头一看是x86_64的二进制,白折腾一整天。
2. 交叉编译工具链的下载与安装:选错版本会坑到你怀疑人生
2.1 先搞清楚RK3588需要什么架构的编译器
RK3588是64位ARM处理器,跑Linux系统时,用户态程序对应的架构是aarch64。也就是说,我们需要一个能在x86主机上运行、但能生成aarch64机器码的编译器,这就是所谓的“交叉编译器”。常见的组合是:
| 主机架构 | 目标架构 | 编译器前缀 | 说明 |
|---|---|---|---|
| x86_64 | aarch64 | aarch64-linux-gnu- | RK3588跑Linux系统时使用 |
| x86_64 | arm | arm-linux-gnueabihf- | 老款32位ARM芯片使用,不是本文目标 |
| aarch64 | aarch64 | gcc | 板载编译,不走交叉编译路线 |
这里必须注意一个全网常见的坑:网上搜“RK3588交叉编译”,会搜到一大堆瑞芯微SDK里自带的工具链,前缀往往是aarch64-rockchip-linux-gnu。这套工具链不是不能用,但它是跟RK3588的Buildroot SDK捆绑发布的,版本较老、依赖路径往往写死,直接用容易遇到GLIBC版本兼容问题。自己玩板子、跑Ubuntu系统,我建议直接用Ubuntu官方源的gcc-aarch64-linux-gnu,干净、省心、后续装依赖库也方便。
2.2 Ubuntu主机上一步到位安装
我的环境是Ubuntu 22.04,x86_64架构。安装命令如下:
sudo apt update sudo apt install -y gcc-aarch64-linux-gnu装完之后验证一下版本:
aarch64-linux-gnu-gcc --version正常会输出类似这样的信息:
aarch64-linux-gnu-gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0看到这里,工具链就算装好了。如果你用的是Windows主机,那就没办法直接跑这些命令了——建议装一个WSL的Ubuntu环境,或者用虚拟机,不要在Windows原生环境里折腾aarch64交叉编译,那是一条不归路。我自己也试过用Windows的交叉编译工具,最后都放弃了,老老实实回到WSL里做,省下来的时间够写好几个hello world了。
注意:交叉编译器里有个“前缀”概念。这里的
aarch64-linux-gnu-就是这个工具链的全部程序名前缀,后面接gcc就是C编译器,接g++就是C++编译器,接ld就是链接器。记住这个规律,后面你用aarch64-linux-gnu-gcc、aarch64-linux-gnu-g++就很容易理解了。
2.3 验证工具链能生成ARM程序的绝招:随便编一个空文件
安装完成后,很多人直接开写hello.c。我习惯先做一个极简自检——写一个只有空main函数的文件,试编译,然后立刻用file命令查看产物格式。这一步能帮你从源头切分问题:如果连空main都编不过,那是工具链本身的问题,不用往后面找原因。
// empty.c int main() { return 0; }编译命令:
aarch64-linux-gnu-gcc empty.c -o empty_arm file empty_armfile命令输出里最关键的信息是这一行:
empty_arm: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1看到ARM aarch64这几个字,就说明交叉编译链路基本是通的。如果这里显示的是x86-64,那说明编译器装错了,不是交叉版本;如果显示cannot open output file之类的错误,多半是权限或路径问题,逐个排查即可。
顺便提一句,file命令的输出里有个dynamically linked,这个点后面第5节还要接着讲,先有个印象。
3. 写一个Hello World并交叉编译出ARM版可执行文件:命令背后的门道
3.1 源码准备:这次可以正式一点了
现在正式写hello.c,不过我建议在源码里加一点额外内容,把交叉编译的信息也打印出来,方便你确认跑的确实是ARM版程序,而不是不小心传了个x86版上去。
// hello.c #include <stdio.h> int main() { printf("Hello from RK3588!\n"); printf("Compiled with aarch64-linux-gnu-gcc\n"); return 0; }这个源码很简单,不需要多解释。就是两个printf,后面在板子上跑起来之后,你一眼就能确认程序是否正常工作。
3.2 编译命令的三种形态和各自使用场景
交叉编译hello.c,核心命令就一条:
aarch64-linux-gnu-gcc hello.c -o hello_arm但实际项目中,我通常会根据不同的使用场景,选择不同形态的编译命令:
形态一:动态链接(默认)
aarch64-linux-gnu-gcc hello.c -o hello_arm_dynamic动态链接意味着可执行文件依赖板子上的C运行库(glibc)。优点是文件体积小,hello程序可能只有几KB。缺点是如果目标板子系统库版本和编译主机不一致,运行时可能报错。
形态二:静态链接
aarch64-linux-gnu-gcc hello.c -o hello_arm_static -static-static参数会把所有依赖的库都打包进可执行文件。优点是不依赖板子上的任何动态库,拷过去就能跑。缺点是文件体积膨胀,一个hello可能变成几百KB甚至上MB,而且如果后续要链接OpenCV这种巨型库,静态编译会非常痛苦。
形态三:标准C++程序
如果是C++代码,要用aarch64-linux-gnu-g++来编译,并且链接标准库:
// hello.cpp #include <iostream> int main() { std::cout << "Hello from RK3588 C++!" << std::endl; return 0; }aarch64-linux-gnu-g++ hello.cpp -o hello_cpp_arm到这个系列后面做yolov5s部署时,C++是主角,所以这一步提前把C++的交叉编译链也打通,一点都不亏。
3.3 编译产物检查:file、readelf、ldd三件套
编译完成后,不要急着传板子,先做三个检查。这三板斧能过滤掉80%的部署问题。
检查一:file确认架构
file hello_arm重点看ARM aarch64字样。
检查二:readelf确认入口点和运行需求
readelf -h hello_arm这一段能看到ELF头信息。关注Class: ELF64、Machine: AArch64,这两项是判断ARM64程序的铁证。
检查三:ldd查看动态库依赖
aarch64-linux-gnu-ldd hello_arm注意,这里ldd也要用交叉版本,因为普通的ldd只能解析x86程序。交叉版ldd的输出会列出程序依赖的动态库,对于hello来说,通常只有linux-vdso和libc.so.6。等一下,我在实际操作中遇到过一个问题:aarch64-linux-gnu-ldd在部分Ubuntu版本里名叫ldd,不一定带前缀。你可以用which aarch64-linux-gnu-ldd或者直接试ldd是否能识别ARM文件,反正都是检查工具,不必太纠结名字。
做完这三步,交叉编译的“编译”部分就算大功告成了,接下来是难啃的“部署”环节。
4. 把程序传到香橙派并运行:三种传输方式实测对比
4.1 传输之前,先确认板子上系统架构和运行库版本
传程序之前,在香橙派RK3588的终端里跑两条命令:
uname -m ldd --versionuname -m输出aarch64,说明架构对上了。ldd --version输出的是板子系统自带的glibc版本,记下这个版本号。如果你后续在主机上用较新版本的交叉编译器编译较大的程序,板子上glibc版本太低就会出现version GLIBC_X.XX not found的报错。hello这种小程序一般不会触雷,但记下这个版本号对后面排查问题有奇效。
我自己手头这块香橙派5跑的是官方Ubuntu 22.04系统,glibc是2.35,而宿主机Ubuntu 22.04的glibc也是2.35,版本刚好一致,所以交叉编译的程序直接跑没问题。如果你的板子系统是Debian或者更老的Ubuntu,版本不一致就要小心了。这一条先放着,第五小节会展开讲怎么绕过。
4.2 方法一:scp传输,最稳妥老练的做法
如果板子已经联网并且你通过SSH能登录,优先推荐scp。在主机终端执行:
scp hello_arm orangepi@你的板子IP:/home/orangepi/输入密码,传输完成。scp走的是SSH加密通道,安全且顺手,没有任何额外配置成本。这是我最常用的一种方式,不仅仅因为稳,还因为它不需要装任何新工具。
4.3 方法二:adb push,插线直传
香橙派5系列支持adb,用Type-C数据线把板子和主机连起来后,在主机上执行:
adb push hello_arm /data/local/tmp/注意,/data/local/tmp是Android系路径,但香橙派的Ubuntu系统也保留了adb的默认push路径,实际操作时你可以push到/home/orangepi/。adb的优点是速度比scp快,尤其适合传大文件,而且不需要知道板子IP就能操作,对于刚烧录完系统、还没连WiFi的板子来说格外方便。
4.4 方法三:U盘拷贝,无奈但可靠
如果板子既没联网,你又没有Type-C数据线,那就老老实实用U盘:主机里把hello_arm复制到U盘,插到香橙派上,在板子系统里挂载U盘,再拷贝到用户目录。具体挂载命令因桌面环境不同而异,命令行下一般是lsblk看设备名,然后mount挂载。这种方法慢且麻烦,只作为Plan C存在。
4.5 运行验证:给文件加执行权限再跑
传过去之后,第一次运行前一定要加权限:
chmod +x hello_arm ./hello_arm正常输出:
Hello from RK3588! Compiled with aarch64-linux-gnu-gcc看到这两行,恭喜你,交叉编译的全链路已经彻底跑通了。此时再回头看file hello_arm的输出,你会发现里面的interpreter /lib/ld-linux-aarch64.so.1正是板子系统实际使用的动态链接器路径。这也就解释了为什么交叉编译的程序在板子上能直接运行——只要架构一致、动态链接器路径一致,它就是标准的arm64 Linux程序,跟板子上本地编译出来的程序没有本质区别。
注意:如果你看到的是“Permission denied”,先别慌,这不代表编译有问题,只是没有执行权限。执行
chmod +x即可。如果看到的是“cannot execute binary file”,那才是大问题——要么传错了文件,要么编译产物不是aarch64架构,需要回头重新检查file的输出。
5. 交叉编译的坑:我从RK3588板子上踩出来的经验
5.1 最常见的四类运行错误和定位思路
hello world这么简单,也会有跑不起来的时候吗?有,而且花样还挺多。我把这几个月实际踩过、帮别人排查过的错误分类列在下面:
错误一:cannot execute binary file
提示:cannot execute binary file: Exec format error
这个几乎可以肯定是架构不匹配。用file命令查看提示的二进制文件,如果是x86-64,说明你交叉编译时编译器没用对,或者直接把x86主机上的程序传到了板子上。如果显示的是ARM aarch64但还是报错,那可能是文件系统挂载了noexec选项,换到/home或者/tmp目录再执行试试。
错误二:No such file or directory(注意:文件明明存在)
这个坑非常反直觉。文件就在当前目录,ls也能看到,但执行时就是提示找不到。原因通常是动态链接器不匹配。可执行文件头部指定的解释器路径/lib/ld-linux-aarch64.so.1在板子上不存在,或者板子上运行库路径跟你编译时不一样。有一个快速验证方法——用readelf -l hello_arm | grep interpreter来查看解释器路径,再去板子的/lib目录下看看这个文件是否存在。对于大部分RK3588的Ubuntu系统来说这个路径都在,但如果你用的是精简版系统或者容器环境,就很容易踩到。
错误三:version GLIBC_2.34 not found
这个就是前面说的glibc版本不匹配。宿主机编译器的glibc版本高于板子系统的版本,就会出现这个错误。解法有两条:一是换用更低版本的交叉编译器,让编译产物的glibc版本需求降下来;二是用-static编译,把glibc直接打包进可执行文件,绕开对板子动态库的依赖。hello阶段推荐用-static偷懒,但后面编译OpenCV时就不能这么玩了,到时候得老老实实配一套兼容的工具链。
错误四:Segmentation fault
hello出现段错误的概率极低,一旦出现,先检查是不是编译时用了奇怪的优化参数,比如-march=native,这会把宿主机CPU的指令集特性编译进程序,而RK3588的ARM架构根本不认识这些x86指令。记住交叉编译永远不要用-march=native,要么不指定,要么手动指定为-march=armv8-a。
5.2 为下一个yolov5s部署预埋的准备工作
hello跑通之后,别急着关终端,趁着手感热乎,把下面这几件事也一起做了。它们都是下一阶段编译opencv和yolov5s时一定会用到的能力。
第一件:确认CMake交叉编译工具链文件可用
yolov5s的C++部署绕不开CMake和OpenCV。交叉编译时,CMake需要指定-DCMAKE_TOOLCHAIN_FILE参数,指向一个工具链文件。创建一个aarch64-toolchain.cmake,内容大致如下:
set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)现在可以先不用深入理解每一行,但建议把这个文件保存好,放在你的工作目录里,下一篇教程直接就能用。
第二件:验证C++交叉编译链路
单独写一个用了STL的C++小程序,交叉编译一次再传到板子上跑,确认aarch64-linux-gnu-g++以及C++标准库都工作正常。因为yolov5s相关项目基本都是C++写的,这条链路早晚要用。
第三件:把宿主机和板子的glibc版本对应关系记录到笔记里
养成这个习惯,后面编译任何大项目前先查一下版本兼容性,能省掉非常多瞎折腾的时间。
# 宿主机 aarch64-linux-gnu-gcc -print-file-name=libc.so.6 strings $(aarch64-linux-gnu-gcc -print-file-name=libc.so.6) | grep GLIBC_ | tail # 板子 strings /lib/aarch64-linux-gnu/libc.so.6 | grep GLIBC_ | tail看一眼输出,如果两边的GLIBC版本号没有交集,那就要警惕了。
5.3 静态编译的代价:救急可以,别当日常
前面提到-static能绕开glibc版本问题,但这里要给个忠告:yolov5s部署阶段,静态编译通常不是好选择。原因有二:一是OpenCV等大库静态链接后二进制体积会膨胀到几百MB甚至上GB,传输和更新都非常痛苦;二是很多动态库的许可证或构建方式根本不支持静态链接,强行静态编译会遇到莫名其妙的链接错误。
我个人的经验是,静态编译只适合“快速验证运行库版本匹配问题”的场景,一旦确认是glibc不匹配,正确的做法是调整工具链版本,而不是一刀切-static。
6. 这一篇做完,你的RK3588开发环境到了什么阶段
跑通hello world不是目的,它只是一个信号,告诉你可以放心进入下一阶段了。到这一步,你在香橙派RK3588上的开发环境应该是这样一幅图景:
- 宿主机是x86_64 Linux环境,装好了
gcc-aarch64-linux-gnu交叉编译工具链 - 板子烧好了Ubuntu系统,能通过SSH或者adb访问
- 你掌握了交叉编译生成
ARM aarch64可执行文件的完整流程 - 你知道了从编译到运行之间所有可能出问题的环节以及排查方法
- 你已经备好了CMake工具链文件,为下一步编译OpenCV做好铺垫
这条链路打通之后,后面不管是编译OpenCV、NCNN,还是最终把yolov5s模型跑起来,核心逻辑都是一样的:x86主机交叉编译出arm64的库和程序,传到板子上运行,中间遇到问题就用上一节说的readelf、ldd、file三板斧排查。区别只是工程的规模更大、依赖更多、报错更刁钻而已。
最后再分享一个我自己的习惯:每次交叉编译完一个大项目,我都会在板子上写一个带时间戳的log文件,记录当时的工具链版本、系统版本、关键库版本。很多问题过了一两个月再回头排查,全靠这些当时的记录才能快速定位。玩嵌入式就是这样,链条太长,任何一个环节都可能出问题,留好证据链,比什么都重要。