ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

交叉编译实战:从hello程序打通RK3588的YOLOv5s部署链路

2026/9/28 1:17:24 拓冰建站 浏览量
交叉编译实战:从hello程序打通RK3588的YOLOv5s部署链路 教程更到第6篇咱们要进入一个很多人一听就头大的话题交叉编译。别急着皱眉——这期的主角只有一个hello程序但你只要把这一个环节彻底跑通后面在香橙派5RK3588上部署yolov5s这条路就算打通了最关键的一环。先交代一下背景前面几篇咱们把Ubuntu 20.04系统烧写进了RK3588通过了SSH登录也做了一些基础配置。当时如果你是在板子上直接敲命令应该能感受到那个速度做小操作没太大问题可一旦要编译稍微大点的项目风扇就开始呼呼转进度条半天不动。这个时候交叉编译就该登场了。所谓交叉编译说白了就是在一台电脑上——通常是你的x86_64架构的PC——编译出另一台机器能直接运行的程序。另一台机器就是我们的ARM64架构的香橙派。编译完的二进制文件通过网络传到板子上加个执行权限直接跑起来就这么简单。但简单背后有几个关键点必须搞清楚工具链怎么选、动态库怎么处理、架构怎么确认。这一篇我全部展开讲。1. 交叉编译这件事先把它彻底看透1.1 为什么非要在PC上编译而不是板子上直接编很多新手第一反应是RK3588这么强8核处理器直接在板子上装个gcc编不就行了我以前也是这么想的后来被现实教育过一次。交叉编译的第一驱动力是编译效率而不是能不能编。RK3588的CPU是4个A76大核加4个A55小核性能绝对不弱。但你想想一个普通的x86 PC编译一个小的C程序只要一两秒换到板子上尤其是要编译OpenCV、FFmpeg、Qt这类大型库的时候时间会拉长到几十分钟甚至几个小时。当年我在板子上交叉编译给树莓派用的Qt 5.12.10赶上过热降频一个晚上就熬在那儿了。PC上跑同样的编译任务给足参数四十分钟一个晚上能跑很多轮上就是几分钟到十几分钟的事这个差距是数量级的。还有一层原因板子上的系统环境通常很精简。Ubuntu镜像虽然带了不少东西但完整编译链、依赖头文件、pkg-config配置往往不全。你为了编译个项目先在板子上装一堆开发包装完了还可能和系统自带的库版本冲突。交叉编译把开发环境隔离在PC端板子只负责运行干净得多。更关键的是后面部署yolov5s时真正的痛点。RK3588跑yolov5s走的是NPU加速路线需要把模型转成RKNN格式还得有运行时库。这个过程中C/C的编译绕不开很多推理框架、图像预处理库、后处理代码都需要先编译成ARM64的二进制。如果没有交叉编译能力这一整条链路是走不下去的。所以hello虽小练的是整套基本功。1.2 交叉编译工具链到底是什么东西可以把交叉编译工具链理解成一个“翻译团队”它的活就是把你的C/C源代码翻译成目标机器能懂的机器码。区别在于普通编译器的翻译目标是你现在这台机器交叉编译器的翻译目标是另一台机器。这个“另一台机器”的特征由一个叫目标三元组的东西描述。咱们手里这套工具链目标三元组是aarch64-linux-gnu。拆开来看很有意义。aarch64指的是ARM 64位架构带AArch64执行状态RK3588运行64位系统时就是aarch64linux表示目标操作系统是Linuxgnu表示这套工具链使用的是GNU C库glibc和Ubuntu系统的ABI是对应的。这个三元组一旦选错后面全是坑。比如你拿了arm-linux-gnueabihf这套专门给ARM 32位用的工具链来编编出来的程序在64位系统上要么跑不了要么运行时崩溃。我见过不少人把树莓派教程里那套arm-linux-gnueabihf直接用在RK3588上结果编译出来的二进制放到板上报错误一查架构armv7差了十万八千里。还有一点值得注意RK3588这颗芯片是ARMv8.2-A架构支持AArch64和AArch32两种执行状态但咱们装的Ubuntu 20.04是64位系统所以一律按aarch64处理。你要是哪天把它折腾成32位系统那才需要考虑armhf的工具链。这个概率很低不用纠结。1.3 RK3588跑yolo到底该选哪套交叉编译方案网上关于RK3588交叉编译的方案五花八门我帮你捋一捋分三类讲清楚。第一类是系统源直接安装的gcc-aarch64-linux-gnu也就是Ubuntu官方源里的交叉编译器。优点是安装简单、依赖自动搞定、和系统自带的glibc版本匹配度高缺点是你没法随意挑选编译器版本。对于咱们部署yolov5s这个场景官方源的版本完全够用稳妥第一。第二类是Linaro工具链这是ARM公司官方推荐的GCC构建版本像野火的RK3568资料里、树莓派交叉编译Qt的教程里经常能看到它的身影。它的优势是版本新、针对ARM优化更激进适合对编译器性能有要求的场景。但解压安装、配置环境变量、处理依赖都比较繁琐新手容易在路径上栽跟头。第三类是Rockchip SDK自带的工具链。如果你用过瑞芯微的SDK里面通常有一整套以arm-rockchip830-linux-uclibcgnueabihf之类命名的编译器这主要用于Rockchip的Buildroot工程。RK3588的SDK里也有对应的aarch64版本但一般捆绑在完整的构建系统里单独拎出来用反而麻烦。我的建议很直接新手先把第一类用熟跑通hello后面有了工程化需求再根据情况切第二类或第三类。这一篇的所有操作我基于的是apt源的gcc-aarch64-linux-gnu实测稳定大家都好复现。2. 搭建交叉编译环境工具链这样装最省心2.1 先确认PC主机环境没跑偏交叉编译的第一步不是装工具而是确认主机是什么系统、什么架构。这一步看起来多余但我真的见过有人在自己32位的旧笔记本上装64位工具链折腾半天全白费。先开一个终端跑一下这个命令uname -m正常的x86_64桌面系统会输出x86_64。如果你的输出是aarch64说明你自己就在一台ARM机器上那你要做的是板端编译而不是交叉编译接下来的操作思路得换一个维度。还有极少数朋友用的是macOS那需要先解决虚拟机或者Docker环境的问题我在教程里就不展开了后面全部基于Ubuntu/Debian系主机。然后确认一下系统版本cat /etc/os-releaseUbuntu 20.04、22.04、Debian 11/12都能流畅支持aarch64交叉编译工具链。如果你用的是CentOS或者Fedora命令会不一样包管理器是yum/dnf本篇不适合你。确认完系统之后顺手把基础工具装上防止后面编译时缺东缺西sudo apt update sudo apt install build-essential cmake git wget filebuild-essential会带来主机端的gcc、g、make等基础编译工具file用来识别二进制文件格式咱们后面要反复用到它。2.2 apt安装gcc-aarch64-linux-gnu全流程环境确认没问题直接上工具链。一条命令搞定sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu我特意把g也一起装了。有些教程只装gcc够用但你后面交叉编译yolov5s相关代码时八成要碰到C项目到时候CMake找aarch64-linux-gnu-g找不到又得回头补装。与其这样不如一次到位。安装完成后验证一下aarch64-linux-gnu-gcc -v正常的输出末尾会显示类似gcc version 9.4.0之类的版本号。如果你是Ubuntu 22.04可能就是gcc version 11.x.x没关系版本差异不影响咱们的hello只影响后面编译大工程时的兼容性细节到时候再单独处理。再列一下工具链全家桶ls /usr/bin/aarch64-linux-gnu-*你会看到一串命令包括编译器、汇编器as、链接器ld、二进制工具objdump、objcopy、readelf、strip等。每一个都有用尤其是readelf和strip后面排查问题和大工程瘦身时要经常用。装完之后不需要配置任何环境变量因为apt装进/usr/bin天然在PATH里。这就是我推荐apt方案的最大原因省心。2.3 apt之外的备用方案与路径管理如果你用的是非Debian系系统或者你就是想用Linaro工具链我简单说说备用方案以防万一。Linaro的aarch64-linux-gnu工具链一般以tar.xz压缩包形式提供下载后解压到某个目录比如/optsudo mkdir -p /opt/linaro sudo tar -xJf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz -C /opt/linaro然后把工具链的bin目录追加到PATHexport PATH/opt/linaro/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH但这样有一个问题export只对当前终端有效。你要是每次新开一个终端都忘了执行后面交叉编译时调不到命令一脸懵。所以我建议把环境变量写入个人配置文件一次性解决问题echo export PATH/opt/linaro/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH ~/.bashrc source ~/.bashrc如果你以后要长期做RK3588开发我强烈建议你建一个env.sh统一管理这类环境变量而不是撒在.bashrc里。等做到了后面的工程化阶段你就知道环境变量这个事有多重要了。最后多说一句不管用哪套工具链都要验证编译器前缀对不对。Linaro的编译器前缀可能不是aarch64-linux-gnu-gcc而是aarch64-linux-gnu-gcc两者命名一致但有些SDK里的是arm-rockchip830-linux-uclibcgnueabihf-gcc这种用在RK3588上纯属找罪受。动手前先敲一下编译器的名字确认它能运行再继续。3. hello实操记录三步跑通交叉编译3.1 写一个带“识别码”的hello.c代码本身很简单但我故意加了一行架构检测逻辑这样在板子上运行的时候能直观地看到执行环境确实变了比单纯打印一句话更有说服力。先建一个工作目录mkdir -p ~/rk3588_work/hello cd ~/rk3588_work/hello然后用你习惯的编辑器写hello.c#include stdio.h #include sys/utsname.h int main() { struct utsname buf; uname(buf); printf(Hello, RK3588! This binary is cross-compiled.\n); printf(Current machine: %s %s\n, buf.sysname, buf.machine); return 0; }这里用uname函数获取系统信息buf.machine字段在64位ARM Linux上返回的是aarch64在x86机器上返回的是x86_64。我们把这个字段直接打印出来传到板子上一跑输出结果会告诉你一切。很多教程的hello都是printf一句完事我不太喜欢那种写法因为没有任何验证逻辑。你这台PC上编译出的程序如果在PC上直接跑它会显示x86_64如果交叉编译后拿到板子上跑它会显示aarch64。这个差异比任何解释都直观。3.2 编译产物体检file和readelf怎么看写完之后交叉编译命令是aarch64-linux-gnu-gcc hello.c -o hello如果你在PC上直接运行./hello会看到权限拒绝或者格式错误这是正常的别慌。PC的x86内核不认识ARM的二进制这是交叉编译的证明而不是失败。关键步骤是体检产物。用file命令看看这个二进制到底是什么file hello期望输出类似hello: 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 3.7.0, not stripped看到ELF 64-bit和ARM aarch64就说明这是给64位ARM机器准备的可执行文件了。这里LSB指的是小端字节序ARM64基本都是小端不用纠结。dynamically linked说明它是动态链接的interpreter那一行标明了动态链接器的路径这个信息在后面排查问题时会非常有价值。再深入一点用readelf看依赖的动态库aarch64-linux-gnu-readelf -d hello | grep NEEDED输出里会有libc.so.6这是最基础的C库依赖。说明程序运行时要靠目标系统提供libc.so.6。咱们的香橙派Ubuntu系统里自带了对应版本的libc所以能直接跑。这一步做完编译阶段就算完工了。3.3 scp传到香橙派上执行接下来把hello传送到板子上。首先要确定你在前面几篇里已经配置好了SSH网络知道板子的IP。假设板子用户名是orangepiIP是192.168.1.100那命令就是scp hello orangepi192.168.1.100:/home/orangepi/如果你之前配置过SSH免密登录这一步会秒传如果还没配就输一次密码。传完登录板子ssh orangepi192.168.1.100然后在板子上给执行权限并运行chmod x hello ./hello板上输出Hello, RK3588! This binary is cross-compiled. Current machine: Linux aarch64看到aarch64这三个字母你这套交叉编译链路就彻底闭环了在x86 PC上写的代码、编的产物在ARM64的RK3588板子上成功运行。这个成就感只有亲手试过的人才知道有多实在。顺带一提如果你在板上也想确认系统架构可以跑uname -m输出同样是aarch64。3.4 动态链接和静态链接都试一遍很多教程教到这里就结束了但我要多说一步再编译一个静态链接的版本你会对动态库的依赖机制理解得更透彻。默认情况下gcc编译出来的是动态链接程序运行时依赖目标系统上的共享库。这在板子上跑没问题因为Ubuntu系统里都有但如果你以后要交叉编译一个程序然后扔到一个精简的嵌入式系统里那系统里可能没有你要的库程序就跑不起来。静态链接会把所有需要的库代码直接打包进二进制文件里程序独立运行不依赖目标系统的库。编译命令就多了个-static参数aarch64-linux-gnu-gcc -static hello.c -o hello_static同样用file看一下file hello_static注意输出的区别静态版本不再有dynamically linked字样而是显示statically linked。再看看文件体积ls -lh hello hello_static动态版通常只有几KB到十几KB静态版会达到几百KB甚至更多因为libc被整个打包进来了。把静态版也传到板子上跑一遍scp hello_static orangepi192.168.1.100:/home/orangepi/两相对比你就明白动态链接和静态链接的权衡了动态版体积小但依赖系统库静态版体积大但去哪都能跑。在后续yolov5s部署里如果编译出来的程序因为缺少动态库而报错静态编译往往是应急的好办法。4. 踩坑实录交叉编译最常见的5个问题4.1 报错cannot execute binary file这个报错一般出现在两种场景。第一种是你用gcc而不是aarch64-linux-gnu-gcc编译导致产物是x86_64的二进制传到板子上自然跑不了。解决办法很简单重新用交叉编译器编译别偷懒。第二种是忘了加执行权限。文件确实传上去了架构也对了但没有chmod x一运行就报这个错。有些新手会在Windows上改文件、传到Linux后发现权限丢了这时候一切换到板子上就得记得补权限。判断方法是先在PC上跑file确认架构再到板子上ls -l确认权限位有没有x。两步都对了基本就能跑。4.2 报错error while loading shared libraries这个报错是动态链接程序最典型的坑。你在板子上运行交叉编译的二进制系统提示找不到某个.so文件比如./hello: error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory原因就是程序依赖的某个共享库在目标系统里不存在。hello程序只依赖libc.so.6Ubuntu系统自带所以不会出问题但你以后编译的程序如果依赖libopencv_core.so这类第三方库而板子上没有装对应版本就会报这个错。解决思路有三个一是把对应库的交叉编译版本一起传到板上并通过LD_LIBRARY_PATH指定路径二是在板子上apt安装对应的库三是改用静态编译把依赖打进去。具体用哪个看你的部署环境允许哪种方式。4.3 报错No such file or directory这个报错特别具有迷惑性。你明明看到文件就在当前目录ls也能列出来但一运行就提示No such file or directory。我第一次遇到时排查了半小时一度以为是系统幻觉。真正的原因是程序内置的动态链接器路径不对。动态链接程序内部写死了一个interpreter路径比如/lib/ld-linux-aarch64.so.1如果这个路径在目标系统里不存在内核在加载程序时会报“找不到文件”而不是报哪个库缺失。解决办法的优先级是先检查程序和系统的架构是否完全匹配再确认glibc版本兼容性最后实在不行就静态编译。hello阶段一般不会触发这个问题但当你以后用更高版本的GCC去编译、放到老系统的板子上运行时这个坑一定会遇到。提前有个印象到时候不慌。4.4 板子磁盘突然满了怎么办这个问题和交叉编译本身关系不大但却是RK3588烧写系统后几乎必踩的坑我在这里一并解决。烧写Ubuntu镜像到开发板后打开系统一看根分区空间常常小得离谱或者默认只占用了TF卡/eMMC的一部分容量剩余空间没有被分配。这是因为官方镜像的分区布局是固定的系统烧进去后并不会自动扩展根分区到整个存储介质。你如果先跑df -h看看大概率会发现自己明明用了64G的卡根分区却只有十几G甚至几G。解决方法是使用resize2fs在线扩展根分区。先确认设备名一般eMMC是mmcblk0TF卡是mmcblk1。执行sudo resize2fs /dev/mmcblk1p2注意p2这个分区号得先看fdisk -l确认别想当然。扩展完再df -h检查空间就正常了。这个操作相当于给系统“解锁”了全部存储空间建议烧写完系统后第一时间做。4.5 问题速查对照表我把这些经典问题整理成一张表方便你以后遇到时直接查现象大概率原因快速处理办法cannot execute binary file交叉编译架构不对或没加执行权限file检查架构、chmod xerror while loading shared libraries缺动态库或库版本不匹配补齐库、设置LD_LIBRARY_PATH或改静态编译No such file or directory动态链接器路径不存在检查glibc兼容性、必要时静态编译传完文件后运行报权限错误Windows/SSH传输丢了可执行位chmod x 文件Text file busy程序正在运行覆盖了二进制文件先结束进程再替换文件Command not foundPATH里没有该命令或环境变量未配置检查PATH、source环境脚本这张表我建议截图存一下后面编译opencv、ncnn、rknn相关代码的时候百分之百会碰到上面至少两个问题。5. 从hello到yolov5s交叉编译能力怎么延续5.1 YOLOv5s部署为什么绕不开交叉编译你可能有一个疑问教程标题写着yolov5s为什么大费周章地先搞hello因为yolov5s在RK3588上的部署链路交叉编译是核心支撑。RK3588跑yolov5s最佳路径是用NPU加速官方提供了RKNN-Toolkit2工具链把训练好的PyTorch模型转换成RKNN格式。模型转换可以在x86主机上完成这个过程不涉及交叉编译。但到了板端推理阶段你要在C/C环境下调用RKNN运行时API读取摄像头图像、缩放到模型输入尺寸、执行推理、处理输出结果这一整套代码要么在板上现场编译要么交叉编译后上传。在板上现场编译流程很痛苦。你要先在板子上安装gcc、cmake、opencv依赖然后编译一个调用RKNN API的程序。板子的运算能力虽然不错但和PC相比编译时间还是要长不少。交叉编译则可以让你秒级迭代代码改一次代码交叉编译只需要几秒到几十秒scp上传板上运行。另外还有一个更实际的原因你后面如果想用yolov8替换yolov5s模型转换和推理代码都会变但交叉编译这个基本功不会变。把hello阶段练扎实了以后只是换不同项目、不同库的编译思路完全一致。5.2 交叉编译的工作目录与CMake工具链准备hello阶段的代码只有一个文件无所谓工程管理。但你马上要迎接的yolov5s部署会涉及很多源文件、第三方头文件、库文件这个时候就要用CMake管理工程了。我给一个交叉编译的CMake工具链文件模板你保存成一个文件比如aarch64-toolchain.cmakeset(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)使用它编译项目时在构建目录里指定工具链文件cmake -DCMAKE_TOOLCHAIN_FILE../aarch64-toolchain.cmake .. makeCMAKE_FIND_ROOT_PATH这一行要注意它告诉CMake在寻找依赖库和头文件时只搜索交叉编译环境的目录而不会错误地找到PC宿主机的x86库。如果你不加这个限制CMake很可能把x86的OpenCV库链进去编译出来的程序到板子上直接崩溃。工作目录方面我建议后面所有交叉编译项目都放在~/rk3588_work下每个项目一个目录交叉编译的产物单独放一个bin子目录这样传文件时不会手忙脚乱。5.3 下一个目标从单个文件到工程化编译hello是一个里程碑但从hello到yolov5s之间还差几步很关键的能力升级。第一步是学会交叉编译一个带多个源文件的C/C工程至少你得会写一个简单的CMakeLists.txt把多个.c文件编成一个可执行文件。这一步用来模拟yolov5s部署时的工程结构。第二步是学会交叉编译第三方依赖库。yolov5s部署基本逃不开OpenCV而用apt在板上装的OpenCV往往是x86派发的ARM版或者不满足需求的版本。自己交叉编译OpenCV虽然耗时但能精确控制版本和功能模块。这一步交叉编译的功夫就体现出来了。第三步是学会将板端运行时报错和交叉编译参数关联起来分析。比如你在板上运行程序时显示缺少某个库你得能反向排查出是哪个编译环节没有把库链接进去从而调整CMake配置。从hello到这些能力每步之间都有大量细节但方法论是共通的先在PC上交叉编译用file和readelf检查产物再上传板子运行遇到报错就按速查表逐项排查。这个过程多走几轮你对交叉编译的理解会比看一百篇文档都深。我自己走了很多弯路才总结出这条路。有一段时间我习惯在板子上现编现测觉得交叉编译多一道传输步骤很麻烦。直到有一次要把一个带深度学习推理的完整工程部署到RK3588上在板子上编译花了一个多小时还因为内存不足直接卡死。后来用交叉编译同样的工程五分钟左右就出产物之后我再没在板子上编译过大型程序。建议你也把hello这个流程完整吃透不要只跑一遍就翻篇。手动编译一遍再用CMake走一遍再把动态、静态两种方式各试一次。这几个动作做完你对交叉编译的掌握程度就已经足够支撑接下来的yolov5s实战了。到时候你会发现RK3588部署yolo这个目标其实并没有想象中那么难。