4. 内核编译与设备树编译:内核编译流程、设备树(DTS)编译、生成dtb文件
在嵌入式Linux驱动开发中,内核编译与设备树编译是构建运行环境的核心环节。内核编译生成可执行的二进制内核映像,而设备树编译则将描述硬件拓扑的DTS源文件转换为二进制DTB文件,供内核在启动时解析。本节将系统梳理这两条编译流水线,并重点讲解设备树编译的语法检查、依赖关系与生成物。
4.1 内核编译流程概览
内核编译并非简单的“make”命令,而是一套包含配置、依赖解析、编译、链接、模块打包的多阶段流程。下表总结了标准内核编译的主要阶段及其产出物:
| 阶段 | 命令/操作 | 关键产出 | 说明 |
|---|---|---|---|
| 配置 | make menuconfig / make defconfig | .config | 选择架构、驱动、文件系统等选项 |
| 依赖生成 | make olddefconfig / make syncconfig | include/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二进制文件的完整编译流程,包含预处理、解析、合并与输出四个阶段:
4.4 内核编译与设备树编译的集成
在实际工程中,设备树编译通常作为内核编译的一部分执行。内核的构建系统(Kbuild)会自动处理设备树的编译依赖。以下是集成编译的典型流程:
- 配置内核:
make ARCH=arm menuconfig,确保选中目标平台和设备树支持。 - 编译内核与设备树:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage dtbs。 - 检查生成物:
ls -l arch/arm/boot/zImage arch/arm/boot/dts/*.dtb。 - 烧录与验证:将
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 查看设备树的完整内存布局,包括字符串表、结构块和保留区。