尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RK3588交叉编译实战:从环境搭建到板端部署全流程详解

RK3588交叉编译实战:从环境搭建到板端部署全流程详解 直接做交叉编译这件事说难也难说简单也简单。我刚接触RK3588那会儿也纠结过很久是直接在板子上装编译器编还是在电脑上交叉编译后来项目一多代码一复杂答案就显而易见了——交叉编译是主流也是每个做嵌入式Linux开发的人绕不过去的基本功。这篇博文我就以RK3588为具体目标平台完整走一遍从环境搭建、工具链安装、程序编写到板端部署运行的实操流程。中间也会把我踩过的坑、试错的经验一并写出来希望能帮你少走点弯路。1. 交叉编译思路与工具链选型1.1 为什么非得交叉编译RK3588是一颗ARM架构的SoC指令集是ARMv8-A也就是我们常说的aarch64。而我们日常开发用的笔记本电脑、台式机绝大多数都是x86_64架构。两种架构的CPU指令集完全不同你不可能直接把在电脑上编译好的二进制程序拷贝到板子上运行反过来也一样。这就引出了交叉编译的概念在一种架构的机器上编译出能在另一种架构上运行的程序。对我们来说就是在x86电脑上编译出aarch64架构的ELF可执行文件然后部署到RK3588板子上跑。你可能会问为什么不在板子上装个GCC直接本地编译理论上当然可以实际我也见过有人这么干。但一旦代码量上来差距就非常明显编译速度RK3588的CPU虽然不弱但跟主流x86桌面处理器比还是有差距。一个大型工程板端编译可能要几十分钟甚至几个小时交叉编译可能几分钟就完事了。资源占用编译过程非常吃内存和磁盘IO板子上的存储和内存本来就紧张本地编译很容易把系统拖卡。依赖管理嵌入式板子的rootfs通常很精简装一套完整的编译工具链和开发头文件非常繁琐而且容易把系统的库版本搞乱。所以交叉编译是嵌入式开发的标准姿势。1.2 工具链选型对比市面上针对ARM的交叉编译工具链有好几种我简单梳理一下Linaro GCC工具链老牌的ARM交叉编译工具链提供aarch64-linux-gnu-gcc等工具更新比较活跃。发行版自带工具链比如Ubuntu/Debian源里的gcc-aarch64-linux-gnu包安装方便版本也比较稳定。芯片厂商提供的SDK工具链瑞芯微官方SDK里一般会附带一套预编译好的交叉编译工具链通常放在SDK的prebuilts目录下。我的建议是优先用瑞芯微SDK里配套的工具链。因为厂商在发布BSP的时候会针对自己的系统环境做大量测试用配套工具链编译出来的程序在兼容性上最稳妥。当然如果你只是做一些简单的应用开发不涉及内核和底层驱动Ubuntu源里的aarch64-linux-gnu-gcc也能用我一开始图省事就是这么干的。1.3 选择合适的编译方式关于编译方式我简单说几个概念后面实操会用得到动态编译默认方式编译出的可执行文件体积小但依赖目标板上的动态库.so文件。静态编译加了-static参数后所有依赖都打进了二进制文件里体积大但不需要目标板有额外依赖拷贝过去就能跑。交叉编译根文件系统sysroot指定头文件和库文件的搜索路径。工具链自带的sysroot通常比较干净如果你的程序依赖了一些板子上的第三方库就需要额外指定。我实操中比较常用的方式是动态编译 指定sysroot。这样既能控制二进制体积又能利用板子上已有的系统库。2. 环境搭建与基础验证2.1 准备开发主机先说开发主机的选择。我在Ubuntu 22.04 LTS上做过完整测试Ubuntu 20.04也能用其他Linux发行版大差不差。如果你用的是Windows建议直接装个虚拟机跑Ubuntu或者用WSL2。Mac用户也可以用docker拉一个x86的Ubuntu镜像来做开发环境。这里有个容易让新手困惑的点网上有很多教程提到“在虚拟机里装ARM架构的Ubuntu”这其实是另一条完全不同的路线。虚拟机里的ARM架构Ubuntu和目标板RK3588确实是同架构但它是通过模拟器模拟出来的性能损失严重而且GPU、VPU这些硬件能力也没法模拟并不能真正替代物理开发板。所以别被带偏了交叉编译这条路线才是正解。检查一下你的主机架构uname -m如果输出是x86_64那就对了我们就在这个环境下操作。2.2 安装交叉编译工具链我是用Ubuntu源的方式装的一劳永逸简单省事sudo apt update sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu如果你想用瑞芯微SDK自带的工具链逻辑也差不多。以Rockchip SDK为例通常在prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin目录下找到对应的aarch64-none-linux-gnu-gcc可执行文件然后把这个目录加进PATH环境变量就行。装完后验证一下aarch64-linux-gnu-gcc --version只要能正常输出版本信息工具链就算装好了。2.3 第一个交叉编译程序光装不会用等于白搭我们先写一个经典的Hello World验证整个链路。新建一个hello.c文件#include stdio.h int main() { printf(hello RK3588! 交叉编译环境部署成功\n); return 0; }分别用两种方式编一下对比看效果# 动态编译 aarch64-linux-gnu-gcc hello.c -o hello_dynamic # 静态编译 aarch64-linux-gnu-gcc hello.c -o hello_static -static编完后用file命令检查产物的格式file hello_dynamic hello_static正常情况下你会看到类似下面的输出hello_dynamic: 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 hello_static: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked注意看ARM aarch64这几个字说明二进制目标架构正确。如果在你的x86电脑上直接运行这个程序会报cannot execute binary file的错误这是正常的因为它只能在ARM平台上运行。2.4 配置环境变量的思路如果你觉得每次敲全称太长可以在~/.bashrc里加个别名alias rkgccaarch64-linux-gnu-gcc alias rkgaarch64-linux-gnu-g另外编译常用的参数也值得在环境变量里固化下来。比如优化等级、标准版本这些每次重新敲太累export RK_CFLAGS-O2 -Wall -stdc11然后编译的时候带上aarch64-linux-gnu-gcc $RK_CFLAGS hello.c -o hello_dynamic提示环境变量只在当前终端会话有效如果把命令写进~/.bashrc或~/.profile每次打开新终端都会自动加载。3. 从单文件到工程化项目CMake实战3.1 为什么要用CMake命令行直接敲gcc只适合单文件的小测试。一旦项目有多个源文件、需要链接第三方库、要配置不同的编译选项再靠命令行就很容易出错也很难维护。CMake是目前嵌入式Linux项目里使用最广的构建工具。它本身不直接编译而是生成Makefile或其他构建系统的输入文件由底层的make来真正完成编译。用CMake做交叉编译核心就是写一个工具链文件toolchain file告诉CMake三件事编译器是什么目标系统是什么系统根目录sysroot在哪3.2 编写CMake工具链文件我建了一个完整的工程目录结构如下hello_project/ ├── CMakeLists.txt ├── toolchain-rk3588.cmake └── src/ └── main.ctoolchain-rk3588.cmake的内容是核心我直接贴出来# 指定目标系统 set(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_SYSROOT /usr/aarch64-linux-gnu) # 指定查找库和头文件的路径 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) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)稍微解释一下这几个参数。CMAKE_SYSROOT和CMAKE_FIND_ROOT_PATH指向的是目标板系统的根目录也就是说编译的时候头文件和库文件优先从这里面找而不是从主机系统里找。后面的四个MODE参数也很关键PROGRAM设成NEVER是因为查找主机上运行的程序时不能去目标rootfs里找LIBRARY和INCLUDE设成ONLY是防止误用了主机上x86版本的库和头文件。3.3 CMakeLists.txt配置接下来是CMakeLists.txtcmake_minimum_required(VERSION 3.10) project(hello_rk3588 C) set(CMAKE_C_STANDARD 11) add_executable(hello_rk3588 src/main.c) # 如果你想静态链接可以打开这行 # set_target_properties(hello_rk3588 PROPERTIES LINK_SEARCH_START_STATIC 1)3.4 实际操作流程进入工程目录执行mkdir build cd build cmake .. -DCMAKE_TOOLCHAIN_FILE../toolchain-rk3588.cmake make编译完成后build目录下会出现hello_rk3588可执行文件。老规矩用file验证file hello_rk3588看到ARM aarch64的输出说明CMake交叉编译配置成功。3.5 动态链接与静态链接的实际选择这一步很容易被新手忽略客户端部署的时候才反应过来。我实测过同一个Hello World程序动态编译出来约16KB但需要目标板上带ld-linux-aarch64.so.1几乎所有ARM Linux系统都有。静态编译出来约800KB不管目标板系统多精简都能直接跑。我个人的经验是基础测试程序用动态编译正式项目如果依赖复杂或者目标板系统不确定就考虑静态编译或把动态库一起带过去。如果用了dlopen这类动态加载机制静态编译会失效这种场景就得老老实实做动态链接。4. 程序部署到RK3588板子4.1 部署方式对比编译产物怎么传到板子上方式挺多我按推荐顺序说scp拷贝前提是板子和电脑在同一个局域网板子开启了SSH服务。最常用。NFS网络文件系统挂载适合开发调试阶段直接把电脑上的目录挂载到板子上免去反复拷贝。U盘拷贝适合PC和板子不在同一网络的环境。我日常调试用NFS多一点代码改动后在电脑上重新编译板子上直接就能跑新的版本非常方便。但NFS的坑在于断电或者网络抖动容易出问题所以最终发布版本我还是老老实实scp。4.2 scp部署实操我的板子IP是192.168.1.100用户名是orangepi我用的是Orange Pi 5 Plus也是RK3588芯片部署命令# 推送到板子的家目录 scp hello_rk3588 orangepi192.168.1.100:/home/orangepi/ # 登录板子 ssh orangepi192.168.1.100 # 给执行权限 chmod x hello_rk3588 # 运行 ./hello_rk3588看到hello RK3588! 交叉编译环境部署成功输出整个流程就算全部打通了。4.3 NFS挂载部署实操如果走NFS板子端需要先安装NFS客户端电脑端需要配置NFS服务。电脑端共享目录设为/home/user/rk3588_nfs在/etc/exports中加入/home/user/rk3588_nfs *(rw,sync,no_root_squash,no_subtree_check)然后重启NFS服务sudo exportfs -ra sudo systemctl restart nfs-kernel-server板子端挂载sudo apt install nfs-common sudo mount -t nfs 192.168.1.50:/home/user/rk3588_nfs /mnt/nfs然后就能直接在板子上运行/mnt/nfs目录下的交叉编译产物了。提示NFS调试虽然方便但要注意编译时是否使用了板子上没有的依赖否则跑起来就是error while loading shared libraries。4.4 部署后验证部署成功后我建议多做一步验证——检查运行环境的动态库依赖确保这个程序在任何同架构的板子上都可能正常运行# 在板子上执行 ldd ./hello_rk3588如果输出里都是系统自带的基础库libc.so.6、ld-linux-aarch64.so.1这种那恭喜你程序基本具备通用性了。如果出现了.so文件路径在板子上不存在的提示就要考虑把对应的库一起拷贝过去或者调整sysroot重新编译。5. 常见问题与排查技巧实录这一节是我实际用的过程里踩坑最多的地方整理成表格方便查阅。5.1 典型错误与解决方法错误现象可能原因解决办法cannot execute binary file拿x86主机直接运行ARM程序在板子上运行或者确认file输出是ARM aarch64No such file or directory明明文件存在动态库缺失或者程序是ARM版但解释器路径不对在板子上用ldd检查依赖确认编译时sysroot正确Permission denied可执行权限没设置chmod x或者用sudo运行undefined reference to xxx链接时找不到库检查CMake里target_link_libraries是否写全库路径是否正确cannot find -lxxx交叉编译器找不到指定的库文件确认库的ARM版本存在且路径在CMAKE_FIND_ROOT_PATH范围内编译警告/usr/include/stdio.h: No such file or directory工具链的sysroot没设对确认CMAKE_SYSROOT指向正确的aarch64头文件位置5.2 最常踩的坑库路径混乱说一个我自己的真实案例。我第一次给RK3588交叉编译一个依赖OpenSSL的程序编出来的二进制拉到板子上一运行就报./myapp: error while loading shared libraries: libssl.so.3: cannot open shared object file: No such file or directory我用ldd查了一下程序找的libssl.so.3路径是/lib/aarch64-linux-gnu/libssl.so.3但板子上这个路径根本不存在因为我的板子系统镜像里OpenSSL版本是1.1库文件名是libssl.so.1.1。这个问题根源在于我的开发机上装了x86版的OpenSSL 3.0交叉编译的时候CMake误用了主机的库路径查找规则导致链接了本不应该链接的版本。排查思路是这样先在板子上查清楚系统里到底有哪个版本的库ls /usr/lib/aarch64-linux-gnu/libssl*。回到开发机下载对应版本的ARM OpenSSL库放进sysroot。重新编译的时候显式指定库路径。这个教训让我养成了一个习惯每一次交叉编译之前先确认目标板上有没有所需的动态库版本对不对。否则编译再顺利部署也是白搭。5.3 设置好你的pkg-config如果你交叉编译的程序用到了第三方库比如GTK、OpenCV这些pkg-config用不好也会让你怀疑人生。默认情况下pkg-config在主机上找到的是x86版本库的.pc文件传给gcc的CFLAGS和LIBS都是x86路径和你交叉编译的目标架构完全不搭。解决方式是在工具链环境里另外配置一份交叉编译用的PKG_CONFIG_PATH指向ARM版库的.pc文件所在目录。我一般在环境变量里设置export PKG_CONFIG_PATH/usr/aarch64-linux-gnu/lib/pkgconfig/然后所有用pkg-config的地方都会只从ARM库目录里找依赖信息。提示这个路径取决于你的工具链和库装在哪个目录不同发行版可能不同以自己机器的实际情况为准。6. 进阶Qt与OpenCV的交叉编译思路6.1 Qt 5.15交叉编译要点很多用RK3588做界面应用的人都会遇到Qt交叉编译的问题。Qt本身是个大工程完整编译一次需要挺长时间所以我建议能二进制就二进制能模块化就模块化。如果你非要从源码编译Qt我个人推荐的流程是下载qt-everywhere-src-5.15.x源码包。安装交叉编译依赖libts-dev、libudev-dev等ARM版本。用./configure -platform linux-g -xplatform linux-aarch64-gnu-g配置交叉编译参数。make make install安装到指定前缀目录。部署时把Qt的lib目录一起拷贝到板子上并设置LD_LIBRARY_PATH。这个流程里-xplatform参数是Qt交叉编译的核心它告诉Qt用aarch64的编译器来编译自己。如果你用的是RK3588官方SDK里面通常会预置好适配的Qt版本省去源码编译的痛苦这是我一直推荐用官方SDK的原因之一。6.2 OpenCV的交叉编译核心在RK3588上做视觉项目比如接USB摄像头跑YOLOv8OpenCV基本是标配。交叉编译OpenCV的CMake配置核心是指定工具链文件cmake -DCMAKE_TOOLCHAIN_FILE../toolchain-rk3588.cmake \ -DWITH_GTKOFF \ -DWITH_QTON \ -DBUILD_SHARED_LIBSON \ -DCMAKE_BUILD_TYPERelease ..这里需要注意的是OpenCV默认会开启很多与GUI相关的模块比如GTK、Qt的支持。如果你没有把这些GUI库的ARM版本装好建议先把它们关掉否则CMake配置阶段就会报错。等你后续需要用到再逐步打开一次性全部打开只会让自己陷入依赖泥潭。6.3 RK3588上的AI部署思路说到YOLOv8部署现在RK3588上的主流方案是用瑞芯微的RKNN工具链。这个和前面说的一般交叉编译不太一样特殊在于先在x86电脑上把PyTorch模型转成RKNN格式。RKNN工具链生成的是专门适配RK3588 NPU的模型文件。板端程序通过RKNN Runtime的C API或Python API调用NPU推理。这里只提一个关键点RKNN Runtime的交叉编译并不是简单地把库拷贝过去就行板子上的系统版本、NPU驱动版本、Runtime版本三者必须匹配。我遇到过的84%的部署问题都出在这个版本匹配上强烈建议使用官方提供的完整测试镜像而不是自己拼凑的rootfs。7. 实操心得与最后分享的经验交叉编译这套流程说到底就两句话工具链要匹配依赖要清晰。工具链不匹配编译出来要么跑不了要么跑着跑着崩溃依赖不清晰部署的时候各种cannot open shared object file砸过来头疼得不得了。我个人的习惯是接手一个新项目时先把下面几条理清楚后面就能省掉很多无谓的排查目标板的系统架构是什么aarch64还是armv7别混了。目标板系统的rootfs用的哪个发行版、哪个版本这决定了libc等基础库的ABI兼容性。项目需要哪些第三方库板子系统里有没有、版本对不对。编译工具链用官方SDK自带还是用发行版源里的选定就固定下来别中途换。另外交叉编译的产物和本机编译的产物一定要分开目录管理。我在build目录里习惯再分arm和x86两个子目录避免文件重名覆盖更清晰一些。最后分享一个小技巧如果你经常需要在多个板子之间切换把每块板子的IP、用户名、系统版本、工具链信息整理成一个小的说明文件放在工程根目录时间久了你就知道这个习惯能帮你省多少事。毕竟内存有限好记性不如烂笔头。
返回列表