2. 嵌入式Linux开发基础
好,咱们正式开始聊交叉编译工具链。很多刚入行的朋友一听到「交叉编译」四个字就觉得头大,觉得这是个很高深的概念。其实说白了,它没那么玄乎。
我刚开始做嵌入式的时候,也犯过迷糊。记得第一次在PC上写好代码,自信满满地编译完,烧到开发板上——结果跑不起来。当时我还纳闷:明明编译没报错啊?后来才明白,我用的编译器是给x86用的,而我的板子是ARM架构。这就好比让一个说中文的人去读英文报纸,字都认识,但意思完全不对。
2.1 什么是交叉编译
交叉编译,简单来说就是:在一个平台上生成另一个平台可执行的代码。
我们平时在Windows或Linux PC上写C代码,用gcc编译,生成的可执行文件只能在当前CPU架构(通常是x86或x86_64)上运行。这叫本地编译。
但嵌入式设备不一样。你的开发板可能是ARM、MIPS、RISC-V架构,它们的指令集和PC完全不同。你不可能在ARM开发板上跑gcc去编译——性能太差,存储也不够。所以,我们就在强大的PC上,用专门的编译器,生成ARM能跑的二进制文件。这就是交叉编译。
一句话总结:交叉编译 = 在A机器上编译,生成B机器能跑的程序。
为什么会需要这个?你想想看,嵌入式设备的资源极其有限。我见过有些IoT模组,Flash只有2MB,RAM才256KB。你让它跑gcc?门儿都没有。所以,交叉编译是嵌入式开发的必修课。
2.2 宿主机与目标机
这两个概念必须分清楚,不然你后面配置工具链时会晕头转向。
| 术语 | 英文 | 角色 | 典型设备 |
|---|---|---|---|
| 宿主机 | Host | 运行编译工具链的机器 | 你的PC(x86_64 Linux) |
| 目标机 | Target | 运行编译生成程序的机器 | ARM开发板、MIPS路由器 |
我个人习惯用「Host-Target」这对组合来记忆。Host是干活的,Target是跑活的。你在Host上敲make,生成的文件要扔到Target上去跑。
这里有个常见的坑:不要把宿主机和目标机搞反了。我曾经见过一个同事,在配置工具链时把--host和--target参数写反了,结果编译出来的程序在PC上能跑,一放到板子上就报「Illegal instruction」。折腾了两天才发现是参数配反了。
避坑指南:配置交叉编译工具链时,--host填的是你当前用的机器(PC),--target填的是你要跑程序的机器(开发板)。别搞反了。
2.3 工具链的三大核心组件
一套完整的交叉编译工具链,主要由三部分组成:gcc、binutils、glibc(或其它C库)。它们各司其职,缺一不可。
2.3.1 gcc(编译器)
gcc是GNU Compiler Collection的缩写。它负责把C/C++源代码翻译成汇编代码。在交叉编译场景下,我们用的是交叉版本的gcc,比如arm-linux-gnueabihf-gcc。
你可能会问:gcc和普通gcc有什么区别?本质上,它们都是gcc,只是目标架构不同。交叉gcc在编译时,生成的机器指令是ARM的,而不是x86的。
我记得有一次调试一个浮点运算问题,板子上的程序总是算不对。后来发现,我用的工具链是arm-linux-gnueabi(软浮点),而我的CPU硬件支持硬浮点。换成arm-linux-gnueabihf之后,问题立刻解决。所以,选gcc时一定要搞清楚目标CPU的硬件特性。
2.3.2 binutils(二进制工具集)
binutils是一组处理二进制文件的工具。它不负责编译,但负责链接、汇编、反汇编、查看文件信息等脏活累活。
常用的binutils工具包括:
- as:汇编器,把汇编代码转成机器码
- ld:链接器,把多个目标文件链接成可执行文件
- objdump:反汇编利器,查看二进制文件的汇编代码
- objcopy:格式转换,比如把ELF转成纯二进制bin文件
- readelf:查看ELF文件头信息、段信息
- strip:去掉符号表,减小文件体积
我个人最常用的是objdump和readelf。调试驱动时,经常需要反汇编看看编译器到底生成了什么指令。比如:
arm-linux-gnueabihf-objdump -D my_driver.ko | less
这条命令能让你看到内核模块的每一条汇编指令。嗯,有时候编译器优化出来的代码,你看了会怀疑人生。
2.3.3 glibc(C运行库)
glibc是GNU C Library,它提供了程序运行所需的基础函数,比如printf、malloc、open、read等。没有C库,你的程序连个Hello World都打印不出来。
在交叉编译中,glibc必须是为目标架构编译好的版本。你不能拿PC上的glibc去给ARM用,指令集都不一样。
这里有个重要的概念:工具链的命名规则。比如:
arm-linux-gnueabihf:ARM架构,Linux系统,glibc,硬浮点aarch64-linux-gnu:ARM 64位,Linux系统,glibcmips-linux-gnu:MIPS架构,Linux系统,glibc
命名中的gnueabihf、gnu这些后缀,指的就是C库的类型和浮点ABI。
小技巧:如果你不确定目标板用哪个C库,可以登录板子执行 ls -l /lib/arm-linux-gnueabihf/libc.so.6 查看。或者直接跑 ldd --version 看glibc版本。
2.4 工具链的整体工作流程
为了让你更直观地理解这三者如何配合,我画了一张流程图:
从这张图你可以看到,整个流程是流水线式的:
- gcc 把源代码编译成汇编代码
- as(binutils的一部分)把汇编代码转成目标文件(.o)
- ld(binutils的一部分)把多个目标文件链接起来,同时链接glibc,最终生成可执行文件
这三者缺一不可。如果你只装了gcc,没有binutils,那连ld都没有,链接这一步就卡死了。如果你没有为目标机编译好的glibc,那链接器会报找不到printf、malloc这些基础函数。
2.5 如何判断你的工具链是否完整
拿到一个交叉编译工具链后,我通常会做三件事来验证它是否可用:
验证三步走:
- 检查编译器版本:
arm-linux-gnueabihf-gcc --version,看能否正常输出版本信息 - 编译一个Hello World:写个简单的C程序,用交叉编译器编译,然后用
file命令查看生成的文件架构是否正确 - 检查C库路径:
arm-linux-gnueabihf-gcc -print-sysroot,看sysroot下是否有lib和usr/lib目录,里面应该有libc.so.6等库文件
我曾经遇到过一种情况:工具链能编译,但编译出来的程序在板子上跑不起来。用readelf -l一看,发现程序请求的ld-linux.so.3路径在板子上不存在。这就是sysroot没配置对,或者工具链和板子的glibc版本不匹配。
2.6 小结
这一章我们聊了三个核心概念:
- 交叉编译:在Host上编译,在Target上运行
- 宿主机与目标机:Host是干活的,Target是跑活的,别搞反
- 工具链三件套:gcc负责编译、binutils负责链接和加工、glibc提供运行时支持