2. 设备树编译与反编译:DTC工具的使用,DTS到DTB的编译过程

设备树源文件(.dts)是人类可读的文本描述,而内核引导时实际使用的是二进制格式的设备树二进制文件(.dtb)。从 .dts 到 .dtb 的转换,以及逆向的从 .dtb 到 .dts 的还原,都依赖于 DTC(Device Tree Compiler) 工具。本章将深入解析 DTC 的编译流程、关键选项以及常见操作。

2.1 DTC 工具概述

DTC 是设备树编译器的核心实现,通常作为 Linux 内核源码树的一部分提供(位于 scripts/dtc/ 目录)。它负责将文本格式的 .dts 文件编译为扁平化的二进制 .dtb 文件,同时支持反编译操作。

提示: 在大多数嵌入式 Linux 开发环境中,DTC 已预装在主机系统中。若未安装,可通过 apt-get install device-tree-compiler(Debian/Ubuntu)或 yum install dtc(Fedora/RHEL)快速获取。

2.1.1 DTC 的核心功能

功能 命令示例 说明
编译(DTS → DTB) dtc -I dts -O dtb -o output.dtb input.dts 将文本源文件编译为二进制设备树
反编译(DTB → DTS) dtc -I dtb -O dts -o output.dts input.dtb 将二进制设备树还原为可读的文本源文件
生成汇编(DTS → S) dtc -I dts -O asm -o output.S input.dts 生成汇编代码,用于早期启动阶段
检查与调试 dtc -I dts -O dtb -o /dev/null input.dts 仅检查语法错误,不输出文件

2.2 DTS 到 DTB 的编译流程

编译过程并非简单的文本到二进制的转换,而是经过多个阶段的处理,包括预处理、解析、展开和扁平化。下图展示了完整的编译流水线:

DTS 到 DTB 编译流水线 .dts 源文件 .dtsi 包含文件 CPP 预处理 DTC 解析器 展开 /include/ FDT 扁平化 .dtb 二进制 关键说明: CPP 预处理展开 #include 和宏定义;DTC 解析器构建抽象语法树; 展开阶段合并 /include/ 文件;扁平化阶段生成 FDT 格式的二进制数据。

2.2.1 编译阶段详解

  1. 预处理阶段: DTC 调用 C 预处理器(CPP)处理 #include、#define 等指令。所有 .dtsi 文件在此阶段被展开并嵌入到主文件中。
  2. 语法解析阶段: DTC 解析器读取预处理后的文本流,构建抽象语法树(AST)。此阶段检查节点、属性、标号等语法正确性。
  3. 展开与合并阶段: 处理 /include/ 伪指令(与 CPP 的 #include 不同),合并多个设备树片段。同时解析 &label 引用,将覆盖节点与基节点合并。
  4. 扁平化阶段: 将树形结构转换为扁平设备树(FDT)格式,生成二进制 .dtb 文件。此阶段包括内存布局优化、字符串表压缩等操作。
关键概念: /include/ 与 #include 的区别在于:#include 由 CPP 处理,支持宏定义;/include/ 由 DTC 直接处理,仅做文本插入,不支持宏展开。现代设备树推荐统一使用 #include。

2.3 DTC 常用选项与参数

DTC 提供了丰富的命令行选项,用于控制编译行为、输出格式和调试信息。下表列出了最常用的选项:

选项 完整参数 功能描述
-I --in-format 指定输入格式:dts、dtb、fs(文件系统)
-O --out-format 指定输出格式:dts、dtb、asm、yaml
-o --out 指定输出文件名
-@ --symbols 生成符号节点(用于设备树叠加层)
-H --phandle 指定 phandle 生成策略:epapr、linux、both
-W --warning 启用特定警告,如 -W unit_address_vs_reg
-E --error 将特定警告视为错误,如 -E graph_child_address
-V --version 指定 DTB 版本(16 或 17)

2.4 编译与反编译实战

2.4.1 基本编译操作

以下命令将 imx6ull.dts 编译为 imx6ull.dtb,并启用所有警告:

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

# 带警告检查的编译
dtc -I dts -O dtb -o imx6ull.dtb imx6ull.dts -W all

# 生成带符号的 DTB(支持叠加层)
dtc -I dts -O dtb -o imx6ull.dtb imx6ull.dts -@

2.4.2 反编译操作

反编译用于调试或分析已有的 .dtb 文件:

# 基本反编译
dtc -I dtb -O dts -o imx6ull_reverse.dts imx6ull.dtb

# 反编译并保留注释(如果原始 DTB 包含)
dtc -I dtb -O dts -o imx6ull_reverse.dts imx6ull.dtb -H epapr
注意: 反编译生成的 .dts 文件会丢失原始源文件中的注释、宏定义和代码格式。反编译结果仅用于调试,不应作为修改设备树的唯一依据。

2.4.3 使用 Makefile 集成编译

在内核源码树中,设备树编译通常通过 Makefile 自动完成。以下是一个典型的编译流程:

# 在内核源码根目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

# 单独编译某个设备树
make ARCH=arm imx6ull-14x14-evk.dtb

# 查看编译过程详细输出
make ARCH=arm dtbs V=1

2.5 编译错误与调试

DTC 编译过程中常见的错误类型及其解决方法:

错误类型 典型信息 原因与解决
语法错误 syntax error before '...' 检查分号、花括号、引号是否匹配;属性值格式是否正确
引用错误 Reference to non-existent node or label "..." &label 引用的节点未定义;检查 .dtsi 是否包含
地址格式错误 reg property size mismatch reg 属性中的地址长度与 #address-cells 不匹配
中断映射错误 interrupt-map length mismatch 中断映射表长度与 #interrupt-cells 定义不一致
重复节点 Duplicate label "..." 同一作用域内出现重复的标号定义;检查 .dtsi 包含冲突

2.6 DTB 文件结构分析

编译生成的 .dtb 文件遵循 FDT(Flattened Device Tree)格式,其内部结构如下:

DTB 文件内部结构 FDT 头部(Header)— 40 字节 内存预留块(Memory Reservation Block) 结构块(Structure Block)— 节点与属性树 字符串块(Strings Block)— 属性名字符串表 每个块起始地址按 4 字节对齐;结构块以 FDT_BEGIN_NODE / FDT_END_NODE 标记节点边界

2.6.1 头部字段说明

FDT 头部包含 40 字节的元数据,用于解析整个设备树:

偏移 字段名 大小 说明
0x00 magic 4 字节 魔数,固定为 0xD00DFEED
0x04 totalsize 4 字节 整个 DTB 文件的总大小(字节)
0x08 off_dt_struct 4 字节 结构块相对于文件头的偏移
0x0C off_dt_strings 4 字节 字符串块相对于文件头的偏移
0x10 off_mem_rsvmap 4 字节 内存预留块相对于文件头的偏移
0x14 version 4 字节 DTB 版本号(当前为 17)
0x18 last_comp_version 4 字节 向后兼容的最低版本号(通常为 16)
0x1C boot_cpuid_phys 4 字节 引导 CPU 的物理 ID
0x20 size_dt_strings 4 字节 字符串块的大小(字节)
0x24 size_dt_struct 4 字节 结构块的大小(字节)

2.7 设备树叠加层编译

设备树叠加层(Device Tree Overlay)允许在运行时动态修改设备树。编译叠加层时需要特殊处理:

# 编译叠加层源文件(.dtso → .dtbo)
dtc -I dts -O dtb -o my_overlay.dtbo my_overlay.dtso -@

# 检查叠加层是否包含必要的符号
dtc -I dtb -O dts my_overlay.dtbo | grep -E "(__symbols__|__fixups__)"
最佳实践: 叠加层文件使用 .dtso 扩展名以区别于普通设备树。编译时必须使用 -@ 选项生成符号表,否则内核无法解析叠加层中的引用。

2.8 本章小结

DTC 工具是设备树开发的核心环节,掌握其编译与反编译操作是嵌入式 Linux 开发的基本功。通过本章学习,应重点理解:

  • DTS 到 DTB 的完整编译流水线,包括预处理、解析、展开和扁平化四个阶段
  • DTC 命令行选项的灵活运用,特别是 -I、-O、-@ 等关键参数
  • DTB 文件的内部结构,包括头部、内存预留块、结构块和字符串块的组织方式
  • 常见编译错误的识别与修复方法
  • 设备树叠加层的编译要求与符号生成机制