4. 内核编译与设备树编译:内核编译流程、设备树(DTS)编译、生成dtb文件

在嵌入式Linux驱动开发中,内核编译与设备树编译是构建运行环境的核心环节。内核编译生成可执行的二进制内核映像,而设备树编译则将描述硬件拓扑的DTS源文件转换为二进制DTB文件,供内核在启动时解析。本节将系统梳理这两条编译流水线,并重点讲解设备树编译的语法检查、依赖关系与生成物。

4.1 内核编译流程概览

内核编译并非简单的“make”命令,而是一套包含配置、依赖解析、编译、链接、模块打包的多阶段流程。下表总结了标准内核编译的主要阶段及其产出物:

阶段命令/操作关键产出说明
配置make menuconfig / make defconfig.config选择架构、驱动、文件系统等选项
依赖生成make olddefconfig / make syncconfiginclude/config/*.h将配置选项展开为C头文件宏定义
编译make / make -jN*.o 目标文件逐文件编译内核源码与驱动模块
链接make 自动执行vmlinux (ELF)未压缩的内核映像,含调试符号
压缩与打包make 自动执行arch/arm/boot/zImage / Image.gz生成目标架构可引导的内核映像
模块编译make modules*.ko可动态加载的内核模块
模块安装make modules_install/lib/modules/$(uname -r)/将模块复制到目标根文件系统
实践要点: 在嵌入式开发中,通常使用交叉编译工具链。需在顶层Makefile或环境变量中设置 ARCH=arm 和 CROSS_COMPILE=arm-linux-gnueabihf-,否则编译出的内核无法在目标硬件上运行。

4.2 设备树编译:从DTS到DTB

设备树编译是独立于内核编译的步骤,但通常集成在内核构建系统中。设备树源文件(.dts)通过设备树编译器(dtc)生成二进制设备树Blob(.dtb)。其编译流程包含预处理、语法解析、符号展开和二进制输出四个阶段。

4.2.1 编译命令与参数

标准设备树编译命令如下:

# 基本编译
dtc -I dts -O dtb -o my_board.dtb my_board.dts

# 保留符号信息(调试用)
dtc -I dts -O dtb -@ -o my_board.dtb my_board.dts

# 在内核源码树中编译(推荐)
make ARCH=arm dtbs

参数说明:

  • -I dts:指定输入格式为DTS文本。
  • -O dtb:指定输出格式为DTB二进制。
  • -o:指定输出文件名。
  • -@:生成符号节点,支持设备树叠加层(DTO)。
  • make dtbs:自动扫描 arch/arm/boot/dts/ 目录下的所有DTS文件并编译。

4.2.2 设备树编译的依赖关系

一个完整的设备树源文件通常包含以下依赖:

// my_board.dts
#include "soc.dtsi"          // SoC级定义(CPU、中断控制器、时钟等)
#include "my_board.dtsi"     // 板级公共定义(内存映射、外设基地址)
#include "my_board-pinctrl.dtsi" // 引脚复用配置

/ {
    model = "My Custom Board";
    compatible = "vendor,my-board", "vendor,soc";

    &uart1 {
        status = "okay";
        pinctrl-0 = &uart1_pins;
    };

    &i2c2 {
        status = "okay";
        clock-frequency = <400000>;
        eeprom@50 {
            compatible = "atmel,24c02";
            reg = <0x50>;
        };
    };
};

编译时,dtc 会先展开 #include 包含的头文件和 .dtsi 文件,然后解析标签(如 &uart1)并合并节点,最后进行语法和语义检查。

常见错误: 如果DTS文件中引用了未定义的标签(例如 &i2c2 但 i2c2 未在 .dtsi 中声明),dtc 会报错 FATAL ERROR: Cannot find node 'i2c2'。此时需要检查 #include 路径是否正确,或确认SoC的 .dtsi 文件是否包含了该外设节点。

4.3 设备树编译流程SVG图

下图展示了从DTS源文件到DTB二进制文件的完整编译流程,包含预处理、解析、合并与输出四个阶段:

设备树编译流程 设备树编译流程(DTS → DTB) 阶段1:预处理 展开 #include / #define 阶段2:语法解析 词法/语法分析 阶段3:符号展开 标签解析 & 节点合并 阶段4:二进制输出 生成 .dtb 文件 输入文件 .dts / .dtsi / .h 输出文件 .dtb (二进制) 说明: • 预处理阶段展开 #include 和 #define,生成完整的DTS文本。 • 符号展开阶段解析 &label 语法,将引用节点合并到根节点树中。 • 最终输出为扁平二进制格式,包含所有节点、属性和字符串表。

4.4 内核编译与设备树编译的集成

在实际工程中,设备树编译通常作为内核编译的一部分执行。内核的构建系统(Kbuild)会自动处理设备树的编译依赖。以下是集成编译的典型流程:

  1. 配置内核:make ARCH=arm menuconfig,确保选中目标平台和设备树支持。
  2. 编译内核与设备树:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage dtbs。
  3. 检查生成物:ls -l arch/arm/boot/zImage arch/arm/boot/dts/*.dtb。
  4. 烧录与验证:将 zImage 和对应的 .dtb 文件烧录到目标板,通过串口观察内核启动日志。
关键路径: 内核编译时,make dtbs 会调用 scripts/dtc/dtc 编译器。如果修改了DTS文件,只需重新执行 make dtbs 即可,无需重新编译整个内核,大幅缩短迭代周期。

4.5 设备树编译常见问题与排查

错误现象可能原因解决方案
FATAL ERROR: Cannot find node 'xxx'引用了未定义的标签检查 .dtsi 是否包含该节点,或标签名拼写错误
ERROR: Input tree has errors语法错误(缺少分号、括号不匹配)使用 dtc -I dts -O dtb -o /dev/null my.dts 仅做语法检查
WARNING: unit name should not have a leading 0节点单元地址有前导零将 eeprom@050 改为 eeprom@50
DTC: dts->dtb on file "xxx.dts" 但未生成 .dtb输出路径权限或磁盘空间不足检查 arch/arm/boot/dts/ 目录写权限
内核启动时提示 No device tree found引导加载程序未加载DTB确认U-Boot中 fdt addr 和 bootm 命令正确传递DTB地址

4.6 实践:手动编译与反编译设备树

为了深入理解设备树编译过程,建议进行手动编译与反编译练习:

# 1. 编译DTS为DTB
dtc -I dts -O dtb -o test.dtb test.dts

# 2. 反编译DTB为DTS(验证编译正确性)
dtc -I dtb -O dts -o test_decompiled.dts test.dtb

# 3. 比较原始DTS与反编译DTS的差异
diff test.dts test_decompiled.dts

反编译后的DTS文件会丢失注释和部分格式,但节点结构、属性值和标签引用应完全一致。如果出现差异,说明原始DTS可能存在语法歧义或编译器优化行为。

调试技巧: 使用 dtc -I dts -O dtb -@ -o test.dtb test.dts 生成带符号的DTB,然后通过 fdtdump test.dtb 查看设备树的完整内存布局,包括字符串表、结构块和保留区。

4.7 本章小结