RK3588交叉编译Hello World:从x86到ARM64的部署基石
发布时间:2026/10/5 9:08:09 锦皓数字建站

按理说第06篇该进入yolov5s模型转换或者NPU推理的正题了但我把节奏停一下先带大家把交叉编译这件事彻底打通。原因是我在这条路上见过太多人不是死在模型转RKNN而是死在最简单的hello程序上。接下来我们要做的事——在x86的PC上编译在香橙派RK3588的ARM64系统上运行——本质上就是从这一个hello开始的。这期不搞虚的直接把这套工具链环境、编译命令、运行验证和踩坑排查全部走一遍保证你看完能自己动手跑通。1. 为什么第06篇要先写一个Hello World1.1 交叉编译在RK3588部署yolov5s里的真实位置很多朋友拿到香橙派RK3588之后第一反应是板子能跑系统了我直接在板子上写代码不就行了。确实板端是完整的Ubuntu装个gcc然后本地编译hello三分钟就能跑起来。但你们有没有想过为什么RK官方提供的NPU推理demo、rknn-toolkit2配套的C接口工程几乎全是让你在PC上先交叉编译、再把产物丢到板子上因为后面的部署链路不是在板子上写一个独立小程序而是PC负责模型转换和业务逻辑开发板子只负责运行推理与IO。拿yolov5s举例。你手上拿到的是yolov5s.pt第一步是在PC上用rknn-toolkit2把它转成RK3588的NPU能识别的.rknn格式第二步是写C/C推理程序调用NPU运行时库librknnmrt.so第三步这个程序是要编译成ARM64架构的二进制放到板子上执行的。第三步就离不开交叉编译。如果你直接ssh到板子上用apt装gcc再编译不是不行。但我实测下来有两个问题一是香橙派5系这类板子的A55小核在执行编译任务时确实比PC慢一个量级尤其是工程一旦带上OpenCV、多源文件编译等待时间非常煎熬二是板端的开发环境装了各种包以后会变得很脏有些编译依赖和运行依赖混在一起后期排查问题会让你怀疑人生。交叉编译的思路说白了就是在性能强、环境干净的PC上把ARM64的二进制造出来板子只负责运行。1.2 一个hello替你验证的底层三件事交叉编译hello不是一个形式主义的仪式它真正验证的是接下来所有C/C工程共用的底层环境。我总结为三件事目标架构是否对编译产物是不是ARM aarch64格式。如果这一步错了后面编出来的yolov5s后处理程序丢到板子上会直接报Exec format error。动态链接器是否存在ARM64 Linux程序需要/lib/ld-linux-aarch64.so.1来加载如果工具链指定的链接器路径和板端系统不一致就会出现明明文件存在却说No such file or directory的经典灵异现象。glibc版本是否兼容工具链编译时依赖的glibc版本不能高于板端系统自带的glibc版本。这是交叉编译最常见的隐性炸弹后面我会专门讲排查方法。这三件事现在用一个hello验证清楚比后续在几百行的推理代码里排查要轻松得多。这也是为什么我坚持从头到脚这个系列在进入yolov5s部署正题之前先插一期hello——它就是一块试金石。2. 装好交叉编译工具链这一步很多人其实是错的2.1 系统自带工具链和厂家SDK工具链怎么选写这段时真想吐槽一句网上很多教程一上来就让你去下载某个厂家交叉编译工具链下载回来是个.tar.gz解压后还要手动配PATH、配CROSS_COMPILE变量一套操作猛如虎结果编出来的hello跑到板子上直接报glibc版本不够。问题根源不是操作不对而是这个工具链往往是针对某个特定系统版本比如老版本Ubuntu、某个Buildroot根文件系统编译的glibc版本和你的香橙派当前系统可能差了十万八千里。我的建议很简单除非你确定板端用的是厂家配套的根文件系统比如某些AIO镜像否则优先用Linux发行版自带的交叉编译工具链。比如PC端是Ubuntu 22.04直接sudo apt update sudo apt install -y gcc-aarch64-linux-gnu g-aarch64-linux-gnu一个关键点如果PC是Ubuntu 22.04apt装到的交叉工具链自带的是glibc 2.35香橙派官方Ubuntu系统22.04版本板端glibc也是2.35这就能保证版本兼容。而某些老教程提供的2018、2019年的工具链glibc还停在2.28左右编译出来的程序虽然也能跑但一旦用了新内核特性或新库函数链接阶段就会出现找不到符号的问题。gcc-aarch64-linux-gnu能做的事情其实不少编译纯C/C的普通程序、静态库、动态库配合g交叉编译器编译C工程后面yolov5s的C推理会用到加上--sysroot参数后还可以指定到某个目标根文件系统的头文件和库进行编译这在实际产品开发中再常用不过。作为一个对比我列一下几种工具链方案的适用场景方案优点缺点适用场景apt安装的aarch64-linux-gnu安装简单、更新跟随系统、版本匹配容易只能针对通用glibc无法定制普通Ubuntu板端系统部署厂家SDK/toolchain包可针对特定根文件系统裁剪库版本统一需要手动配置、版本通常较旧使用厂家自带rootfs或量产固件Buildroot自编译工具链完全可控sdk内部所有库版本一致编译要花几十分钟甚至更久做完整系统镜像和量产开发对咱们这个系列来说香橙派跑的是官方Ubuntu镜像所以apt方案是第一选择省事且风险最低。2.2 装好之后先用三个命令确认工具链可用安装完成后别急着写hello先确认三件事。先看版本号aarch64-linux-gnu-gcc --version aarch64-linux-gnu-g --version再做一个极简的编译测试。这里不需要写任何源文件直接利用gcc从标准输入读代码echo int main(){return 0;} | aarch64-linux-gnu-gcc -x c - -o /tmp/test_arm64 file /tmp/test_arm64如果输出里有ARM aarch64字样就说明工具链本身是可用的。这一步把工具链坏了和你的代码有问题这两个变量分开后面排查起来会省很多时间。提示Windows用户建议用WSL2或者虚拟机装Ubuntu来做交叉编译。后面rknn-toolkit2的模型转换工具、各种构建脚本基本都是以bash环境为基准直接在Windows上操作会白踩一堆坑。我见过有人在Windows下折腾了一整天最后发现是路径反斜杠和bash脚本不兼容。这个系列所有编译和部署操作我都默认在Linux环境里完成你也趁早统一环境。2.3 交叉编译器输出的文件到底长什么样掌握一个习惯每次交叉编译完先对产物执行file命令。这比看任何肉眼判断都靠谱。在PC上跑file hello你会看到类似这样的输出hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]c10e..., for GNU/Linux 3.7.0, not stripped注意关键信息ARM aarch64这是给ARM64平台用的interpreter /lib/ld-linux-aarch64.so.1这是动态链接器路径后面排查找不到文件问题要用dynamically linked动态链接依赖外部.so文件如果你在板子上用本机gcc编译同样一份代码file结果基本长一样。区别只在于编译工具链所处架构不同。交叉编译之所以叫交叉就是因为在x86机器的编译环境里产出了一份arm64架构的二进制。3. 先解决PC和板子路径不一致这个隐形炸弹3.1 为什么我坚持两台设备用相同目录工作说到交叉编译的坑路径不一致是我遇到过的第一大坑比工具链版本问题还阴。动态链接程序里如果编译时用了绝对路径的RPATH/RUNPATH那么程序在板子上运行时会先去那个绝对路径找.so库文件。举个我当年真实踩过的例子我在PC编译一个用了自定义.so的程序编译时链接器的-L参数指向/home/me/project/lib编译链接器把这个路径写进了二进制的RUNPATH如果你没有显式用-rpath某些构建系统也会默默记录它拷到板子上以后板子这个路径根本不存在运行时直接抛error while loading shared libraries。解决办法不是什么高深技巧就是在PC和板子上使用同一个目录路径。我这个系列的工作目录统一为/home/orangepi/rk3588_wsPC上建一份板子上也建一份存放源码、编译产物、模型文件和运行库的位置保持对称。这样即使二进制里残留了编译机的绝对路径在板子上也能找到同样的位置。3.2 两个传输方案scp简单直接NFS一劳永逸把文件从PC弄到板子最直接的是scp。假设板子的IP是192.168.1.100、用户名orangepiscp hello orangepi192.168.1.100:/home/orangepi/rk3588_ws/需要输入密码完事儿。适用在前期验证阶段文件不多、改动不频繁。但后面你会频繁修改源码、反复编译、把新的二进制丢到板子上测试再用scp就有点烦躁了。我强烈建议这时候配一个NFS共享目录把PC上的/home/orangepi/rk3588_ws直接挂载到板子的同一路径下。PC端开启NFS服务假设PC IP为192.168.1.10sudo apt install nfs-kernel-server echo /home/orangepi/rk3588_ws 192.168.1.100(rw,sync,no_subtree_check,no_root_squash) | sudo tee -a /etc/exports sudo exportfs -r板子端挂载sudo mount -t nfs 192.168.1.10:/home/orangepi/rk3588_ws /home/orangepi/rk3588_ws -o nolock挂载之后PC上编译的产物在板子上直接就是同一个文件连scp都不用做。更关键的是后面如果要在板子上调试运行时的.so加载问题NFS目录下调试起来非常舒服改一版跑一下立刻看到结果。注意NFS的no_root_squash加上后要留意板端和PC端的用户ID。如果板端用户ID是1000、PC端用户ID也是1000权限基本不用操心如果对不上编译产物往往是别人的文件板子上执行会报Permission denied。最简单的办法是把两边的普通用户都设成相同uid这在开发阶段能省掉很多权限烂事儿。4. Hello World实战从写代码到板子打印出来4.1 写出一个带体检功能的hello.c普通教程会给你这么一份hello.c#include stdio.h int main(void) { printf(Hello World!\n); return 0; }这当然能跑但我建议你多写两行让它顺带打印出当前系统的glibc版本和架构信息方便后面排查问题#include stdio.h #include gnu/libc-version.h int main(void) { printf(Hello from Orange Pi RK3588!\n); printf(Calling arch: aarch64\n); printf(Runtime glibc: %s\n, gnu_get_libc_version()); return 0; }gnu_get_libc_version()这个函数需要在包含gnu/libc-version.h后才能调用它返回的是板端运行时libc的版本号。不要小看这行输出后面排查glibc兼容问题的时候运行一次这个程序就能立刻确认板端libc版本不用再去翻strings /lib/aarch64-linux-gnu/libc.so.6。4.2 交叉编译动态链接和静态链接怎么选把hello.c放到PC端的/home/orangepi/rk3588_ws/src目录下然后执行交叉编译cd /home/orangepi/rk3588_ws/src aarch64-linux-gnu-gcc hello.c -o ../hello这里我故意用了相对路径把二进制放到和板子一致的工作目录下。编译出来以后先看产物大小ls -lh ../hello动态链接的hello一般只有十几KB因为printf、gnu_get_libc_version这些函数都来自系统动态库。如果你用静态链接aarch64-linux-gnu-gcc hello.c -o ../hello_static -static产物会一下子变成700KB以上因为整个C运行库都被塞进了可执行文件里。动态和静态怎么选我的原则很明确编译方式产物大小运行时依赖适用场景动态链接极小依赖板端系统.so库后续所有正常开发场景静态链接大几乎无外部依赖临时应急、极简环境调试看起来静态链接好像很省心但我必须提醒你后面的RKNPU推理程序一定会依赖librknnmrt.so这样的动态库你没法把它静态塞进去。所以从一开始就要习惯动态链接的方式尽早把板端glibc和工具链的兼容性问题暴露出来。这个hello项目本质就是把动态链接环境先跑通。4.3 上传、赋权、运行三步走在PC上执行scp ../hello orangepi192.168.1.100:/home/orangepi/rk3588_ws/再ssh进板子ssh orangepi192.168.1.100 cd /home/orangepi/rk3588_ws ./hello如果你直接执行报Permission denied看一下权限ls -l hello交叉编译出来的文件默认带rwxr-xr-x权限但scp不会保留可执行位所以经常需要补一刀chmod x hello运行成功后你会看到类似这样的输出Hello from Orange Pi RK3588! Calling arch: aarch64 Runtime glibc: 2.35看到这行输出恭喜你的交叉编译工具链和板端环境的底层兼容性已经完全打通了。接下来编译任何C/C工程底层环境都不用再怀疑。5. 如果你在现场碰了这些钉子运行失败排查手册5.1 No such file or directory其实不是文件不存在这是我在群里回答得最多的一个问题。用户在PC上交叉编译完helloscp到板子执行./hello结果报./hello: No such file or directory他反复确认ls -l hello文件明明在。为什么会报不存在原因大概率是当前被加载的动态链接器路径在板子上不存在或者格式无法识别。之前file输出里的interpreter /lib/ld-linux-aarch64.so.1就是线索。如果你的板端系统使用某种简化rootfs没有这个文件系统内核在启动这个二进制时就会对外表现为找不到文件。先用readelf看一下二进制请求的解释器路径readelf -l hello | grep interpreter输出类似于[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]再到板子上检查这个文件是否存在ls -l /lib/ld-linux-aarch64.so.1如果缺乏的是动态链接器有两种做法一个是从板端系统的软件源补装libc6-arm64-cross或对应基础包另一个更粗暴也最常用的办法是换用和板端系统匹配的工具链重新编译。老工具链指定老路径的情况虽然少见但一旦遇上这个命令能帮你快速锁定。5.2 glibc版本不一致工具链与板端系统的老新问题hello能在PC上正常编译不代表板子一定能运行。如果工具链自带的glibc高于板端系统的glibc程序会在加载阶段报./hello: /lib/aarch64-linux-gnu/libc.so.6: version GLIBC_2.36 not found这种现象在PC apt工具链很新 板端系统偏旧或者老工具链 新板系统的时候都容易出现。排查套路分两步。先看二进制需要的最低glibc版本readelf --version-info hello关注GLIBC_2.xx字段。然后在板子上看实际libc提供的版本strings /lib/aarch64-linux-gnu/libc.so.6 | grep GLIBC_ | tail如果板端的最高版本小于程序要求的最低版本立刻就能确诊。解决的办法按优先级把工具链换成和板端系统相同或更旧版本。最简单的就是检查PC apt源里的编译器版本以及板端系统版本是否一致比如都是Ubuntu 22.04基本不会踩这个雷临时用-static静态编译绕过运行库版本问题但这只是应急方案如果板子非要跑新版库那就得重新做一个系统镜像或者手动升级板端基础库风险较大开发阶段不建议。见过太多人一上来就用某个老教程的2019年工具链编yolov5s的C部署代码然后被一堆GLIBC版本符号折磨。我的建议是先把版本匹配这关过了再谈模型优化。5.3 在板子上用这些命令验证运行环境最后补充几个板端常用的验证命令。注意一个很容易搞混的点ldd是一个shell脚本它的原理是执行目标平台的动态链接器去解析依赖。如果你在PC上对一个ARM64的hello执行ldd hello大概率会得到not a dynamic executable或者直接报错因为PC上的动态链接器是x86的没法正确加载ARM64的二进制。正确的做法是ssh到板子上再对hello执行lddldd hello输出里会列出hello依赖的.so文件路径。比如linux-vdso.so.1 (0x0000xxxx) libc.so.6 /lib/aarch64-linux-gnu/libc.so.6 (0x0000xxxx) /lib/ld-linux-aarch64.so.1 (0x0000xxxx)如果输出里出现not found那就是某个依赖库在板端缺失顺着路径去找就行了。此外我强烈建议在板子上装一个stracesudo apt install strace strace ./hello当程序运行失败时strace会把加载过程的每个系统调用列出来你会看到它卡在哪个openat、哪个execve上比瞎猜高效得多。6. 交叉编译与下一步YOLOv5s部署的衔接6.1 从hello到librknnmrt.so链路怎么走一个hello打通了交叉编译工具链但实际上真正的挑战还在后面。接下来在RK3588上部署yolov5s大致链路是这样的在PC上把yolov5s.pt用rknn-toolkit2转换成.rknn格式模型在PC上用C/C写推理主程序调用NPU runtime库librknnmrt.so头文件包含rknn_api.h链接时加上-lrknnmrt和库路径交叉编译生成ARM64的推理程序连同.rknn模型一起传到板子板端执行程序NPU加载模型完成推理。在这个链路里hello里做过的每一步都会复用交叉编译、动态链接、路径统一、版本排查、ldd/strace验证。尤其librknnmrt.so本身就是ARM64的.so它在板端被加载时的路径、依赖关系、glibc版本要求排查思路和hello一模一样。这也是为什么我一直劝大家别跳过hello直接上模型。6.2 为什么我不建议直接在板子上本地编译推理程序有些朋友可能觉得反正绕来绕去不如直接在板子上g一把梭。我在香橙派上实测过本地编译一个小工程确实可以但代价不小。一来香橙派5系的小核A55在编译时比较慢。你写个hello感觉不出来但工程一变大、模板展开一多CPU占用瞬间拉满风扇呼呼转编译时间成倍增长。二来板子上的开发环境通常装了各种调试工具、Python环境和系统更新包编译器的版本、头文件路径、库路径都可能在某个升级之后悄悄变化导致一个昨天还能编过的工程今天突然编不过。交叉编译把编译环境固化在PC上板子始终保持干净的运行环境这个理念在后期调试模型推理、定位性能问题时特别好用。6.3 给从头到脚系列读者的实战目录建议既然PC端和板端的目录已经统一为/home/orangepi/rk3588_ws建议把这个目录好好规划一下。我目前用的是这样一套结构rk3588_ws/ ├── src/ # 所有源码包括本次hello.c ├── deps/ # 第三方依赖库比如rknn runtime的.h和.so ├── models/ # rknn格式模型文件 ├── deploy/ # 交叉编译后的可执行文件和配套脚本 └── logs/ # 各种运行日志、性能测试记录这个目录结构的好处是源码、依赖、模型、产物分得很清楚后面写部署脚本、做性能对比实验的时候不会出现到处找文件的混乱。交叉编译hello这件事做完了你会发现它小得不能再小但它把整个PC交叉编译到ARM64板子的工作流完整走了一遍。后面的yolov5s部署不管是改模型、调后处理、优化NPU推理都是在这个工作流上加功能而已。工具链和环境稳了剩下的就是模型本身的活了。最后再分享一个小经验在PC上交叉编译成功并不等于万事大吉编译器和它背后的一整套底层环境是PC与板子之间的桥而验证这座桥是否真的通的唯一标准就是那个最简单的hello能否在板子上打印出一行字。这个系列后续每篇文章都会建立在这个已验证过的桥上你把这期扎扎实实走完后面会顺很多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。