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系统,glibc
  • mips-linux-gnu:MIPS架构,Linux系统,glibc

命名中的gnueabihf、gnu这些后缀,指的就是C库的类型和浮点ABI。

小技巧:如果你不确定目标板用哪个C库,可以登录板子执行 ls -l /lib/arm-linux-gnueabihf/libc.so.6 查看。或者直接跑 ldd --version 看glibc版本。

2.4 工具链的整体工作流程

为了让你更直观地理解这三者如何配合,我画了一张流程图:

交叉编译工具链工作流程 源代码 (.c / .h) gcc (交叉编译器) 汇编代码 (.s) as (汇编器) → 目标文件 (.o) ld (链接器) + glibc (C库) → 可执行文件 (ELF / .ko) 关键点 gcc 负责翻译语言 binutils 负责加工二进制 glibc 提供运行时支持 输出产物 最终生成目标机架构的 可执行文件或内核模块

从这张图你可以看到,整个流程是流水线式的:

  1. gcc 把源代码编译成汇编代码
  2. as(binutils的一部分)把汇编代码转成目标文件(.o)
  3. ld(binutils的一部分)把多个目标文件链接起来,同时链接glibc,最终生成可执行文件

这三者缺一不可。如果你只装了gcc,没有binutils,那连ld都没有,链接这一步就卡死了。如果你没有为目标机编译好的glibc,那链接器会报找不到printf、malloc这些基础函数。

2.5 如何判断你的工具链是否完整

拿到一个交叉编译工具链后,我通常会做三件事来验证它是否可用:

验证三步走:

  1. 检查编译器版本:arm-linux-gnueabihf-gcc --version,看能否正常输出版本信息
  2. 编译一个Hello World:写个简单的C程序,用交叉编译器编译,然后用file命令查看生成的文件架构是否正确
  3. 检查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提供运行时支持