2. DTS、DTB、DTC 关系:DTS 源文件、DTB 二进制文件、DTC 编译器的概念与工作流程

在嵌入式 Linux 开发中,设备树(Device Tree)是描述硬件资源的标准化数据结构。理解 DTS(源文件)、DTB(二进制文件)和 DTC(编译器)三者之间的关系,是掌握设备树机制的第一步。本章将系统梳理三者的定义、差异以及完整的编译工作流程。

2.1 核心概念定义

设备树的开发流程类似于 C 语言的“源文件 → 编译 → 二进制”模型。下表对比了三者的核心定位:

术语 全称 本质 可读性 用途
DTS Device Tree Source 文本源文件 人类可读 开发者编写硬件描述
DTB Device Tree Blob 二进制数据块 机器可读 Bootloader 传递给内核
DTC Device Tree Compiler 编译工具 — 将 DTS 编译为 DTB
关键认知: DTS 是“图纸”,DTC 是“施工队”,DTB 是“成品”。内核启动时直接读取 DTB,不接触 DTS。

2.2 DTS 源文件详解

DTS 文件采用树形结构描述硬件节点,每个节点包含属性(property)和子节点。一个典型的 DTS 文件片段如下:

/dts-v1/;

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am335x";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a8";
            reg = <0x0>;
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;  /* 256 MB */
    };
};
  • /dts-v1/; — 版本标识,必须位于文件头部。
  • / — 根节点,所有硬件节点均挂载其下。
  • model / compatible — 描述板级型号与兼容性。
  • reg — 地址与大小信息,是节点最重要的属性之一。

2.3 DTB 二进制文件结构

DTB 是扁平化的二进制数据,由 DTC 编译生成。其内部结构包含三个主要区域:

  1. 头部(Header) — 魔数、大小、版本号、偏移量等信息。
  2. 结构块(Structure Block) — 以 token 形式存储节点和属性的树形结构。
  3. 字符串块(Strings Block) — 存储属性名称的字符串表,节省空间。
实际调试技巧: 使用 dtc -I dtb -O dts -o dump.dts board.dtb 可将 DTB 反编译为 DTS,用于验证编译结果或逆向分析。

2.4 DTC 编译器工作流程

DTC 不仅支持 DTS → DTB 的正向编译,还支持 DTB → DTS 的反向反编译,以及 DTS/DTB 与文件系统(fs)格式之间的转换。其标准工作流程如下:

DTC 编译器工作流程 DTS 源文件 *.dts CPP 预处理 展开 #include / #define DTC 编译器 词法/语法/语义分析 DTB 二进制 *.dtb 反编译 DTS 说明: DTC 支持 -I dts -O dtb(编译)和 -I dtb -O dts(反编译)。 预处理阶段由 C 预处理器(cpp)完成宏展开和头文件包含。

2.5 完整编译命令示例

在实际开发中,最常用的 DTC 命令如下:

# 正向编译:DTS → DTB
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 反编译:DTB → DTS
dtc -I dtb -O dts -o myboard.dts myboard.dtb

# 生成可读的 DTS 文件(带标签和注释)
dtc -I dtb -O dts -o myboard.dts -@ myboard.dtb

# 检查 DTB 合法性
dtc -I dtb -O dtb -o /dev/null myboard.dtb
常见错误: 如果 DTS 中包含 #include 指令,必须使用 -i 参数指定头文件搜索路径,否则预处理阶段会失败。例如:dtc -I dts -O dtb -i ./include -o output.dtb input.dts。

2.6 三者在启动流程中的角色

从源码到内核启动,DTS/DTB/DTC 的完整链路如下:

  1. 开发阶段: 开发者编写 .dts 和 .dtsi(公共头文件)描述硬件。
  2. 编译阶段: 构建系统调用 DTC 将 DTS 编译为 DTB,通常集成在 Linux 内核的 make dtbs 目标中。
  3. 烧录阶段: DTB 被烧录到存储介质(如 eMMC、NAND Flash)或与内核镜像打包在一起。
  4. 启动阶段: Bootloader(如 U-Boot)从存储介质加载 DTB 到内存,并将 DTB 地址传递给内核。
  5. 内核阶段: 内核解析 DTB,根据节点信息注册平台设备、初始化驱动。
设备树在启动流程中的传递链路 DTS 源文件 开发编写 DTC 编译 构建系统 DTB 二进制 存储介质 Bootloader 加载到内存 内核 解析 关键路径说明: 1. DTS 是人工编写的硬件描述,经过 DTC 编译生成 DTB。 2. Bootloader 负责从 Flash/网络加载 DTB 到内存,并通过寄存器或固定地址传递给内核。 3. 内核启动早期(setup_arch)解析 DTB,创建设备树结构,驱动模型据此匹配设备。

2.7 常见问题与避坑指南

问题 原因 解决方案
编译报错“syntax error” DTS 文件缺少分号或括号不匹配 使用 dtc -I dts -O dtb -o /dev/null file.dts 检查语法
内核启动时“FDT”相关错误 DTB 与内核版本不匹配或地址传递错误 确认 Bootloader 中 fdt_addr 设置正确
反编译后节点丢失 DTB 编译时使用了 -@ 选项(保留符号) 反编译时也使用 -@ 选项
设备驱动无法匹配 compatible 字符串拼写错误 核对驱动源码中的 of_match_table
最佳实践: 在项目初期建立 Makefile 自动化编译流程,将 dtc -I dts -O dtb 命令集成到构建脚本中,避免手动编译带来的低级错误。