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

资讯详情

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

ARM架构与交叉编译实战:从工具链选型到板级部署

ARM架构与交叉编译实战:从工具链选型到板级部署 好标题是DAY17意思是这个博主在按天打卡学嵌入式或Linux底层开发。ARM架构与交叉编译这几乎是每个做嵌入式Linux的人绕不过去的坎。结合搜索词里那些高频词——arm compiler 5.06u7、qt5.12.10交叉编译、linaro工具链、飞腾arm、rk3576、redis arm版本——能看出来大家对于“在x86电脑上编译出ARM板子能跑的程序”这件事有很多分类不清、工具选型混乱、报错不会看的问题。这篇文章我就按我自己带项目的经验来写不搞教科书式的概念罗列直接把这套东西掰开揉碎讲讲ARM架构的分类、工具链怎么选、交叉编译怎么实操、遇到报错怎么排查最后再聊聊Qt交叉编译和AI部署这些进阶场景。希望能帮到正在DAY17这个节点上挣扎的朋友。1. 先弄清ARM架构到底在学什么1.1 从下载工具链的困惑说起我见过太多新手上来就问交叉编译工具链到底该下哪个然后百度出一堆版本号什么arm-linux-gnueabihf、aarch64-linux-gnu、arm-none-eabi、arm compiler 5.06u7看得一头雾水随便装一个发现完全不能用。这个问题的根源就是没搞明白ARM架构这棵大树下到底分了多少岔路。ARM不像x86那样“一把尺子量到底”它是一个IP授权模式ARM公司只卖设计图纸芯片厂商拿过去随便改。所以你会看到手机里的骁龙、服务器里的飞腾、开发板上的树莓派、单片机里的STM32它们都叫ARM但软件完全不通用。这里有一个方便理解的类比x86像是买精装房图纸固定你只管买家具往里搬ARM像是买毛坯房开发商给你一个户型图墙怎么敲、水电怎么走全看你自己。所以做ARM开发你首先要搞清楚自己手里的那把钥匙交叉编译工具链对应的到底是哪一套户型。1.2 RISC与CISC的底层差异ARM属于RISC精简指令集计算机x86属于CISC复杂指令集计算机。这是学习ARM绕不开的第一对概念。CISC的哲学是“一条指令干很多事”比如x86里有一条指令可以直接从内存读数据、做运算、再写回内存一条顶三条。这样的好处是编译器好写但硬件实现极其复杂功耗居高不下。RISC的哲学是“每条指令只干一件小事”ARM里大多数指令就是读写寄存器或者做运算内存访问必须通过load/store指令单独完成。指令条数变多了但每条指令的执行时间都差不多硬件实现简单流水线效率高所以功耗低。这个差异直接决定了它们的命运x86在追求极致性能的PC和服务器市场称王ARM在寸土寸金的嵌入式、移动设备市场称霸。ARM能在低功耗下提供不错的性能这才是它能在物联网终端遍地开花的核心原因。1.3 ARM产品线地图Cortex-A/R/M怎么选ARM处理器分三大产品线很多新手就是栽在这里Cortex-A系列Application应用处理器。跑Linux、Android全靠它比如树莓派里的A72、RK3576里的A72A53、手机里的A77等。你手上的开发板只要说能跑Linux基本就是A系列。交叉编译时目标平台是跑着Linux操作系统的环境。Cortex-R系列Real-time实时处理器。用在汽车刹车、硬盘控制器这种对响应时间极其敏感的场合讲究确定性延迟一般跑RTOS或者裸机程序。Cortex-M系列Microcontroller微控制器。就是STM32那种单片机资源极小跑裸机代码或者FreeRTOS不能用Linux这种大系统。你学交叉编译绝大多数情况面对的是A系列。至于搜索词里那个arm compiler 5.06u7那是ARM公司自家的商业编译器主要用在Keil MDK里编译Cortex-M单片机的固件跟Linux下的交叉编译完全是两个世界。如果你不是在做单片机开发直接用开源GCC工具链就够了别被那些版本号带跑偏。2. 交叉编译的核心原理2.1 为什么要交叉编译交叉编译英文叫cross compile意思是“在一种架构的电脑上编译出能在另一种架构上运行的程序”。最常见的场景就是你在x86的PC上编译出ARM板子上能跑的二进制文件。那为什么不直接在ARM板子上编译呢三个原因第一开发板性能有限。嵌入式板子很多只有1G内存、几核低主频CPU在上面直接跑GCC编译大工程编译时间可能以小时计严重影响效率。第二工具链不完整。板子上往往没有完整的编译环境和依赖库装个包都费劲更别说是编译大型软件。第三开发流程不允许。现代开发都是多人协作代码在服务器上构建、测试、发布。如果每次编译都要跑到板子上版本管理、CI/CD流程全都得重设计完全不现实。所以行业标准做法就是x86主机负责编译ARM板子负责运行。中间的桥梁就是一套交叉编译工具链。2.2 交叉编译工具链的组成一套完整的交叉编译工具链包含三个关键部分binutils二进制工具集包含汇编器as、链接器ld、反汇编器objdump、查看文件格式的file等。它负责把汇编语言变成机器码再把多个目标文件链接成可执行文件。gcc真正的编译器前端负责把C/C源码编译成汇编代码。C/C库最核心的是C库常见的有glibc、musl、uClibc。程序跑起来调用的printf、malloc这些函数都是这个库提供的。交叉编译工具链里必须带上目标平台的C库因为C库和内核接口是强相关的。工具链的命名其实暗藏玄机。举个例子arm-linux-gnueabihf-gcc拆开看就是“目标架构是ARM运行环境是Linux用的C库是glibc浮点ABI是硬浮点hfhard-float”。换句话说只要读懂了这个名字你就知道这套工具编出来的程序是什么样子的也就大概能判断它能不能在某个板子上跑。2.3 编译的四个阶段与ELF格式不管是本机编译还是交叉编译GCC把源码变成可执行文件都走四个阶段值得自己过一遍心里有底预处理Preprocessing处理#include、#define这些指令把头文件内容原样插入到源码中生成.i文件。这个阶段把“人写的源码”变成“真正能被编译器读懂的完整源码”。编译Compilation把预处理后的代码翻译成汇编代码生成.s文件。这里的汇编代码是基于目标平台的指令集生成的比如你让arm-gcc编译生成的.s文件里就是ARM指令的汇编。汇编Assembly把汇编代码转换成机器码生成.o目标文件。注意这时的.o文件里有很多外部函数比如printf还没有被解析地址都是空的需要等下一步链接。链接Linking把多个.o文件和库文件组合在一起解析符号引用分配最终的内存地址生成可执行文件。链接是交叉编译中最容易出问题的阶段因为各种库的路径、依赖关系全在这里爆发冲突。最终生成的可执行文件是ELF格式。x86下你运行file hello会看到ELF 32-bit LSB executable, Intel 80386而交叉编译出来的应该显示ELF 32-bit LSB executable, ARM, EABI5注意架构从Intel 80386变成了ARM。这一步是验证交叉编译是否成功的第一道标准。3. 工具链选型与环境搭建实操3.1 裸机工具链和Linux工具链先分清选工具链第一步要分清你是开发裸机程序还是Linux用户态程序裸机工具链典型代表是arm-none-eabi-gcc。它没有操作系统依赖生成的是跑在裸芯片上的固件比如STM32的初始化代码。没有操作系统支持所以不能用来编译需要Linux系统调用的程序。Linux工具链典型代表是arm-linux-gnueabihf-gcc和aarch64-linux-gnu-gcc。它们生成的程序依赖目标板上的Linux内核和C库是我们做嵌入式Linux的主流选择。aarch64对应ARM的64位架构也就是ARMv8的AArch64模式arm-linux-gnueabihf对应32位架构。这两个要是用混了出现的第一类报错就是找不到头文件、找不到动态链接器、或者直接编译出来运行不了。记住学交叉编译你的目标板上跑的是Linux就用Linux工具链别拿裸机工具链浪费时间。3.2 主流交叉编译器横向对比我长期用过几套这里做个对比工具链目标平台适用场景版本建议arm-linux-gnueabihf-gcc32位ARMARMv7老一些的Cortex-A板子GCC 8.x之后直接apt装aarch64-linux-gnu-gcc64位ARMARMv8目前绝大多数新板子RK35系列、飞腾等GCC 10.x以上arm-none-eabi-gcc裸机Cortex-M/RSTM32固件开发10.3版本Linaro GCC32/64位ARM需要特定GCC版本时7.5、9.3较常用ARM Compiler 5/6Cortex-MKeil MDK配套商业编译器5.06u7是最后版本提到Linaro工具链这是ARM公司支持的一个开源项目专门为ARM架构优化GCC。很多芯片厂商的SDK里都是打包的Linaro版本。它的下载页面会显示类似gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf的名字前半段是GCC版本后半段是宿主机架构和目标的组合。挑的时候看准这个格式一般不会选错。3.3 手把手搭建arm-linux-gnueabihf环境我用Ubuntu 20.04演示这是目前最稳妥的交叉编译宿主系统。其他发行版操作类似只是包源不同。第一步安装工具链sudo apt update sudo apt install gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf装完后验证arm-linux-gnueabihf-gcc -v看到版本信息就成功了。如果你用的是64位目标装的是gcc-aarch64-linux-gnu。Ubuntu自带的版本可能不是最新但做学习完全够用不必迷信最新版。第二步配置环境变量echo export PATH$PATH:/usr/bin ~/.bashrc source ~/.bashrc这一步的意义是让bash能找到这个工具链。如果工具链没有装到/usr/bin而是在其他目录就替换成工具链所在的bin目录。比如Linario工具链解压到/opt/linaro那PATH里就加/opt/linaro/bin。第三步验证功能正常arm-linux-gnueabihf-gcc -v # 或者 arm-linux-gnueabihf-gcc -print-sysroot看到工具链能正常输出环境就OK了。这里有一个常见坑32位宿主机上运行64位工具链会报cannot execute binary file: Exec format error反过来也一样。现在的Ubuntu都是64位基本不会遇到但虚拟机里有可能装成32位系统需要注意。4. 从Hello World到板子运行4.1 编写和编译第一个程序环境搭建好之后写个最朴素的测试程序这个流程必须走通// hello.c #include stdio.h int main(int argc, char *argv[]) { printf(hello arm, from %s\n, argc 1 ? argv[1] : world); return 0; }然后开始交叉编译arm-linux-gnueabihf-gcc -o hello hello.c编译过程如果不报错先别急着高兴查看一下生成的文件的属性file hello输出应该是类似hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0关键信息是ARM、EABI5、dynamically linked。这说明编译出来的确实是ARM架构的ELF可执行文件。再看它的动态链接器是/lib/ld-linux-armhf.so.3这个路径是板子上要存在的东西对应的是硬浮点abihf库。如果这一步看到Intel 80386说明用的是宿主机自带的gcc不是交叉编译器。这个错误我见太多次了。4.2 动态链接与静态编译的取舍上面的编译方式是动态链接程序体积很小但是运行的时候需要板子上有配套的动态库。如果板子的C库版本跟编译器附带的库版本不一致运行时就会报GLIBC_2.33 not found之类的错误。另一个选择是静态编译arm-linux-gnueabihf-gcc -static -o hello_static hello.c生成的程序把所有依赖库都打包进二进制里了体积会大不少但从x86服务器直接拷到板子上就能跑不用关心板子上装的什么库版本。这在调试初期特别有用。不过生产环境一般不推荐全部静态编译因为补丁更新不方便任何一个库有问题都要重新编译整个程序。正确的做法是前期用静态编译验证功能通路后期确认了目标板的库版本后再换成动态编译。4.3 部署到目标板运行的三种方式程序编译出来后怎么弄到板子上我常用的有三种最直接scpscp ./hello user板子IP:/home/user/然后ssh登到板子上./hello就能跑。前提是板子已经联网。批量调试NFS网络挂载在宿主机上配置NFS把编译输出目录共享给板子。板子上mount -t nfs 宿主机IP:/share /mnt之后直接运行/mnt/hello。这种方式的好处是程序更新后不用重新拷板子上跑到的是最新版本非常适合迭代调试。纯串口传输rz/sz或者base64板子上没有网络只有串口时用SecureCRT的zmodem协议传文件或者先base64编码再粘贴进串口解码。很土但很可靠应急必用。到了板子上运行之前再看一眼依赖ldd ./hello这条命令会列出程序依赖的共享库。如果某个库显示not found那就是板子上缺库或者动态链接器路径不对。这一步是排查运行时失败的关键手段。5. 常见问题与排查技巧实录5.1 典型错误对照速查表交叉编译报错五花八门但归类下来就那么几类。我整理一个对照表你遇到报错可以先对号入座报错信息根本原因解决思路cannot execute binary file: Exec format error生成的程序架构和当前系统架构不对最常见的两种情况x86跑了ARM程序或者64位宿主机跑了32位程序/lib/ld-linux-armhf.so.3: No such file or directory板子上缺少动态链接器检查工具链的ABI类型与板子是否一致改用静态编译绕过unrecognized command-line option -mfloat-abihard工具链不支持硬浮点选项可能误用了裸机工具链检查是不是用了arm-none-eabi-gccGLIBC_2.xx not found程序依赖的C库版本高于板子上装的用strings /lib/arm-linux-gnueabihf/libc.so.6 | grep GLIBC查看板子库版本换合适版本的编译器cannot find -lxxx缺少某个库的交叉编译版不能用宿主机自带的库需要单独交叉编译该库源代码undefined reference to xxx链接阶段符号没找到检查库的链接顺序GCC链接库要放在目标文件后面selected processor does not support \dmb ish编译器默认目标太老编译时显式指定CPU架构比如加-mcpucortex-a725.2 Qt交叉编译的高频坑位搜索词里频繁出现“qt5.12.10交叉编译”、“rk3576 qt交叉编译环境”这算是二次进阶了。Qt交叉编译比裸编译复杂一个量级因为Qt本身是大型框架要先在宿主机上编译一套针对ARM的Qt库。关键点有三个第一qmake的配置。Qt使用qmake管理工程交叉编译时要用Qt自带的qtbase/mkspecs目录下的deivce配置。以RK3576为例官方SDK里一般会带一套完整的设备配置里面写死了编译器路径、sysroot路径、默认的架构选项。修改这套配置时需要保持一致性尤其不要随意改动里面的编译选项。第二依赖库。Qt依赖很多第三方库比如字体渲染的freetype、压缩的zlib、图形接口的EGL。这些库都需要先交叉编译出ARM版本丢到sysroot里Qt配置时才能找到它们。经常有人卡在这里因为sysroot里的库版本和X86宿主机里的版本不一致。第三tslib和触摸屏校准。如果是带触摸屏的板子还要编译tslib库并在运行时通过环境变量QWS_MOUSE_PROTO或者新的QT_QPA_PLATFORM去指定输入设备。其实Qt交叉编译本质并不神秘就是把“编译出hello world”这件事放到一个庞大的工程上重复。先走通小程序再走通Qt梯度推进能少踩一半坑。5.3 进阶应用内核编译与AI推理部署再往外走交叉编译的用处就广了编译Linux内核芯片厂商发布了新板子你想用新的内核版本就得在x86宿主机上交叉编译出ARM架构的zImage。具体步骤一般是使用厂商提供的默认配置文件make ARCHarm加上CROSS_COMPILEarm-linux-gnueabihf-然后执行make。这里的CROSS_COMPILE变量就是指定工具链前缀的。AI部署DNN/CNN推理框架在ARM板子上落地比如把某个推理引擎跑在RK3576这类带NPU的芯片上要么用厂商SDK直接交叉编译要么在板子上用pip装ARM版的wheel包。搜索词里的“redis arm版本”也是同样的逻辑很多基础软件都有人预编译好了ARM版直接下载即可不需要自己从头交叉编译。一个很有用的经验是动手编译之前先搜索一下有没有现成的ARM包。Redis、Python的各种库、MQTT broker这些在官方源或第三方源里基本都有ARM版本。交叉编译消耗的时间成本很高如果别人已经打包好了直接用是最省时的方案。6. 写在最后的一些实操体会学交叉编译本质上是学一门“翻译”的能力。你能在x86的世界里写代码然后让这些代码在ARM的世界里跑起来这个能力在嵌入式、物联网、边缘计算领域都是硬通货。我个人在实际操作中的体会是交叉编译最大的障碍不是技术而是心态。很多人在工具链选型、版本匹配上反复纠结生怕选错了结果下载了一堆工具链却一个都用不顺。我的建议是先随便挑一个和你板子架构基本匹配的工具链32位选gnueabihf64位选aarch64-linux-gnu把hello world跑通了再来逐步调整优化。路线通了细节问题都能一个个解决。另外一定要养成看报错信息的习惯。交叉编译的报错信息里往往已经告诉了你答案Exec format error说明架构不匹配GLIBC not found说明版本不匹配cannot find -l说明库路径不对。不要一看到英文报错就慌逐字翻译定位到关键字再上网搜索基本都能解决。最后分享一个小技巧在你的开发环境里常备一个仓库目录专门存放各种工具的交叉编译笔记和脚本。这一步真的很有价值——以后换板子、换工具链版本照着以前的笔记改重新走一遍能省下大量重试的时间。我把这个方法坚持了多年从STM32裸机到现在的ARM64 Linux应用靠的全是一点一滴积累的笔记。交叉编译这条路没有捷径但踩过的坑越少后面跑得越快。
返回列表