第4章:Linux基础入门:内核裁剪与编译、设备树配置、根文件系统构建

好,咱们进入正题。这一章讲的是Linux基础入门,但别小看“基础”这两个字。内核裁剪、设备树、根文件系统——这三样东西,说白了就是嵌入式Linux的“三驾马车”。你跑不起来,多半是其中一环出了问题。

我个人习惯把这三件事分开讲,但心里要清楚:它们是联动的。内核配错了,设备树再对也没用;根文件系统缺个库,应用就跑不起来。嗯,咱们一个一个来。

4.1 Linux内核裁剪与编译

内核裁剪,说白了就是“瘦身”。你想想看,一个通用Linux内核有好几万个驱动,你的板子上可能只用到了几十个。剩下的全是累赘。

我在项目中遇到过一块Flash只有16MB的板子,默认内核编译出来快30MB。根本烧不进去。后来裁剪到4MB,跑得稳稳的。

4.1.1 获取内核源码

先从官网或者你的芯片厂商那里拿到内核源码。我建议用长期支持版(LTS),稳定。

# 以Linux 5.10 LTS为例
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz
tar -xf linux-5.10.100.tar.xz
cd linux-5.10.100

4.1.2 配置内核

配置内核有几种方式。我个人最常用的是menuconfig,图形界面,直观。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

进去之后,你会看到一堆菜单。别慌。核心思路是:

  • 关掉不需要的驱动:比如你的板子没有WiFi,就把无线网络驱动全关了。
  • 保留必要的文件系统支持:ext4、squashfs、initramfs这些,看你的根文件系统类型。
  • 开启调试选项:初期开发阶段,建议打开Kernel hacking里的printkearly printk
小技巧:如果你是从芯片厂商的BSP开始,先拿他们的默认配置做基础。别自己从头配,太容易漏东西。我曾经因为漏了串口驱动,折腾了一整天。

4.1.3 编译内核

配置好了,直接编译。注意指定架构和交叉编译器。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 modules

这里-j4是并行编译,看你CPU核心数。我习惯用-j$(nproc)自动检测。

编译完成后,你会得到arch/arm/boot/zImage。这就是你的内核镜像。

注意:如果你的板子是64位的,用Image而不是zImage。别搞混了,否则启动时会直接卡住。

4.2 设备树配置

设备树(Device Tree),说白了就是告诉内核“你的板子上有什么硬件”。以前老内核用板级文件(board file),硬编码。现在都改用设备树了,灵活得多。

我记得第一次接触设备树时,觉得它就是个“硬件描述语言”。其实没那么玄乎,就是个树形结构,描述CPU、内存、外设的地址和中断。

4.2.1 设备树的基本结构

一个典型的设备树文件(.dts)长这样:

/dts-v1/;

/ {
    model = "My Custom Board";
    compatible = "vendor,myboard";

    chosen {
        stdout-path = &uart0;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;  // 256MB
    };

    uart0: serial@10000000 {
        compatible = "ns16550";
        reg = <0x10000000 0x1000>;
        interrupts = <0 20 4>;
        clock-frequency = <24000000>;
    };
};

看到没?每个节点就是一个硬件设备。属性里写的是它的配置参数。

4.2.2 编译设备树

设备树源文件(.dts)需要编译成二进制(.dtb),内核才能用。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- myboard.dtb

或者手动用dtc工具:

dtc -I dts -O dtb -o myboard.dtb myboard.dts
关键点:设备树里的compatible属性必须和内核驱动里的匹配。否则驱动不会加载。我踩过这个坑,明明驱动写好了,就是没反应,最后发现是compatible拼写错了。

4.2.3 设备树覆盖

有时候你需要动态修改设备树,比如接一个扩展板。这时候可以用设备树覆盖(overlay)。

我个人觉得这个功能在调试阶段特别有用。不用重新编译整个内核,改个.dts文件,加载一下就行。

4.3 根文件系统构建

根文件系统,就是Linux启动后挂载的“/”目录。它里面要有init程序、库文件、配置文件、应用等等。

构建根文件系统,主流工具有两个:BuildrootYocto。我两个都用过,说说我的感受。

4.3.1 Buildroot:轻量级首选

Buildroot适合做小系统。配置简单,编译快。我做一个简单的IoT网关,用Buildroot半小时就能搞定。

基本用法:

git clone https://git.buildroot.net/buildroot
cd buildroot
make menuconfig

在菜单里选择:

  • Target architecture:ARM
  • Toolchain:用外部工具链或Buildroot自己编译
  • Filesystem images:选ext4或squashfs

然后:

make

编译完成后,output/images/下就有根文件系统镜像了。

经验之谈:Buildroot的包管理是“全编译”模式。你加一个包,它会重新编译整个系统。所以第一次编译慢,后面加包就快了。我习惯先配好所有需要的包,再一次性编译。

4.3.2 Yocto:大型项目的选择

Yocto功能更强大,但学习曲线也陡。它用BitBake作为构建引擎,支持复杂的依赖关系和定制化。

我参与过一个工业控制项目,用了Yocto。因为它需要支持多种硬件平台、多种软件栈,Yocto的层(layer)机制特别适合这种场景。

基本步骤:

git clone git://git.yoctoproject.org/poky
cd poky
source oe-init-build-env
bitbake core-image-minimal

Yocto的配置文件在conf/local.conf里。你可以指定目标架构、包类型、调试选项等。

特性 Buildroot Yocto
编译速度 慢(第一次可能几小时)
定制化程度 中等
学习曲线 平缓 陡峭
适用场景 小系统、原型验证 大型产品、多平台

4.3.3 手动构建根文件系统

如果你想彻底搞懂根文件系统,可以手动构建。虽然麻烦,但能学到东西。

基本步骤:

  1. 创建目录结构:bin, sbin, etc, lib, usr, dev, proc, sys
  2. 复制BusyBox到bin/,它提供基本的shell和命令
  3. 复制交叉编译的库文件到lib/
  4. 创建/etc/inittab/etc/fstab
  5. 创建设备节点:mknod /dev/console c 5 1

嗯,手动构建确实繁琐。我建议新手先用Buildroot,等熟悉了再尝试手动。

避坑指南:我曾经手动构建时忘了复制ld-linux.so,结果所有动态链接的程序都跑不起来。检查了半天才发现。所以,lib/目录一定要完整。

4.4 三者的整合

内核、设备树、根文件系统,这三者最终要整合在一起,才能启动系统。

启动流程大致是:

  1. Bootloader(如U-Boot)加载内核和设备树到内存
  2. 内核解析设备树,初始化硬件
  3. 内核挂载根文件系统,启动init进程

所以,你在开发时要确保:

  • 内核配置正确(驱动、文件系统支持)
  • 设备树描述准确(地址、中断、时钟)
  • 根文件系统完整(init、库、应用)

任何一个环节出问题,系统都起不来。调试时,我习惯先看内核启动日志(通过串口),看它卡在哪一步。是设备树解析失败?还是根文件系统挂载不上?日志会告诉你。

好了,这一章的内容就到这里。下一章我们会深入Bootloader的配置和调试,到时候再聊。