4、内核定制与编译:从配置到烧录的完整链路

内核定制与编译,说白了就是把 Linux 内核源码变成能在你目标板上跑起来的二进制文件。这个过程我做了不下上百次,每次换平台都得走一遍。今天咱们就把内核配置裁剪、设备树编写、镜像生成到烧录这条线彻底捋清楚。

4.1 内核配置裁剪——别让内核“虚胖”

嵌入式设备资源有限,Flash 可能就 64MB,RAM 才 128MB。如果直接把 PC 上的完整内核搬过去,启动都成问题。所以配置裁剪是第一步,也是最重要的一步。

4.1.1 配置的三种方式

方式 命令 适用场景
基于默认配置 make defconfig 快速开始,适合架构通用配置
基于现有配置 make menuconfig 交互式裁剪,最常用
基于厂商配置 make xxx_defconfig 厂商提供的基础配置

我个人习惯先用 make menuconfig 做第一轮裁剪。为什么?因为它有图形界面,能直观看到每个模块的依赖关系。你想想看,如果直接改 .config 文件,很容易漏掉依赖项,编译时一堆报错。

我的经验:第一次裁剪时,先关掉所有不用的驱动。比如你的板子没有 WiFi 模块,就把 CONFIG_WLAN 整个关掉。我曾经接手一个项目,内核镜像 8MB,裁剪后只剩 2.3MB,启动时间从 12 秒降到 4 秒。

4.1.2 裁剪的核心原则

  • 关掉不用的硬件驱动:声卡、显卡、网卡,板子上没有的就关掉。
  • 关掉调试选项:CONFIG_DEBUG_KERNEL、CONFIG_PRINTK 在发布版里可以关掉。
  • 关掉不用的文件系统:只用 ext4 就别编译 btrfs、xfs。
  • 模块化 vs 内建:频繁改动的驱动做成模块,基础驱动内建。
注意:别把启动必需的驱动关掉。比如根文件系统在 SD 卡上,你把 MMC/SD 驱动关成模块,内核就找不到根文件系统了。我曾经犯过这个错,板子启动到一半卡住,查了半天才发现是驱动没内建。

4.2 设备树(DTS)编写——硬件描述的艺术

设备树是内核和硬件之间的“翻译官”。它告诉内核:CPU 是什么、内存多大、外设挂在哪、中断号是多少。没有设备树,内核就是个瞎子。

4.2.1 DTS 文件结构

一个典型的 DTS 文件长这样:

/dts-v1/;

#include "imx6ul.dtsi"

/ {
    model = "MyBoard i.MX6UL";
    compatible = "myboard,imx6ul", "fsl,imx6ul";

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

    &uart1 {
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_uart1>;
        status = "okay";
    };
};

这里有几个关键点:

  • dtsi 是 SoC 级描述文件,一般由芯片厂商提供。
  • model 和 compatible 是板级标识,内核靠它匹配驱动。
  • reg 描述地址和大小,格式是 <起始地址 长度>。

4.2.2 编写 DTS 的常见坑

我在项目中遇到过最头疼的问题,是中断号写错了。板子上的按键中断一直不触发,查了三天,最后发现 DTS 里的中断号比实际硬件少了一个。为什么会这样?因为有些 SoC 的中断号是从 0 开始编号,有些是从 1 开始,一定要看芯片手册确认。

避坑指南:写 DTS 时,先对照芯片手册把 reg、interrupts、clocks 这三个属性确认清楚。这三个最容易出错。

4.3 设备树编译——把文本变成二进制

DTS 是文本文件,内核不认识。需要编译成 DTB(Device Tree Blob)二进制文件。命令很简单:

# 编译单个 DTS
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 在内核源码树中编译
make dtbs

我个人习惯用 make dtbs,因为它会自动处理依赖关系。如果你改了某个 .dtsi 文件,它会把所有引用它的 DTS 都重新编译。

小技巧:编译完 DTB 后,用 dtc -I dtb -O dts myboard.dtb 反编译回来,检查一下有没有丢失信息。有时候编译过程会报 warning,但 DTB 还是生成了,反编译能帮你确认内容是否正确。

4.4 内核镜像生成——从源码到二进制

配置和 DTS 都准备好了,接下来就是编译内核。嵌入式 Linux 常用的内核镜像格式有三种:

格式 生成命令 特点
zImage make zImage 自解压,体积小,适合 ARM
uImage make uImage 带 U-Boot 头,用于 U-Boot 引导
Image make Image 原始 ELF 格式,调试用

我一般用 make uImage LOADADDR=0x80008000。为什么?因为 U-Boot 是嵌入式最常用的 bootloader,它需要 uImage 格式才能识别。LOADADDR 是内核的加载地址,不同 SoC 不一样,一定要查手册。

4.4.1 编译命令示例

# 设置交叉编译工具链
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# 配置内核
make myboard_defconfig

# 编译内核镜像
make uImage LOADADDR=0x80008000 -j4

# 编译设备树
make dtbs

# 编译内核模块
make modules
注意:-j4 表示用 4 个线程并行编译。别设太大,否则容易把电脑搞死机。我一般设成 CPU 核心数减 1。

4.5 烧录——把镜像写进板子

镜像生成后,最后一步是烧录。烧录方式取决于你的板子支持什么。常见的有三种:

  • SD 卡烧录:把镜像拷到 SD 卡,板子从 SD 卡启动。
  • 网络烧录:通过 TFTP 或 NFS 加载镜像。
  • JTAG 烧录:用调试器直接写 Flash。

我最常用的是 SD 卡烧录,因为简单。步骤大致如下:

  1. 把 SD 卡插到电脑,用 dd 命令把 uImage 和 DTB 写到指定分区。
  2. 设置板子的启动拨码开关为 SD 卡启动。
  3. 上电,看串口输出。
# 烧录 uImage 到 SD 卡第一个分区
sudo dd if=arch/arm/boot/uImage of=/dev/sdb1 bs=1M

# 烧录 DTB
sudo dd if=arch/arm/boot/dts/myboard.dtb of=/dev/sdb2 bs=1M
核心流程总结:配置裁剪 → DTS 编写 → DTB 编译 → 内核编译 → 镜像烧录。每一步都有坑,但走通一次后面就顺了。

4.6 本章知识结构图

下面这张图把内核定制与编译的完整流程串起来了,你可以对照着看:

内核定制与编译完整流程 内核定制与编译完整流程 1. 内核配置裁剪 2. DTS 编写 3. DTB 编译 4. 内核镜像编译 5. 内核模块编译 6. 镜像烧录 输出产物:uImage / zImage + DTB + 内核模块 (.ko) SD 卡烧录 网络烧录 (TFTP/NFS) JTAG 烧录 验证:串口输出 → 内核启动日志 → 文件系统挂载

嗯,到这里内核定制与编译的完整流程就讲完了。从配置裁剪到 DTS 编写,再到编译和烧录,每一步都有它的门道。你只要按这个流程走一遍,基本就能把内核跑起来。遇到问题别慌,先看串口日志,八成问题都能从日志里找到线索。