RK3588交叉编译入门:Hello World打通YOLOv5s部署第一步 香橙派RK3588的教程写到第六篇终于要聊交叉编译了。前几篇我们把板子点亮、把系统跑起来接下来要干的正事是在RK3588上部署yolov5s。但在这之前有一个必须迈过去的坎交叉编译。很多新手卡在这里不是不会写代码而是不知道为什么要在一台x86电脑上给一块ARM架构的板子编译程序。这一篇我就用最经典的hello world把交叉编译的完整链路走一遍。这次跑通了后面YOLOv5s的依赖库、推理框架全部照着这个路子来。很多人看到“交叉编译hello”觉得太简单实际不是。hello虽然代码只有几行但它涉及工具链、架构、链接、传输、运行权限、动态库依赖每一个环节都是后面编译OpenCV、ncnn、rknn-toolkit的必经之路。这篇文章适合谁适合已经装好Ubuntu系统、手里有香橙派5或任何RK3588开发板想自己动手把YOLOv5s模型部署上去的人。不管你是第一次接触ARM开发还是老手照着这一篇走一遍能少踩一半的坑。1. 为什么这一篇要先解决“交叉编译hello”1.1 板子跑得动但直接在板子上编译并不明智香橙派RK3588的性能在单板机里算很能打的A76大核加A55小核的组合跑Ubuntu桌面、看4K视频都轻轻松松。但如果你直接在这块板子上编译东西体验就完全不一样了。尤其是后续要编译OpenCV、ncnn这类动辄几万个源文件的项目板载编译会吃掉大量内存和CPU时间风扇呼呼转编译一个库可能让你等上半小时甚至更久。交叉编译的思路很简单在性能充足的x86电脑上用一套针对ARM架构的编译器生成可执行文件再把编译好的文件拷贝到板子上运行。这个过程有点像在工厂里用模具把零件批量生产好再运到工地现场组装而不是在工地上现搭一个铸造车间。对YOLOv5s部署来说交叉编译几乎是绕不开的因为你最终要在板子上跑的推理程序大部分依赖库都得先从源码编译成arm64版本。hello world代码量极小却能把交叉编译的主干逻辑完整暴露出来用什么工具链、编译成什么架构、文件怎么传、运行需要什么权限、动态链接库缺了会怎样。把这一套流程跑通后面所有复杂项目的编译只是在这个主干上挂更多依赖而已。1.2 hello是YOLOv5s部署链路的“试金石”接触过RK3588 NPU开发的人都知道YOLOv5s部署通常要走两条路一条是转成rknn格式用瑞芯微的NPU驱动跑这条路依赖rknn-toolkit另一条是直接用ncnn或ONNXRuntime在CPU/GPU上跑这条路依赖ncnn或opencv。无论哪条路你都需要一个完整的arm64编译环境。hello最大的价值在于验证这个环境是否真的可用。我见过太多人在编译YOLOv5s依赖时失败查到最后发现是最开始工具链就装错了或者编译出来的二进制是x86架构的。先花十分钟把hello在板子上跑起来后面编译环节如果出问题至少能排除掉“基础环境没搭好”这个最大的变量。而且hello还能帮你建立一个很重要的概念交叉编译不只是“换个编译器”那么简单它还牵扯到链接器、C库、动态依赖、文件传输、执行权限。这些概念在hello里都缩得很小每个都能看清楚到了大项目里它们就藏在一堆日志背后不好排查了。2. 搭建交叉编译工具链三个方案别选错2.1 先确认你的目标架构是arm64而不是arm32RK3588是64位ARM处理器核心指令集是AArch64在Linux里看到的消息一般是aarch64。这一点必须先确认清楚因为很多人会被“ARM”这个泛称带偏下载了一个32位的arm工具链编译出来的程序放到板子上直接报Exec format error。你在板子上打开终端执行uname -m看到输出aarch64就说明这是64位架构。所以我们的交叉编译器前缀应该是aarch64-linux-gnu-而不是arm-linux-gnueabihf-。后者是给32位ARM用的这俩不能混用。如果是在香橙派RK3588上装的是官方Ubuntu或Debian镜像它的C库是glibc所以工具链选择aarch64-linux-gnu这套是没有问题的。后续YOLOv5s用到的绝大多数开源库依赖的也是glibc。2.2 方案一Ubuntu主机安装现成的跨平台编译器如果你的开发机是Ubuntu或Debian系这是最省事的方式。直接执行sudo apt update sudo apt install -y gcc-aarch64-linux-gnu装完以后你会在/usr/bin/下面看到一堆aarch64-linux-gnu-开头的命令包括aarch64-linux-gnu-gcc aarch64-linux-gnu-g aarch64-linux-gnu-ld aarch64-linux-gnu-objdump验证一下版本aarch64-linux-gnu-gcc -v看到Target: aarch64-linux-gnu就说明工具链正确。这个方案的优点是安装快、命令路径自动配置好适合第一次接触交叉编译的人。缺点是工具链版本跟着Ubuntu仓库走未必是RK3588 SDK里指定的版本后续如果给NPU相关库编译最好再对照官方SDK要求。2.3 方案二使用瑞芯微SDK工具链如果已经在用瑞芯微官方的SDK或者后续要编译一些对工具链版本敏感的程序我更推荐用他们预编译的交叉工具链。常见的有类似gcc-arm-10.3-x86_64-aarch64-none-linux-gnu的包解压后放到一个固定目录比如~/toolchain。解压之后需要把bin目录加入PATHexport PATH$HOME/toolchain/gcc-arm-10.3-x86_64-aarch64-none-linux-gnu/bin:$PATH为了不让每次开终端都手动敲一遍你可以把这行追加到~/.bashrc里echo export PATH$HOME/toolchain/gcc-arm-10.3-x86_64-aarch64-none-linux-gnu/bin:$PATH ~/.bashrc source ~/.bashrc然后用aarch64-none-linux-gnu-gcc -v验证。要注意的是瑞芯微的工具链前缀可能没有linux字段中间的gnu命令名和Ubuntu方案不一样写Makefile的时候需要确认前缀别张冠李戴。2.4 方案三不要一开始就用板子本地编译有些朋友觉得反正RK3588性能不错直接在板子上编译不也照样能跑对hello这类小程序确实可以但你一旦进入YOLOv5s依赖编译阶段就会非常难受。先不说OpenCV的编译可能囤积几十个依赖光是一个cmake配置就要吃掉板子大量内存编译中途卡死是家常便饭。更关键的是RK3588板子上的系统本来就跑着桌面、网络服务、开发调试工具再压上一个重型编译任务很容易把系统拖慢到无法响应。所以交叉编译不是炫技是真的能提升效率、减少瓶颈。2.5 选型建议我的建议是第一次起步用Ubuntu的gcc-aarch64-linux-gnu把hello流程跑通等到需要编译RK3588 NPU相关组件、RKNN SDK里某些自带库时再切换成瑞芯微的工具链。两种工具链可以共存命令前缀不同不会互相覆盖。后续给YOLOv5s编译依赖库也尽量固定用同一套工具链不然会出现A库用GCC 9编译、B库用GCC 12编译链接阶段符号对不上的问题。3. 手写并编译第一个ARM版Hello World3.1 编写一个带点实用信息的hello.c既然是给后续RK3588部署yolov5s打基础我建议hello不要只打印一个字符串顺手把CPU架构和系统信息也带出来这样验证时能看得更直观。用文本编辑器创建hello.c#include stdio.h #include stdlib.h #include sys/utsname.h int main(int argc, char *argv[]) { printf(Hello from Orange Pi RK3588!\n); printf(This program was cross-compiled.\n); struct utsname buf; if (uname(buf) 0) { printf(Machine: %s\n, buf.machine); printf(System: %s %s\n, buf.sysname, buf.release); } printf(argc %d\n, argc); for (int i 0; i argc; i) { printf(argv[%d] %s\n, i, argv[i]); } return 0; }这里通过uname()拿到machine字段如果程序被正确交叉编译成了arm64运行时会输出aarch64。打印argc和argv是为了测试参数传递后面调试程序时经常要用这个能力验证入口逻辑。3.2 编译命令与架构验证在开发机上执行aarch64-linux-gnu-gcc -o hello hello.c这一步默认是动态链接生成的可执行文件依赖arm64版的动态链接库。编译完成后先不要急着传板子先看文件类型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, not stripped看到ARM aarch64和interpreter /lib/ld-linux-aarch64.so.1就说明这一步成功了。如果你不小心用了x86的gcc编译这里会显示x86-64那样传到板子上一定跑不起来。如果想编译一个完全不依赖动态库的程序方便新手排查问题可以使用静态链接aarch64-linux-gnu-gcc -static -o hello_static hello.c静态编译后的hello_static会大不少但好处是拷贝到任意arm64 Linux板子上都可以直接跑不需要关心目标系统C库版本。对只验证烧录、启动这类场景很实用。3.3 顺手做一个可复用的Makefile后续YOLOv5s的依赖库虽然用的是CMake但工程底层的测试小程序用Makefile最直观。新建一个MakefileCROSS_COMPILE ? aarch64-linux-gnu- CC : $(CROSS_COMPILE)gcc CFLAGS : -Wall -O2 all: hello hello: hello.c $(CC) $(CFLAGS) -o $ $ clean: rm -f hello hello_static .PHONY: all clean注意这里定义了CROSS_COMPILE变量默认是aarch64-linux-gnu-。这样以后如果想换成瑞芯微工具链只需要编译时传入make clean make CROSS_COMPILEaarch64-none-linux-gnu-不需要改Makefile。这种方法在嵌入式项目里很常见很多开源库的交叉编译也是这么做的。3.4 用readelf检查内部细节file只能看到外壳如果想确认目标平台细节可以用aarch64-linux-gnu-readelf -h hello | grep Machine输出Machine: AArch64也可以看头部的入口信息和段表。readelf在排查“编译出来的二进制不匹配”这类问题时非常有用建议养成习惯。后续编译YOLOv5s相关依赖时如果出现了SIGILL这类非法指令错误也可以回过来用readelf检查是不是编译时-march参数设得太激进。4. 部署到香橙派并运行验证4.1 把编译好的文件传到板子上开发机和香橙派RK3588网络连通的情况下最直接的方式是用scp。假设板子的IP是192.168.1.100用户名是orangepiscp hello orangepi192.168.1.100:/home/orangepi/第一次连接会提示确认host key输入yes然后输入密码就传过去了。如果你编译了hello_static可以一起传scp hello hello_static orangepi192.168.1.100:/home/orangepi/传完以后用ssh登到板子上ssh orangepi192.168.1.1004.2 运行hello并观察输出在板子终端执行chmod x hello hello_static ./hello如果一切正常你会看到类似输出Hello from Orange Pi RK3588! This program was cross-compiled. Machine: aarch64 System: Linux 5.10.110 argc 1 argv[0] ./hello看到Machine: aarch64就说明程序确实是ARM64代码而且在RK3588上正常加载执行了。这里可以顺手测试一下参数传递./hello a b c输出里能看到argc 4对应的argv参数也按顺序打印出来。这个特性后面做模型推理程序时很有用因为往往需要传入图片路径、权重路径、置信度阈值这些参数。4.3 动态链接依赖检查动态编译的hello在板子上运行成功依赖的C库自然没问题。但为了更清楚它需要哪些.so文件执行ldd hello输出类似linux-vdso.so.1 (0x0000ffff8feb0000) libc.so.6 /lib/aarch64-linux-gnu/libc.so.6 /lib/ld-linux-aarch64.so.1 (0x0000ffff8fe90000)如果ldd输出出现not found说明板子上缺少对应的动态库。hello默认只依赖libc几乎不会缺。但到了YOLOv5s阶段你交叉编译出来的程序可能会依赖libopencv_core.so、libncnn.so这些自定义位置库到时候就要通过LD_LIBRARY_PATH或把库安装到系统路径来解决问题。4.4 顺手把SSH公钥配置好后面做YOLOv5s部署会频繁在开发机和板子之间传文件、跑命令每次输密码很烦。建议现在就配置免密登录。在开发机执行ssh-keygen -t ed25519 ssh-copy-id orangepi192.168.1.100配置好以后再执行scp和ssh就不用输密码了。这一步虽然变通简单但能显著降低后续反复测试时的摩擦成本。5. 从Hello到YOLOv5s搭建部署链路的三条经验5.1 工具链版本要与目标系统的glibc兼容hello能跑通不代表工具链版本就完全没有隐患。RK3588的板子系统如果是较新的Ubuntu 22.04glibc版本通常比较新老工具链編译出来的程序一般也能兼容。反过来如果板子系统很老工具链版本却很新编译出来的程序可能动态链接了一个板上没有的新版GLIBC_XX符号运行时就报version not found。我在实际项目里吃过这个亏。当时给一块RK3588板子编译ncnn用了开发机自带的GCC 12交叉编译板子上的系统还是老版本Ubuntu结果一运行就报GLIBC版本不对。后来改用瑞芯微SDK工具链并且用readelf --version-info检查符号版本才把问题解决。所以hello跑通只代表基本链路OK后续重依赖项目编译前最好确认工具链版本和系统库版本匹配。5.2 静态链接虽省事但YOLOv5s依赖库不能全静态hello可以用-static把libc都编进去但等到了OpenCV、ncnn这种规模全静态编译基本不现实。静态编译会让可执行文件体积暴涨而且部分库比如涉及GPU/NPU驱动后端的本身就不支持纯静态链接。所以你应该尽早习惯动态链接、动态库传输、运行时搜索路径这一套机制。具体到YOLOv5s部署建议把交叉编译好的.so统一放到板子的/usr/local/lib或一个独立目录/opt/arm_libs然后通过环境变量指定。例如在板子的~/.bashrc里加export LD_LIBRARY_PATH/opt/arm_libs:$LD_LIBRARY_PATH这样程序运行时能自动找到依赖的.so文件。5.3 所有代码和依赖库必须用同一套架构这个听起来像废话但实际踩坑的人非常多。最常见的场景是OpenCV用aarch64-linux-gnu-gcc交叉编译好了ncnn却是在x86的PC上本地编译的然后把整个文件夹拷贝到板子链接时符号错乱。或者用cmake配置交叉编译时忘了指定编译器CMake检测到本机gcc生成了一堆x86的.o文件最后链接阶段才报错。要防止这个问题建议在交叉编译每个库之前先检查生成的.so或可执行文件file libopencv_core.so | head -1看到ARM aarch64就继续看到x86-64就停下来查CMake缓存。hello阶段就养成这个检查习惯后面能少走很多弯路。6. 常见问题与排查实录6.1 问题速查表现象常见原因解决办法cannot execute binary file: Exec format error可执行文件是x86_64或armhf不是arm64用file确认架构用aarch64-linux-gnu-gcc重新编译./hello: not found文件存在但动态链接器路径不对或者文件是CRLF换行在开发机用file查看确认是ARM aarch64用readelf -l查interpreter路径Permission denied文件没有执行权限chmod x helloscp: Permission denied目标目录没有写权限传到用户目录如/home/orangepi或者先放到/tmp再sudo mv运行时报GLIBC版本找不到工具链较新目标系统glibc较老使用目标系统对应的旧工具链或升级板子系统ldd显示某个.so not found动态依赖库不在默认路径设置LD_LIBRARY_PATH或把库文件放到/lib/aarch64-linux-gnu/6.2 现场复盘我踩过的三个坑第一个坑是工具链前缀抄错。刚开始我图省事把网上ARM32位教程的arm-linux-gnueabihf-gcc直接拿来编译板子上一运行就报Exec format error。排查了半天最后发现RK3588是aarch64必须用aarch64-linux-gnu-gcc。后来我在Makefile里把CROSS_COMPILE写死成aarch64-linux-gnu-并用uname -m确认板子架构才算根治。第二个坑是动态链接器不对。某个版本的工具链编译出的hello在板子上报not found但文件明明存在。后来用readelf -l hello一看interpreter路径指向/lib/ld-linux-aarch64.so.1而那个板子系统里这个路径确实不存在是SDK带的老rootfs路径不标准。解决方案是改用板子系统自带库路径对应的工具链或者直接换-static先跑通。第三个坑更隐蔽。我在开发机上把代码改完用FTP传hello.c到板子上在板子上用本地gcc编译通过但后来准备交叉编译时又把源码从Windows传到了开发机导致\r\n换行。编译倒是没报错运行起来却总提示./hello: not found。用file看发现是文本格式不对。从那以后我都在Linux开发机里面写代码避免换行符问题。6.3 hello之外的必备技能如果你已经把hello跑通了接下来我强烈建议你顺手做三个小练习。第一把hello.c改成一个简单加法器输入两个整数输出相加结果然后交叉编译到板子上用ssh传参运行熟悉argv解析。第二在hello里用fopen创建一个文件板上运行后检查文件是否生成这能验证板子用户目录的可写权限后面程序要保存推理结果时会用到。第三试试降低编译优化级别、增加-g调试选项生成一个较大的带调试符号的可执行文件并用aarch64-linux-gnu-gdb简单调试为以后调YOLOv5s断点做准备。这三个小练习本质上就是在重复交叉编译的完整闭环只是每次多加一点点复杂度。等这套流程变成肌肉记忆再去看YOLOv5s的模型转换、NPU推理你会觉得顺手很多。最后再分享一个小技巧在PC上把hello编译成静态链接放板子上运行成功之后再换成动态链接用ldd看看依赖这个过程能让你对交叉编译的理解上一个台阶。我的习惯是把工具链装好之后先写一个hello.c存在工程目录里后续每次配置新SDK都先编译一下它确认环境没变再继续。等你在RK3588上把YOLOv5s跑起来回头看就会发现hello这一步真的没有白做。