3、嵌入式Linux基础:交叉编译环境搭建、Bootloader、内核编译、根文件系统制作

好,咱们进入嵌入式Linux的核心环节。说实话,很多初学者一上来就被「交叉编译」「Bootloader」「内核」「根文件系统」这四个词给唬住了。我当年也一样,觉得这玩意儿高深莫测。但干过几个项目之后你会发现,说白了就是一套固定的流程,你照着走,走通了,系统就跑起来了。

这一章,我带你把这四个环节挨个捋一遍。每个环节我都会讲讲我踩过的坑,你注意避让就行。

3.1 交叉编译环境搭建:为什么不能在PC上直接编译?

先问个问题:你的开发电脑是x86架构,而目标板(比如ARM开发板)是ARM架构。你在x86上编译出来的二进制文件,ARM能认识吗?答案是不能。这就是交叉编译存在的理由——在一种架构上生成另一种架构能跑的程序。

我个人习惯用Linaro GCC工具链,稳定、社区活跃。搭建步骤其实很简单:

  1. 下载对应架构的交叉编译器(比如arm-linux-gnueabihf-)
  2. 解压到 /opt 目录下
  3. 配置环境变量 PATH

具体命令如下:

# 下载并解压
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
tar -xvf gcc-linaro-*.tar.xz -C /opt/

# 配置环境变量(建议写到 ~/.bashrc 里)
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin

# 验证是否安装成功
arm-linux-gnueabihf-gcc --version
小提示: 我建议你装完立刻写个简单的hello.c,用交叉编译器编译一下,放到板子上跑跑看。这一步能帮你快速确认工具链是否正常工作,别等到编译内核时才发现问题。
注意: 环境变量一定要配对。我曾经因为配错了路径,折腾了一下午才发现编译器版本不对。嗯,这种低级错误,犯一次就够了。

3.2 Bootloader:系统启动的第一段代码

Bootloader,说白了就是上电后第一个跑起来的程序。它的任务很简单:初始化硬件,加载内核到内存,然后跳过去执行。

嵌入式领域最常用的Bootloader是U-Boot(全称Das U-Boot)。我参与过的项目里,90%都在用U-Boot。它的配置和编译流程大致如下:

# 下载U-Boot源码
git clone https://github.com/u-boot/u-boot.git
cd u-boot

# 配置(以某款ARM开发板为例)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- <board_name>_defconfig

# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

编译完成后,你会得到一个 u-boot.bin 文件。这个文件需要烧写到板子的启动介质上,比如SD卡、NAND Flash或eMMC。

这里有个关键点:U-Boot的环境变量。你可以在U-Boot命令行里设置启动参数,比如内核从哪里加载、根文件系统挂载到哪里。我习惯把常用的环境变量写到 include/configs/ 目录下的头文件里,这样每次编译都自动带上了。

避坑指南: 我曾经遇到过一块板子,U-Boot死活起不来。查了半天,发现是DDR初始化参数配错了。U-Boot的板级配置里,DDR时序参数必须跟硬件设计完全一致,差一个纳秒都可能挂掉。所以,拿到一块新板子,第一件事就是核对DDR参数。

3.3 内核编译:裁剪与定制

Linux内核是个庞然大物,但嵌入式设备资源有限,你不能把整个内核都塞进去。所以,内核编译的核心就是裁剪——只保留你需要的功能。

编译内核的步骤其实不复杂:

# 下载内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz
tar -xvf linux-5.10.100.tar.xz
cd linux-5.10.100

# 配置内核(基于默认配置进行修改)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- <board_defconfig>
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

# 编译内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage

# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

编译完成后,你会得到 arch/arm/boot/zImage(内核镜像)和一堆 .dtb 文件(设备树)。

关于设备树,我多说两句。设备树是用来描述硬件信息的,比如GPIO怎么配、I2C挂在哪里、中断号是多少。以前这些信息都硬编码在内核里,后来发现太死板了,就改成了设备树这种灵活的方式。你想想看,换一块板子,只要改改设备树,内核不用重新编译,多方便。

我的经验: 第一次编译内核时,别急着裁剪。先用默认配置编译一遍,确保能跑起来。然后再慢慢去掉不需要的驱动和功能。这样出了问题,你知道是裁剪导致的,而不是编译环境的问题。

3.4 根文件系统制作:让系统有「家」

内核跑起来之后,需要挂载一个根文件系统(Root Filesystem)。没有它,系统就只是个空壳,什么应用都跑不了。

根文件系统里包含什么呢?说白了就是 /bin/sbin/etc/lib/usr 这些目录,以及里面的各种工具和库。

制作根文件系统,我推荐用 BuildrootYocto。Buildroot更轻量,适合初学者。Yocto功能更强大,但学习曲线陡峭一些。

用Buildroot制作根文件系统的流程:

# 下载Buildroot
git clone https://github.com/buildroot/buildroot.git
cd buildroot

# 配置(选择目标架构、工具链、软件包等)
make menuconfig

# 编译
make

# 编译完成后,output/images/ 目录下会生成:
# rootfs.tar(根文件系统压缩包)
# uImage(内核镜像,如果配置了的话)
# <board>.dtb(设备树)

生成的 rootfs.tar 可以解压到SD卡或NFS目录下,让内核挂载。

关键点: 根文件系统里一定要包含 /dev/proc/sys 这三个目录。它们是系统运行的基础,分别对应设备节点、进程信息和内核参数。少了任何一个,系统都可能无法正常工作。
我曾经踩过的坑: 有一次我做的根文件系统里忘了放 init 程序,结果内核启动后直接panic了。系统找不到第一个用户态进程,当然跑不起来。所以,记得检查 /sbin/init/bin/sh 是否存在。

3.5 整体流程回顾

好,咱们把整个流程串起来看看:

步骤 产出物 说明
交叉编译环境搭建 arm-linux-gnueabihf-gcc等工具 用于编译Bootloader、内核、应用
Bootloader编译 u-boot.bin 负责初始化硬件、加载内核
内核编译 zImage + dtb文件 系统核心,负责管理硬件和进程
根文件系统制作 rootfs.tar 或 ext4镜像 提供用户空间环境和工具

你把这四样东西准备好,烧写到板子上,上电,系统就能跑起来了。嗯,听起来是不是没那么难了?

最后说一句:嵌入式Linux开发,说白了就是跟硬件打交道。你多动手、多试错,慢慢就能摸清门道。我当年也是从烧坏好几块板子开始的,别怕。