【手把手从头到脚香橙派RK3588yolov5s教程】06交叉编译hello
聊到在香橙派RK3588上部署YOLOv5s,很多朋友第一步就卡在交叉编译上。“交叉编译hello”这几个字,拆开看每一个都认识,合在一起就让不少新手直接熄火。这一期教程把它一次性讲透:为什么要交叉编译、工具链怎么装、Hello World怎么编出来、怎么传到开发板上跑起来,每一步都有完整命令和踩坑记录。
先说清楚背景。RK3588这颗芯片用的是ARM架构,而我们日常开发用的电脑绝大多数是x86_64架构,两者的指令集完全不一样,在x86电脑上直接编译出来的程序,放到ARM板子上根本没法执行。交叉编译的意思,就是在x86主机上使用专门针对ARM64的工具链,编译出能在香橙派上运行的可执行文件。这一步是后续所有部署工作的地基——后面要交叉编译OpenCV、编译各种依赖库,甚至给YOLOv5s的推理程序做ARM版本,统统绕不开这个能力。
这篇教程适合手上有一块香橙派5(RK3588)、打算在上面跑YOLOv5s,但还没搞定交叉编译环境的朋友。跟着实操一遍之后,你不仅能得到一个在开发板上正常运行的Hello World,更重要的是把整套交叉编译的思路、工具链选型和排错方法都理顺。后面再遇到“编译出来的程序上板跑不了”这种问题,你就知道问题出在哪一层了。
1. 在RK3588上部署YOLOv5s,为什么要先做交叉编译
很多人第一反应是:开发板上又不是不能装编译器,直接把源码拷到板子上编不就行了?理论上的确可以,我给你展开讲讲为什么实操中几乎没人这么做。
1.1 架构差异是一切问题的根源
先补一个最基础的概念。CPU有各自的指令集架构,x86和ARM是两套完全不同的指令集。编译器的作用就是把C代码翻译成CPU能认识的机器指令,x86编译器翻译出来的是x86指令,ARM编译器翻译出来的是ARM指令,两者互不通用。
RK3588是ARM64架构,具体来说是AArch64,也就是64位ARM指令集,内核是四颗Cortex-A76加四颗Cortex-A55。你手里的电脑,只要是近几年的x86_64平台,架构就是AMD64。这里就有个很直白的矛盾:在x86电脑上用系统自带的gcc编译,出来的可执行文件是x86格式的,拿到香橙派上一运行,系统直接认不出来。
这个报错长什么样,我后面会贴出来。很多新手第一次看到“Exec format error”完全懵了——文件明明存在,权限也给了,为什么不能执行?本质就是格式不对,拿一张中文报纸让只会读英文的人看,他当然看不懂。
1.2 交叉编译到底在“交”什么
所谓交叉编译,按教科书说法是“在一个平台上生成另一个平台上可执行代码的过程”。说得再直白点:你的电脑是x86,香橙派是ARM,两边语言不通,交叉编译就是你用一台“翻译机”,在x86电脑上把代码翻译成ARM能执行的机器码。
这台“翻译机”就是交叉编译工具链,核心是交叉编译器。它的名字里的“交叉”体现在哪里?比如你安装好后,会看到一串命令叫aarch64-linux-gnu-gcc,这个名字有三个关键部分:
aarch64表示目标架构是ARM64位;linux表示目标是Linux系统;gnu表示用的是GNU工具链(也就是GCC那一套)。
看明白这个命名规则,你以后再见到arm-linux-gnueabihf-gcc、aarch64-none-linux-gnu-gcc这类工具链,一眼就能读出它是个什么东西——都是交叉编译器,只是目标架构或C库版本不同。
为什么不直接上板编译?理由很实际。香橙派虽然能装gcc,但ARM版的编译速度远不如x86桌面机,而且板子上编译大项目(比如OpenCV)动辄一两个小时,风扇呼呼转;更重要的是,整个YOLOv5s部署流程中,模型转换、预编译依赖库这些环节本来就在x86主机上做,交叉编译能让你在主机上完成全部编译工作,最后只把成品文件拷到板上。这是嵌入式开发的通行做法,不是谁拍脑袋定的规矩。
1.3 这一步在整个部署流程里的位置
我这套教程是按“从头到脚”的顺序推进的,前几期解决了板子到手、系统烧录、远程登录这些准备工作,这一期讲交叉编译,是在为后面的重头戏铺路。
你可以把YOLOv5s在RK3588上的落地流程想象成一条流水线:先用PyTorch训练好YOLOv5s模型,导出成ONNX,再用RKNN-Toolkit在x86电脑上把ONNX转成RK3588 NPU能识别的rknn格式,最后在板子上部署一段推理程序。这段推理程序如果走C/C++路线,大概率需要交叉编译;路径上还要依赖OpenCV等库,这些库在板子上不一定有现成的ARM版本,要么用预编译包,要么自己交叉编译。所以现在把交叉编译跑通,后面每一步都会顺畅得多。
说白了,这期教程的Hello World就是个“环境自检”:如果连一个小小的Hello World都编不出来、跑不起来,那后续编译OpenCV、集成rknn runtime更是无从谈起。把它当成一块试金石,就知道自己的工具链和环境到底通没通。
2. 交叉编译工具链的选型与安装
工具链选不对,后面全是无用功。这一节我把主流方案摆出来,直接告诉你哪个最省事,以及验证工具链是否可用的具体方法。
2.1 三套主流工具链怎么选
目前社区里常用的是下面这三套,我分别说明它们的优缺点:
| 方案 | 来源 | 优点 | 缺点 | 适合人群 |
|---|---|---|---|---|
Ubuntu软件源里的gcc-aarch64-linux-gnu | 系统自带软件源 | 一条apt命令装完,兼容性好 | 版本跟随系统,可能偏旧 | 绝大多数新手,推荐先用这个 |
| Linaro GCC工具链 | Linaro官网下载 | 版本新、优化选项多 | 需要手动配置环境变量 | 需要特定编译器版本或做深度优化 |
| 瑞芯微SDK自带工具链 | 瑞芯微官方SDK | 与SDK配套,贴合RK平台 | 包体大、路径要自行配置 | 直接使用官方SDK开发包的场景 |
我的建议很明确:第一步就用gcc-aarch64-linux-gnu。原因有两个。一是省心,apt会帮你处理依赖,装完就能用,不会出现“下载了工具链却缺库”的情况;二是这套工具链是Debian/Ubuntu官方发布的,质量和兼容性有保障,搞YOLOv5s部署完全够用。等你之后真的碰到性能或版本瓶颈了,再去折腾Linaro也不迟。
2.2 Ubuntu上一条命令安装
我这里默认你是在x86_64的Ubuntu系统上操作,开发板系统则刷好了官方或Debian系的ARM64镜像。执行下面的命令:
sudo apt update sudo apt install -y gcc-aarch64-linux-gnu安装过程会拉取交叉编译器以及配套的binutils、libc6-dev-arm64-cross等辅助工具。装完之后,在终端里检查一下命令是否存在:
which aarch64-linux-gnu-gcc能打印出/usr/bin/aarch64-linux-gnu-gcc这类路径,说明装好了。再看一下版本:
aarch64-linux-gnu-gcc --version输出中会包含 GCC 版本号,比如gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)。不同Ubuntu版本带的GCC版本不一样,不影响Hello World这步,后面如果遇到需要指定GCC版本的场景,再单独处理。
2.3 装完怎么验证可用
光看版本号还不够,我建议做一个更硬核的验证——交叉编译生成一个空程序,然后用file命令检查它的架构类型。这里其实已经在做“预演”了:
echo 'int main(){return 0;}' > /tmp/test.c aarch64-linux-gnu-gcc /tmp/test.c -o /tmp/test_arm file /tmp/test_arm预期输出里会出现关键字段:
/tmp/test_arm: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=..., for GNU/Linux 4.1.0, stripped看到ELF 64-bit LSB pie executable, ARM aarch64这一行,就说明交叉编译器真的在干活了,编译出来的是ARM64格式而不是x86格式。这个file命令是交叉编译里的“照妖镜”,后面每次编译完都跑一下,能避免你把x86产物误当成ARM产物传到板子上,然后又一脸懵地找原因。
提示:如果你在Windows上开发,可以用WSL2运行Ubuntu,或者装个虚拟机。交叉编译本身是Linux生态的玩法,Windows原生环境玩起来非常别扭,别硬刚。
2.4 补充:Linaro工具链与官方SDK工具链
如果之后确实需要换工具链,有一个口碑很好的老牌方案:Linaro提供的预编译GCC工具链,包名类似gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz,下载解压后,把bin目录加进PATH即可:
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH直接进终端敲命令设环境变量重启就失效,建议写进~/.bashrc。另外瑞芯微SDK自带的工具链一般位于SDK的prebuilts目录,路径结构更复杂,适合跟着官方SDK工程走的朋友,这里不展开,遇到再用到时自然就明白了。
3. 手把手写出并编译第一个ARM版Hello World
环境就绪,接下来就是正式的Hello World实操。这一节会细致到每一步命令和每个输出字段,照着做就能跑通。
3.1 源码怎么写才不会被后面的坑埋雷
写一个Hello World,看起来闭着眼都能写,但有几个细节值得注意。先建个项目目录,保持习惯:
mkdir -p ~/cross_hello && cd ~/cross_hello vim hello.c代码内容:
#include <stdio.h> int main(int argc, char *argv[]) { printf("Hello from Orange Pi RK3588!\n"); printf("Cross compile demo: %s\n", argv[0]); return 0; }我故意加了一行打印argv[0],这样你在板子上运行时能直接看到可执行文件的名字,直观感受到“程序真的是从板子上跑起来的”。源码本身没什么特别,但有两个习惯值得从现在就养起来。
第一,main函数建议带参数。嵌入式开发后面经常要用命令行参数控制程序行为,现在把架子搭好,后面扩展直接改就行。第二,记得加return 0;,这是程序正常结束的标志,也能避免一些奇怪的运行时告警。
3.2 编译命令与产物信息检查
编译命令如下:
aarch64-linux-gnu-gcc hello.c -o hello_arm如果没报错,会在当前目录生成一个hello_arm文件。接下来必须做的一件事是检查产物格式:
file hello_arm正常输出:
hello_arm: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=..., for GNU/Linux 4.1.0, not stripped看到ARM aarch64就说明编译目标对了。这份文件的几个关键信息我解释一下,你以后排查问题时经常会用到:
dynamically linked:动态链接,意味着程序运行时依赖共享库;interpreter /lib/ld-linux-aarch64.so.1:指定了动态链接器的路径,这是ARM64系统上的标准路径;not stripped:二进制里还带着符号表,调试用得上,正式发布时可以用strip命令瘦身。
再分享一个进阶检查手法,用readelf看ELF头,能确认更多细节:
aarch64-linux-gnu-readelf -h hello_arm输出里重点看Machine: AArch64这一行,如果显示的是Advanced Micro Devices X86-64,说明你的编译产物根本不是ARM的,赶紧回查工具链。
3.3 动态编译还是静态编译,怎么选
这是交叉编译里最容易踩坑也是引起最多困惑的一个选择。前面那个dynamically linked意味着程序运行时需要到共享库里面找printf等函数的实现。在x86主机上跑没问题,因为主机的动态库齐全;但一旦拷到香橙派上,如果板子里缺少对应的libc.so.6,程序就会罢工。
验证方法很简单,先在电脑上看一下依赖:
aarch64-linux-gnu-readelf -d hello_arm | grep NEEDED你会看到类似libc.so.6这样的共享库条目。这时候就面临一个问题:香橙派系统镜像是否带了这个库?只要你的板子系统是完整的Ubuntu/Debian ARM64镜像,libc.so.6基本都在,动态编译没问题。但如果你用的是一个精简rootfs,或者打算把程序塞进initramfs这类极简环境,那就必须静态编译。
静态编译命令:
aarch64-linux-gnu-gcc -static hello.c -o hello_arm_static file hello_arm_static输出中会出现statically linked字样,说明所有依赖的库都打包进了这个文件,体积会明显变大(从十几KB变成700多KB),但好处是随便拷到哪个ARM64 Linux环境都能跑,不依赖任何共享库。
我的建议是:调试阶段用动态编译,方便定位问题;正式部署到板子上的时候,如果目标环境库齐全,动态没问题,如果不确定,直接用静态版,省心。YOLOv5s部署时涉及大量依赖库,到时候会单独讲每个库的处理方式,但“静态还是动态”这个判断模型,你现在就应该建立起来。
注意:千万别把静态编译理解成“一切都静态就好”。静态版体积大、启动稍慢,有些库也不支持静态链接,后续编译OpenCV时你就体会到了。这里记住“默认动态、必要时静态”的原则就够了。
4. 把Hello World部署到香橙派上跑起来
编译出了ARM64的可执行文件,下一步就是传到板子、运行验证。我实测过几种方式,直接把经验拿来给你参考。
4.1 三种传文件的方式实测对比
先用ifconfig或hostname -I在香橙派上查一下IP地址,假设开发板IP是192.168.31.118,用户名是默认的orangepi。然后选一种方式传文件。
方式一:scp,Linux/macOS和Windows下都能用,命令行解决,我最常用。
scp hello_arm orangepi@192.168.31.118:~/会提示输入密码,默认密码一般是orangepi。传完后文件就在板子的home目录下。
方式二:Windows下用WinSCP或MobaXterm的SFTP面板,图形化操作,双击拖拽就能传。适合不习惯敲命令行的情况。MobaXterm还能顺便当终端用,一举两得。
方式三:U盘拷。适合哈希冲突、网络不通的极端情况,但效率最低,只作为备用方案。
三种方式里,我推荐优先掌握scp,原因很简单:后面反复调库、传模型文件时你会不停地用,而scp一条命令搞定,还能配合脚本自动化。Windows用户装个OpenSSH客户端就有了scp,无脑可用。
4.2 在开发板上运行并确认结果
传完之后,通过SSH登录香橙派:
ssh orangepi@192.168.31.118然后执行:
cd ~ ls -l hello_arm chmod +x hello_arm ./hello_arm如果你之前用的是scp,文件权限一般是-rw-r--r--,没有执行权限,所以必须先chmod +x,不然直接运行会报Permission denied。补上权限后再运行,预期输出:
Hello from Orange Pi RK3588! Cross compile demo: ./hello_arm看到这两行,恭喜你,整条交叉编译链路已经全通了——x86主机上编译,ARM板子上执行,这个“Hello World”证明了你已经具备了后续在香橙派上部署任何原生程序的基础能力。
4.3 别忘了在板上自查架构信息
运行成功后,建议顺手在板子上执行几个自查命令,把“知己知彼”这件事做扎实:
uname -m输出应该是aarch64。再执行:
cat /etc/os-release看看系统版本,记下来,后续装依赖库时,这个系统和架构信息就是你的“配货清单”。最后,如果你想直观感受交叉编译的意义,可以在板子上也跑一次file ~/hello_arm,如果没装file就先apt install file,输出应该和你电脑上看到的一致——都是ARM aarch64的ELF文件。
这里还有个小实验,属于我的课外加分题:在x86主机上随手写个临时文件,编译一个原生程序传上去跑一下,比如:
gcc hello.c -o hello_x86 scp hello_x86 orangepi@192.168.31.118:~/然后在板子上运行./hello_x86,你会看到cannot execute binary file: Exec format error。亲身撞一次这个报错,比看十遍文档都管用,你会从骨子里理解“指令集不通用”到底是什么意思。
5. 交叉编译Hello的常见报错排查实录
这一节内容是很多教程不会写的,但恰恰是新手最需要的。我把实际操作中遇到频率最高的几个报错整理成档,每个都附上排查思路和解决方案。
5.1 Exec format error:十有八九是编错了平台
报错长这样:
-bash: ./hello_x86: cannot execute binary file: Exec format error前面已经演示过如何触发它:把x86编译的二进制拿到ARM板子上执行。解决办法只有一个:用交叉编译器重新编译。如果确定自己用的是aarch64-linux-gnu-gcc但还是遇到这个错,那就要检查工具链是否真的生效了,比如你在Windows上装了某个ARM编译器的exe版本,生成的产物格式可能是MinGW格式而不是真正的Linux ARM ELF,这时候就得回到file命令检查产物。
5.2 “No such file or directory”其实最坑
这个报错最具迷惑性。你明明在执行一个存在且可见的文件:
-bash: ./hello_arm: No such file or directory程序文件就在目录里,ls也能看到,但系统就是“找不到”。这个问题的本质是动态链接器缺失。前面提到interpreter /lib/ld-linux-aarch64.so.1,当程序启动时,系统会先找这个动态链接器,如果板子的系统里没有对应路径或者版本不对,就会抛出这个假象般的错误。
排查方法:
- 在板子上检查动态链接器是否存在:
ls /lib/ld-linux-aarch64.so.1,如果不存在说明系统缺libc; - 解决方式有两种:
apt install libc6-arm64-cross(在板子上重新安装)或者干脆改用静态编译。
你的板子系统如果当初刷的是厂商的完整镜像,一般不会缺这个文件;如果用的是精简rootfs或厂商裁剪过的系统,就容易撞上。遇到这个报错时先冷静,用ls看看/lib下的东西,十有八九能定位问题。
5.3 Permission denied与运行环境依赖
这个就比较好理解了:
-bash: ./hello_arm: Permission denied检查文件权限:
ls -l hello_arm如果权限是-rw-r--r--,缺x权限,执行chmod +x hello_arm即可。这是小问题,但很多人传完文件后忘了这一步,在群里问半天。提前说一句:用scp传过去的文件普遍没有执行权限,养成“传完就 chmod”的习惯。
还有一种情况是文件系统挂载选项导致的,比如挂在noexec的目录下,可执行文件也会被拒绝运行。如果程序放在/data或者SD卡挂载点下遇到权限问题,检查一下挂载参数:
mount | grep noexec把程序挪到home目录下即可绕开。
5.4 一张速查表解决大多数问题
把我这些年积累的交叉编译报错经验整理成对照表,遇到问题直接查:
| 报错信息 | 可能原因 | 排查与解决 |
|---|---|---|
Exec format error | 二进制架构不对 | 用file检查目标文件,确认是ARM aarch64 |
No such file or directory | 动态链接器缺失或损坏 | 检查/lib/ld-linux-aarch64.so.1,必要时静态编译 |
Permission denied | 无执行权限 | chmod +x,或检查挂载选项是否含noexec |
symbol lookup error | 共享库版本不匹配 | 检查LD_LIBRARY_PATH,比对库的版本 |
Segmentation fault | 代码或运行环境问题 | 先用gdb调试,检查内存相关调用 |
编译期大量undefined reference | 头文件或链接库缺失 | 检查依赖库是否安装,链接顺序是否正确 |
这表里后几项在你做YOLOv5s相关编译时会高频出现,现在存个档,到时候翻出来对照就行。
经验:遇到任何交叉编译问题,第一个动作永远是执行
file 可执行文件和readelf -h 可执行文件,把格式、架构、链接方式这三个要素先搞清楚。90%的问题在这个步骤里就暴露了。
6. 交叉编译Hello和YOLOv5s之间还隔着什么
跑通了Hello World后,很多人会问:接下来呢?这一步和最终在RK3588上跑起YOLOv5s,中间还隔着一大段路,我给你把地图画清楚。
6.1 打通这条路的实际意义
先别小看这个Hello World,它验证的不只是“会编译”,而是一整套技术链路的可行性:x86主机上安装交叉工具链、编译出ARM64二进制、通过网络部署到板子、在ARM Linux上正常运行。这个链路就是你后续所有工作的主线,后续做模型推理、图像处理,走的都是同一条路,只是换成了更复杂的源码和库。
具体到YOLOv5s项目,交叉编译Hello World只是开胃菜,后面你至少会遇到这样几个场景。
第一,可能在板子上跑Python版的rknn推理,那就不需要交叉编译太多东西,但你需要确认板子上的Python、numpy、opencv-python这些依赖都装好了,这个反而更简单,直接装预编译包就行。
第二,如果走C/C++推理路线,用rknn-toolkit2在x86主机上把YOLOv5s的ONNX模型转成rknn格式,然后在板子上用librknnrt.so写推理程序。这段推理程序如果是自己C++写的,一般直接在板子上本地编译也行(板子上装了gcc的话),或者交叉编译后传上去。到这一步,你现在掌握的交叉编译能力就派上用场了。
第三,OpenCV是绕不开的硬骨头。YOLOv5s推理前后要做图像缩放、颜色转换、画框,OpenCV几乎是标配。ARM版OpenCV要么从源码在板子上编(慢),要么交叉编译(复杂),要么用预编译包(省心)。我建议先用预编译包,后续再按需深究。
6.2 后续几步怎么规划
按我的经验,接下来的节奏应该是这样安排。
先做模型侧工作:在x86主机上跑通YOLOv5s的推理,导出成ONNX,再用rknn-toolkit2转成rknn格式,在主机上先用模拟器验证模型能正常推理。这一步不涉及开发板,风险低、反馈快。
然后做板侧环境准备:确认香橙派上的系统、Python环境、rknn runtime库都装好,用官方的rknn自带的demo跑通,确保NPU能正常调用。瑞芯微官方提供的rknn_model_zoo里就有YOLOv5相关的demo,先把它跑起来。
最后再做定制化:把自己的模型替换进去,修改预处理和后处理的逻辑,加入自己的业务代码。这时候如果发现官方demo不满足需求,再考虑自己写C++推理程序,就需要组合你现在学的交叉编译、后面的OpenCV交叉编译能力。
这一路走过来,你会发现自己已经不再是“照着教程敲命令”的状态,而是真的理解了嵌入式Linux部署的完整逻辑。交叉编译Hello World就是这个理解过程的第一块基石,看似朴素,实际上撑起了后面所有的复杂工作。
我个人在实际操作中的体会是,交叉编译这件事,难的不是命令本身,而是对“架构”“工具链”“链接方式”这些抽象概念建立直观感知。如果你跟着这篇教程亲手撞过一两次报错,又亲手把问题定位清楚,那这个感知就已经建立起来了。后面碰到再复杂的问题,你都敢动手拆,这比会粘贴几行命令重要得多。