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

资讯详情

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

RK3588交叉编译实战:从hello world到YOLOv5s环境搭建

RK3588交叉编译实战:从hello world到YOLOv5s环境搭建 1. 为什么交叉编译hello是RK3588开发绕不开的第一道坎很多人拿到香橙派5之后第一反应是插电、烧系统、接屏幕然后在板子上直接写代码编译。这么做在PC上没问题放到嵌入式板子上就是另一回事了。香橙派5搭载的RK3588是一颗8核ARM64处理器4个Cortex-A76大核加4个Cortex-A55小核性能确实强但它的存储空间、内存资源、散热条件跟一台正常的开发主机没法比。你不可能在板子上装一整套GCC工具链、头文件、CMake、各种依赖库然后编译一个稍微大一点的项目——编译到一半磁盘满了、温度飙到降频、链接阶段内存不够被OOM杀掉这些都是家常便饭。所以嵌入式开发的标准做法是在x86_64的PC上编译出ARM64能跑的可执行文件再传到板子上运行。这个在A平台上编译出B平台能执行的程序的过程就叫交叉编译。而hello world是验证整条工具链是否打通的最小闭环——它不涉及任何业务逻辑只验证编译器能不能用、链接器能不能找到正确的库、生成的可执行文件架构对不对、传到板子上能不能跑起来。这一步跑通了后面编译YOLOv5s的推理程序、编译OpenCV、编译FFmpeg才有意义。我见过太多人卡在这一步工具链装了三遍环境变量配了又改编译出来的文件一放到板子上就报cannot execute binary file: Exec format error或者No such file or directory——明明文件就在那里。这些问题的根因往往不是工具链本身有问题而是架构没对上、动态链接器路径不对、或者用了错误的编译选项。这篇就把交叉编译hello的完整链路拆开讲清楚从工具链选型到验证方法每一步都说明白为什么这么做。提示交叉编译的核心不是编译这个动作而是让编译产物在目标平台上正确加载和运行。理解这一点后面所有报错你都能自己定位。2. 工具链选型为什么我最终用了官方推荐的aarch64-none-linux-gnu2.1 三种常见工具链的取舍给RK3588做交叉编译市面上能用的工具链大致分三类我逐个说下实际体验。第一类是Linaro官方发布的aarch64工具链比如gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu。这类工具链历史悠久、资料多但版本偏老对C17/20的支持不完整。如果你后面要编译YOLOv5s的推理代码里面用到一些较新的C特性老版本GCC会直接报语法错误。我早期用7.5.0编译一个用了std::filesystem的demo直接编译不过换成8.x才解决。第二类是ARM官方维护的GNU Toolchain也就是arm-gnu-toolchain-xx.x-x86_64-aarch64-none-linux-gnu。这是目前最推荐的选择版本更新及时对C标准支持好而且命名规范清晰。aarch64-none-linux-gnu这个target triple的含义是aarch64架构、none表示不绑定特定厂商、linux表示目标操作系统、gnu表示使用glibc。香橙派5跑的是Ubuntu 20.04glibc 2.31用这个工具链编译出来的程序能直接跑。第三类是Buildroot或Yocto生成的工具链。如果你是用Buildroot自己构建的根文件系统那工具链必须用Buildroot配套生成的那个因为它的sysroot里包含了目标系统所有的库和头文件。用别的工具链编译出来的程序很可能因为glibc版本不匹配而跑不起来。但如果你用的是香橙派官方提供的Ubuntu镜像那就用ARM官方工具链即可。我最终选的是ARM GNU Toolchain 10.3或11.3版本原因是版本足够新能支持现代C同时glibc版本2.33/2.35跟Ubuntu 20.04的2.31兼容——注意这里有个坑工具链的glibc版本不能高于目标系统的glibc版本否则编译出来的程序在板子上会因为找不到对应版本的符号而报错。10.3和11.3的glibc分别是2.33和2.35理论上高于2.31但实际测试中只要不调用新版本特有的符号程序能正常运行。如果你追求绝对稳妥可以用9.2版本它的glibc是2.31跟Ubuntu 20.04完全一致。2.2 下载与目录规划工具链不要随便解压到桌面或者下载目录后面环境变量配起来会乱。我习惯在/opt下建一个专门的目录sudo mkdir -p /opt/toolchain cd /opt/toolchain # 假设你已经下载了arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz sudo tar -xf arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz解压后目录结构是这样的/opt/toolchain/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/ ├── bin/ │ ├── aarch64-none-linux-gnu-gcc │ ├── aarch64-none-linux-gnu-g │ ├── aarch64-none-linux-gnu-ld │ └── ... ├── lib/ ├── include/ └── aarch64-none-linux-gnu/ └── libc/ # 这就是sysrootbin目录下是各种工具aarch64-none-linux-gnu/libc是sysroot里面包含了目标平台的libc、头文件等。交叉编译时编译器会默认去这个sysroot里找头文件和库而不是去你PC的/usr/include找——这一点很关键很多人编译报找不到xxx.h就是因为编译器错误地用了主机的头文件。2.3 环境变量配置的两种方式配置环境变量有两种方式我推荐第二种。第一种是临时生效只在当前终端有效export PATH/opt/toolchain/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin:$PATH第二种是写进~/.bashrc永久生效echo export PATH/opt/toolchain/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin:$PATH ~/.bashrc source ~/.bashrc配完之后验证一下aarch64-none-linux-gnu-gcc -v如果输出了gcc版本信息说明PATH配对了。如果提示command not found检查路径拼写特别是版本号那一段很容易打错。注意不要用export CROSS_COMPILEaarch64-none-linux-gnu-这种方式来配那个是给内核编译用的用户态程序编译直接靠PATH找工具就行。3. 从hello.c到可执行文件每一步到底发生了什么3.1 写一个不那么简单的hello很多人写hello就是printf(hello\n)但这样验证不出什么东西。我建议写一个稍微带点信息的版本把编译时间、架构信息都打出来#include stdio.h int main(void) { printf(Hello from RK3588 cross compile!\n); printf(Compiled on: %s %s\n, __DATE__, __TIME__); printf(Pointer size: %zu bytes\n, sizeof(void*)); return 0; }sizeof(void*)在64位ARM上是8在32位ARM上是4。如果编译出来的程序在板子上打印出8说明确实是64位程序如果打印出4那说明工具链用错了编成了32位。这是一个非常直观的验证手段。3.2 编译命令的每个参数都值得说清楚最基础的编译命令是aarch64-none-linux-gnu-gcc hello.c -o hello但实际项目中我建议加上几个参数让编译过程更可控aarch64-none-linux-gnu-gcc hello.c -o hello -static -O2 -Wall逐个解释-static静态链接。这个参数在交叉编译hello阶段特别有用因为它把libc也打包进可执行文件板子上不需要有对应的动态库就能跑。缺点是文件体积大几MB优点是绝对不会出现动态链接器找不到的问题。等你验证完工具链没问题再改成动态链接去验证库路径。-O2优化级别。hello用不上但养成习惯后面编译YOLOv5s推理代码时优化级别直接影响推理速度。-Wall打开所有警告。交叉编译时警告往往能提前暴露架构相关的问题比如指针截断、类型不匹配。编译完成后用file命令检查产物架构file hello正确输出应该是hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, for GNU/Linux 3.7.0, not stripped如果看到的是x86-64说明你用了主机的gcc而不是交叉编译器如果看到的是ARM, EABI532位说明工具链选错了。这一步是交叉编译验证的第一道关卡必须确认架构是ARM aarch64。3.3 动态链接版本验证sysroot是否正确静态版本跑通后再试动态链接aarch64-none-linux-gnu-gcc hello.c -o hello_dynamic -O2 -Wall用readelf看它依赖哪些动态库aarch64-none-linux-gnu-readelf -d hello_dynamic | grep NEEDED输出大概是0x0000000000000001 (NEEDED) Shared library: [libc.so.6]再看它指定的动态链接器路径aarch64-none-linux-gnu-readelf -l hello_dynamic | grep interpreter输出[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]这个路径/lib/ld-linux-aarch64.so.1是目标板上的路径不是PC上的。板子的Ubuntu系统里必须有这个文件否则程序跑不起来。香橙派官方的Ubuntu镜像里默认就有所以一般不用担心。但如果你是自己构建的根文件系统就要确认这个文件存在。提示如果板子上报No such file or directory但文件明明存在99%是动态链接器路径不对。用readelf -l看interpreter那一行然后去板子上确认那个路径下的文件是否存在。4. 把hello传到板子上几种传输方式的实测对比4.1 scp最通用但有前提最常用的方式是scpscp hello orangepi192.168.1.100:/home/orangepi/前提是板子已经联网而且开了SSH服务。香橙派官方的Ubuntu镜像默认装了openssh-server开机就能用。默认用户名密码一般是orangepi/orangepi。scp的优点是简单直接缺点是依赖网络。如果板子还没配好网络或者你在一个没有路由器的环境里scp就用不了。4.2 U盘最土但最可靠我调试初期最常用的其实是U盘。把编译好的hello拷到U盘插到板子上mount一下cp过去。听起来很原始但它不依赖任何网络配置板子只要能启动就能用。# 在板子上操作 lsblk # 找到U盘设备名一般是sda1 sudo mount /dev/sda1 /mnt cp /mnt/hello ~/ sudo umount /mntU盘的坑在于文件系统格式。如果U盘是NTFS格式Ubuntu默认可能不带ntfs-3g驱动mount会失败。用FAT32或ext4格式最稳妥。另外从U盘拷过来的文件可执行权限会丢失需要手动加chmod x hello4.3 ADB如果你烧的是Android系统有些人的RK3588板子烧的是Android而不是Ubuntu那就用adbadb push hello /data/local/tmp/ adb shell chmod x /data/local/tmp/hello adb shell /data/local/tmp/hello但要注意Android的libc是bionic而不是glibc用aarch64-none-linux-gnu工具链编译出来的程序在Android上跑不了。Android要用NDK的工具链这是另一套东西。这篇讲的是Linux环境Android的情况不展开。4.4 传输方式对比方式前提条件优点缺点scp板子联网SSH快、可脚本化依赖网络U盘无不依赖网络手动操作、权限丢失ADBAndroid系统集成度高仅限AndroidNFS挂载主机开NFS服务实时同步配置复杂我个人的习惯是调试阶段用U盘稳定后用scp。因为调试阶段网络配置经常变U盘最省心。5. 板子上跑不起来按这个顺序排查5.1 第一层文件能不能执行chmod x hello ./hello如果报Permission denied就是没加执行权限。如果报cannot execute binary file: Exec format error说明架构不对回到第3步用file命令检查。5.2 第二层动态链接器找不找得到如果报No such file or directory但ls -l hello明明能看到文件那就是动态链接器的问题。用readelf -l hello | grep interpreter看它要哪个链接器然后去板子上确认ls -l /lib/ld-linux-aarch64.so.1如果这个文件不存在要么换成静态编译加-static要么在板子上装对应的libc包。5.3 第三层依赖库版本对不对如果报version GLIBC_2.34 not found说明你编译时用的glibc版本高于板子上的。解决办法有两个换用更低版本的交叉工具链或者在板子上升级glibc不推荐容易搞崩系统。查看板子glibc版本ldd --version查看编译产物需要的glibc版本aarch64-none-linux-gnu-readelf -V hello | grep GLIBC两边对比一下编译产物要求的版本不能高于板子提供的版本。5.4 第四层CPU特性对不对RK3588是ARMv8.2-A架构支持一些较新的指令。如果你编译时用了-marcharmv8.2-a但板子的内核没开对应的支持可能会报Illegal instruction。交叉编译hello阶段不要加-march参数用默认的就行。等确认工具链没问题了再针对RK3588做指令集优化。注意Illegal instruction这个报错很隐蔽因为程序能启动只是在执行到某条特定指令时才崩。排查方法是把优化级别降到-O0看是否还崩。如果-O0不崩、-O2崩基本就是指令集的问题。6. 从hello延伸到YOLOv5s交叉编译环境的复用6.1 为什么hello跑通了YOLOv5s还不一定能编译hello只依赖libc而YOLOv5s的推理程序依赖OpenCV、可能还依赖RKNN SDK、pthread、数学库等等。hello跑通只证明工具链本身没问题不证明所有依赖库都能找到。编译YOLOv5s推理程序时最常见的报错是fatal error: opencv2/opencv.hpp: No such file or directory。这是因为交叉编译器的sysroot里没有OpenCV的头文件。解决办法是自己交叉编译OpenCV然后把它安装到sysroot里或者用-I和-L参数手动指定头文件和库的路径。6.2 sysroot的扩展方法假设你把OpenCV交叉编译后安装到了/opt/rk3588/opencv编译YOLOv5s程序时可以这样指定aarch64-none-linux-gnu-g yolov5_demo.cpp -o yolov5_demo \ -I/opt/rk3588/opencv/include \ -L/opt/rk3588/opencv/lib \ -lopencv_core -lopencv_imgproc -lopencv_dnn \ -Wl,-rpath-link/opt/rk3588/opencv/lib-Wl,-rpath-link这个参数很关键它告诉链接器在链接阶段去哪里找依赖库。不加的话链接时可能报undefined reference。6.3 把常用路径写进环境变量每次编译都敲一长串-I和-L太累可以写进环境变量export CPLUS_INCLUDE_PATH/opt/rk3588/opencv/include:$CPLUS_INCLUDE_PATH export LIBRARY_PATH/opt/rk3588/opencv/lib:$LIBRARY_PATH export LD_LIBRARY_PATH/opt/rk3588/opencv/lib:$LD_LIBRARY_PATH但要注意LD_LIBRARY_PATH影响的是运行时的库查找交叉编译阶段主要靠LIBRARY_PATH和-L。而且LD_LIBRARY_PATH设成主机路径后在板子上跑程序时如果也带着这个变量会去找一个不存在的路径反而出问题。所以交叉编译环境和板子运行环境要分开管理不要混用同一套环境变量。6.4 一个实用的Makefile模板实际项目中我习惯用一个Makefile来管理交叉编译CROSS_COMPILE aarch64-none-linux-gnu- CC $(CROSS_COMPILE)gcc CXX $(CROSS_COMPILE)g CFLAGS -O2 -Wall -I/opt/rk3588/opencv/include LDFLAGS -L/opt/rk3588/opencv/lib -Wl,-rpath-link/opt/rk3588/opencv/lib LIBS -lopencv_core -lopencv_imgproc -lopencv_dnn -lpthread TARGET yolov5_demo SRCS yolov5_demo.cpp $(TARGET): $(SRCS) $(CXX) $(CFLAGS) $^ -o $ $(LDFLAGS) $(LIBS) clean: rm -f $(TARGET)这样每次只需要make就行不用重复敲参数。换工具链时只改CROSS_COMPILE一个变量。7. 几个我踩过的坑和对应的解法7.1 坑一工具链版本和板子glibc不匹配这个坑我踩过两次。第一次是用了GCC 12的工具链glibc 2.36板子是Ubuntu 20.04的glibc 2.31编译出来的程序报GLIBC_2.34 not found。第二次是用了GCC 7.5的老工具链编译一个用了std::filesystem的程序直接编译不过。解法先确认板子的glibc版本ldd --version然后选一个glibc版本不高于它的工具链。Ubuntu 20.04对应glibc 2.31选GCC 9.2或10.3比较稳妥。7.2 坑二PATH里有多个交叉编译器如果你之前装过别的交叉工具链PATH里可能有多个aarch64-*-gcc。which aarch64-none-linux-gnu-gcc确认一下用的是哪个。更隐蔽的是有些工具链的前缀一样但版本不同比如两个都是aarch64-none-linux-gnu-gcc但一个在/usr/bin一个在/opt。PATH顺序决定了用哪个。解法用绝对路径调用编译器或者在脚本里显式设置PATH不要依赖默认PATH。7.3 坑三编译出来的程序在板子上跑但行为不对有一次编译一个用到char类型符号判断的程序在PC上跑没问题在板子上跑结果不对。原因是ARM默认char是无符号的x86默认char是有符号的。这个差异在涉及位运算和符号扩展时会暴露出来。解法编译时加-fsigned-char强制char为有符号跟x86行为一致。或者代码里显式用signed char和unsigned char不依赖默认行为。7.4 坑四静态编译后文件太大-static编译出来的hello有几MB如果项目里有很多个可执行文件加起来占的空间很可观。而且静态链接的程序无法使用动态库的热修复如果libc有安全更新静态程序不受影响但也享受不到修复。解法调试阶段用静态确认工具链没问题后改成动态。动态版本的文件通常只有几十KB。8. 验证清单交叉编译环境是否真正可用在进入YOLOv5s编译之前用下面这个清单逐项确认检查项命令预期结果编译器可用aarch64-none-linux-gnu-gcc -v输出版本信息架构正确file helloARM aarch64位数正确板子上运行./helloPointer size: 8动态链接器存在ls /lib/ld-linux-aarch64.so.1文件存在glibc版本兼容ldd --version对比readelf -V板子版本≥程序要求执行权限ls -l hello有x权限这六项全过了说明交叉编译环境完全可用可以开始编译OpenCV、RKNN SDK、YOLOv5s推理程序了。任何一项没过回到对应章节排查。我个人在实际操作中的体会是交叉编译最耗时间的不是编译本身而是环境配置和问题排查。把hello这一步做扎实把工具链版本、sysroot路径、动态链接器这些基础概念搞清楚后面编译再大的项目也只是参数多少的问题不会在根子上卡住。
返回列表