☰
RK3588交叉编译入门:Hello World打通YOLOv5s部署第一步
2026/10/5 2:57:29 网站建设 项目流程

香橙派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而不是arm32

RK3588是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目录加入PATH:

export 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 World

3.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最直观。新建一个Makefile:

CROSS_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_COMPILE=aarch64-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,用户名是orangepi:

scp hello orangepi@192.168.1.100:/home/orangepi/

第一次连接会提示确认host key,输入yes,然后输入密码就传过去了。如果你编译了hello_static,可以一起传:

scp hello hello_static orangepi@192.168.1.100:/home/orangepi/

传完以后用ssh登到板子上:

ssh orangepi@192.168.1.100

4.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 orangepi@192.168.1.100

配置好以后,再执行scp和ssh就不用输密码了。这一步虽然变通简单,但能显著降低后续反复测试时的摩擦成本。

5. 从Hello到YOLOv5s:搭建部署链路的三条经验

5.1 工具链版本要与目标系统的glibc兼容

hello能跑通,不代表工具链版本就完全没有隐患。RK3588的板子系统如果是较新的Ubuntu 22.04,glibc版本通常比较新,老工具链編译出来的程序一般也能兼容。反过来如果板子系统很老,工具链版本却很新,编译出来的程序可能动态链接了一个板上没有的新版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 hello
scp: 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这一步真的没有白做。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询