2. DTS、DTC、DTB、DTBO 文件格式详解:源文件、编译器、二进制文件、叠加层的关系与编译流程
在嵌入式 Linux 开发中,设备树(Device Tree)是描述硬件资源的标准化数据结构。理解其文件格式与编译流程,是编写正确设备树的基础。本章将深入剖析 DTS、DTC、DTB、DTBO 四种文件类型的定义、关系及编译过程。
2.1 文件格式概述
设备树体系包含四种核心文件类型,它们分别对应源代码、编译器、二进制镜像和运行时叠加层。下表总结了每种文件的核心特征:
| 文件类型 | 全称 | 本质 | 可读性 | 用途 |
|---|---|---|---|---|
| DTS | Device Tree Source | 文本源文件 | 人类可读 | 编写硬件描述 |
| DTC | Device Tree Compiler | 编译工具 | 不可读 | 将 DTS 编译为 DTB |
| DTB | Device Tree Blob | 二进制文件 | 机器可读 | Bootloader 传递给内核 |
| DTBO | Device Tree Blob Overlay | 二进制叠加层 | 机器可读 | 运行时动态修改 DTB |
2.2 文件格式详解
2.2.1 DTS(Device Tree Source)
DTS 是设备树的源文件,采用树形结构描述硬件。它使用大括号表示层级,分号结束语句,支持 #include 预处理指令。一个典型的 DTS 文件包含根节点、CPU 节点、内存节点、外设节点等。
// 示例:simple.dts
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
model = "Example Board";
compatible = "vendor,example-board";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB
};
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
interrupts = <0 5 4>;
status = "okay";
};
};
2.2.2 DTC(Device Tree Compiler)
DTC 是设备树的编译工具,通常集成在 Linux 内核源码的 scripts/dtc/ 目录下。它负责将 DTS 源文件解析、检查并生成 DTB 二进制文件。DTC 支持以下关键功能:
- 语法检查: 检测节点名称、属性格式、标签引用等错误。
- 宏展开: 处理
#include和#define预处理指令。 - 叠加层生成: 通过
-@标志生成支持叠加的 DTB。 - 反编译: 使用
-I dtb -O dts将 DTB 还原为 DTS。
2.2.3 DTB(Device Tree Blob)
DTB 是设备树的二进制表示,由 DTC 编译生成。它采用扁平化结构,包含节点、属性、字符串表和内存保留映射。Bootloader(如 U-Boot)在启动内核前,将 DTB 加载到内存中,并将地址传递给内核。内核解析 DTB 后,根据其中的硬件描述初始化驱动。
2.2.4 DTBO(Device Tree Blob Overlay)
DTBO 是设备树叠加层文件,用于在运行时动态修改基础 DTB。它包含 __overlay__ 和 __fixups__ 等特殊节点,通过符号引用与基础 DTB 合并。典型应用场景包括:FPGA 动态加载、外设热插拔、多板卡兼容。
// 示例:overlay.dts
/dts-v1/;
/plugin/;
&uart0 {
status = "disabled";
};
&gpio_leds {
led-extra {
label = "extra:green:user";
gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
};
};
2.3 编译流程与关系
从 DTS 到 DTB 的编译流程是一个多阶段过程,涉及预处理、解析、检查和二进制生成。叠加层(DTBO)的编译需要额外的 -@ 标志和符号表支持。下图展示了完整的编译流程与文件关系:
2.4 编译命令与实战
在实际开发中,DTC 编译器的使用非常频繁。以下列出最常用的编译命令及其参数含义:
| 命令 | 说明 | 典型参数 |
|---|---|---|
dtc -I dts -O dtb -o output.dtb input.dts |
将 DTS 编译为 DTB | -I 输入格式,-O 输出格式,-o 输出文件 |
dtc -I dtb -O dts -o output.dts input.dtb |
反编译 DTB 为 DTS | 用于调试或查看二进制内容 |
dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts |
编译叠加层 DTBO | -@ 生成符号表,支持叠加 |
dtc -I dts -O dtb -o output.dtb -@ input.dts |
编译支持叠加的基础 DTB | 基础 DTB 也需要 -@ 才能接收叠加 |
-@ 标志。否则,运行时合并会因缺少符号表而失败。检查 DTB 是否支持叠加,可使用 fdtdump 工具查看是否包含 __symbols__ 节点。
2.5 运行时合并机制
DTBO 与基础 DTB 的合并发生在内核启动后或运行时,通过 configfs 文件系统实现。合并流程如下:
- 加载基础 DTB: Bootloader 将基础 DTB 传递给内核,内核解析并建立初始设备树。
- 加载 DTBO: 用户空间通过
configfs将 DTBO 文件写入/sys/kernel/config/device-tree/overlays/目录。 - 符号解析: 内核根据 DTBO 中的
__fixups__节点,在基础 DTB 的__symbols__中查找对应符号。 - 节点合并: 将 DTBO 中的
__overlay__节点内容合并到基础 DTB 的对应位置。 - 驱动重绑定: 合并完成后,内核触发受影响设备的驱动重绑定,使新硬件生效。
fdtoverlay 工具(dtc 工具集的一部分)在用户空间预验证叠加层。
2.6 常见问题与避坑指南
- 编译错误: 检查 DTS 语法,特别是分号、大括号和标签引用。使用
dtc -I dts -O dtb -o /dev/null input.dts进行语法检查。 - 叠加层不生效: 确认基础 DTB 和 DTBO 都使用
-@编译。使用fdtdump检查__symbols__和__fixups__节点是否存在。 - 内存地址冲突: 叠加层中的
reg属性不能与基础 DTB 中的已有区域重叠,否则内核会拒绝合并。 - 驱动未加载: 叠加层添加的新节点需要对应的驱动支持。确保驱动编译进内核或作为模块加载。
dtc -I dtb -O dts -o debug.dts merged.dtb 将合并后的 DTB 反编译为 DTS,可以直观地查看叠加层是否正确合并。这是排查叠加层问题的“杀手锏”。