4. dtc 编译流程:从 DTS 到 DTB 的完整编译过程

设备树编译器(dtc)是将人类可读的 .dts 源文件转换为二进制 .dtb 设备树 Blob 的核心工具。理解 dtc 的内部编译流程,对于定位语法错误、宏展开异常以及二进制布局问题至关重要。dtc 的编译过程遵循经典编译器的三段式架构:词法分析 → 语法分析 → 代码生成。

核心要点: dtc 并非简单的文本替换工具,它包含完整的词法解析器(Lexer)和语法解析器(Parser),能够检测结构错误、类型不匹配以及节点/属性重复等语义问题。

4.1 编译流程总览

下图展示了从 .dts 源文件到 .dtb 二进制文件的完整编译流水线,以及中间产物与关键处理阶段。

dtc 编译流程总览 .dts 源文件 .dtsi 包含文件 C 预处理器 (cpp) 展开宏/包含 词法分析 (Lexer) 生成 Token 流 语法分析 (Parser) 构建 AST 语义检查 (节点/属性校验) 代码生成 (输出 DTB 二进制) .dtb 二进制文件 ⚠ 常见错误来源 语法错误、标签重复 属性值类型不匹配

4.2 预处理阶段(C 预处理器)

dtc 在正式编译前,首先调用 C 预处理器(cpp)对 .dts 文件进行预处理。这一阶段处理所有以 # 开头的指令,包括:

  • 文件包含: 展开 #include "soc.dtsi" 或 #include ,将外部头文件和 .dtsi 文件内容递归插入。
  • 宏展开: 替换 #define 定义的常量,例如 #define GPIO_ACTIVE_LOW 1 在属性值中被替换为数字 1。
  • 条件编译: 处理 #ifdef、#if、#endif 等指令,根据编译时定义的宏选择性地包含或排除代码块。
调试技巧: 使用 dtc -E 选项可以仅输出预处理后的 .dts 内容,而不进行后续编译。这是排查宏定义错误或包含路径问题的首选方法。

4.3 词法分析(Lexer)

预处理后的文本流进入词法分析器(由 lexer.l 定义)。词法分析器将字符序列分解为有意义的词法单元(Token)。dtc 的词法单元主要包括:

Token 类型 匹配模式 示例
DT_V1 /dts-v1/; 版本标识
LITERAL 节点名或属性名 cpu, compatible
NUMBER 十进制/十六进制整数 0x100, 42
STRING 双引号括起的字符串 "okay"
BYTES 尖括号内的字节序列 [01 02 03]
REF &label 引用 &cpu0
SEMICOLON ; 语句结束符
LBRACE/RBRACE { / } 节点作用域

词法分析器会跳过注释(// 和 /* */)和空白字符。如果遇到无法识别的字符序列,将产生 词法错误,例如未闭合的字符串或非法的特殊字符。

4.4 语法分析(Parser)

Token 流被送入语法分析器(由 yacc/bison 语法文件 dtc-parser.y 定义)。语法分析器根据设备树语法规则,将 Token 序列组织成抽象语法树(AST)。核心语法规则包括:

  • 源文件结构: /dts-v1/; 版本声明后跟一个或多个节点定义。
  • 节点定义: [label:] node-name [@unit-address] { properties; child-nodes; };
  • 属性赋值: property-name = value; 或 property-name;(空属性)。
  • 引用与合并: &label { ... }; 用于向已定义节点追加内容。

语法分析阶段会检测以下常见错误:

典型语法错误:
  • 缺少分号 ; 结束属性或节点。
  • 花括号 {} 不匹配。
  • 节点名包含非法字符(如空格、@ 后跟非十六进制字符)。
  • 属性值类型与期望不符(例如字符串赋值给需要 <u32> 的属性)。

4.5 语义检查与中间表示

在生成最终二进制之前,dtc 对 AST 进行多轮语义检查和规范化处理:

检查项 说明 错误示例
节点/属性唯一性 同一层级下不允许同名节点或属性 两个 cpu@0 节点
标签唯一性 标签(label)必须在全局唯一 cpu0: cpu@0 重复定义
引用解析 &label 必须指向已存在的标签 &nonexist 未定义标签
属性值类型 检查属性值是否符合绑定(binding)要求 reg = "string"; 应为 <u32>
地址/大小单元 验证 #address-cells 和 #size-cells 的合法性 负值或非整数

通过语义检查后,dtc 将 AST 转换为内部扁平化表示(Flattened Device Tree),这是一个线性化的结构,便于后续二进制编码。

4.6 代码生成(Code Generation)

最后阶段将内部表示序列化为 .dtb 二进制格式。dtb 文件由以下四部分组成:

  1. 头部(Header): 包含魔数(0xd00dfeed)、总大小、结构块和字符串块的偏移量及大小。
  2. 结构块(Structure Block): 以 FDT_BEGIN_NODE、FDT_PROP 等令牌(token)编码的节点和属性树形结构。
  3. 字符串块(Strings Block): 存储所有属性名和节点名的字符串池,结构块通过偏移量引用。
  4. 内存预留块(Memory Reservation Block): 记录操作系统不应使用的物理内存区域。
  5. 代码生成阶段还会执行以下优化:

    • 字符串去重: 相同的属性名(如 compatible、status)在字符串块中只存储一次。
    • 对齐填充: 结构块和字符串块按 4 字节对齐,确保内核访问效率。
    • 大小计算: 递归计算所有节点和属性的总大小,填充头部中的偏移量字段。
    编译选项速查:
    • dtc -I dts -O dtb -o output.dtb input.dts:标准编译。
    • dtc -O dts -o decompiled.dts input.dtb:反编译 dtb 为 dts。
    • dtc -O dtb -@ -o output.dtb input.dts:启用符号支持(overlay 编译)。
    • dtc -V:查看 dtc 版本,不同版本对语法严格度有差异。

    4.7 编译流程总结

    下表汇总了 dtc 编译各阶段的输入、输出及关键检查点:

    阶段 输入 输出 关键检查
    预处理 .dts + .dtsi + 头文件 展开后的文本流 包含路径、宏定义
    词法分析 文本流 Token 序列 非法字符、未闭合字符串
    语法分析 Token 序列 抽象语法树(AST) 分号、花括号、节点名格式
    语义检查 AST 扁平化内部表示 标签唯一性、引用有效性、类型匹配
    代码生成 内部表示 .dtb 二进制 对齐、大小、偏移量