1. 设备树基础概念

1.1 什么是设备树 (Device Tree)

设备树(Device Tree,简称 DT)是一种描述硬件资源的数据结构,它采用树状层次结构来表述嵌入式系统中 CPU、内存、外设、总线、中断控制器等硬件组件及其连接关系。设备树源文件(.dts)经过设备树编译器(dtc)编译后生成设备树二进制文件(.dtb),由 Bootloader 在引导内核时传递给操作系统。

设备树的核心思想是:将硬件描述从内核代码中剥离出来,形成独立的数据文件,使得同一份内核镜像可以支持多种硬件平台,无需为每块开发板重新编译内核。

关键术语速查:
  • DTS (Device Tree Source) — 设备树源文件,文本格式,由开发者编写
  • DTC (Device Tree Compiler) — 设备树编译器,将 DTS 编译为 DTB
  • DTB (Device Tree Blob) — 设备树二进制文件,内核解析的最终格式
  • DTSI (Device Tree Source Include) — 设备树头文件,用于公共硬件描述复用
设备树编译与传递流程 设备树编译与传递流程 .dts 源文件 .dtsi 头文件 DTC 编译器 .dtb 二进制 Bootloader Linux 内核 💡 内核启动时通过解析 DTB 获取硬件拓扑,动态创建设备节点 无需为每块板子重新编译内核镜像

1.2 为什么需要设备树

在设备树出现之前,嵌入式 Linux 内核维护着大量 board-*.c 和 mach-*.c 文件,每个开发板都需要在源码中硬编码硬件信息。这种模式带来了严重问题:

问题维度 传统硬编码方式 设备树方式
内核维护 每块板子需独立 board.c,代码冗余 一份内核镜像适配多板,硬件描述外置
硬件变更 修改硬件需改内核源码并重新编译 仅修改 .dts 文件,重新编译 DTB
社区协作 厂商代码难以合入主线内核 设备树标准化,主线合入门槛降低
启动灵活性 Bootloader 无法动态调整硬件配置 Bootloader 可修改 DTB 或叠加 Overlay
驱动开发 驱动与板级信息耦合,复用性差 驱动通过设备树匹配,与板级解耦
实战经验: 在 ARM 嵌入式项目中,设备树已成为硬件描述的事实标准。从 Linux 3.x 开始,ARM 架构强制要求使用设备树,不再接受新的 board-*.c 文件。掌握 DTS 编写是嵌入式驱动开发者的必备技能。
传统硬编码 vs 设备树方式对比 传统硬编码 vs 设备树方式对比 传统硬编码方式 board-a.c board-b.c board-c.c board-d.c 每个板子独立编译内核 代码冗余,主线合入困难 硬件变更需改内核源码 设备树方式 板A.dts 板B.dts 板C.dts 板D.dts 同一份内核镜像 硬件描述外置,内核与板级解耦 修改硬件只需改 DTS,无需重编内核

1.3 设备树与 ACPI 的区别

ACPI(Advanced Configuration and Power Interface)是 x86 平台广泛使用的硬件描述和电源管理规范,与设备树在理念和实现上有显著差异。两者对比如下:

对比维度 设备树 (Device Tree) ACPI
主要应用平台 ARM、RISC-V、PowerPC 等嵌入式架构 x86、x86_64 服务器与 PC 平台
描述方式 树状结构,纯文本 DTS 源文件 表结构,ASL 源码编译为 AML 字节码
运行时能力 静态描述,Bootloader 传递 DTB 支持动态枚举、热插拔、电源管理
复杂度 轻量级,语法简洁,学习曲线平缓 重量级,规范庞大,实现复杂
内核集成 驱动通过 compatible 字符串匹配 驱动通过 ACPI ID 匹配
修改方式 修改 DTS 文件,重新编译 DTB 修改 ASL 表,重新编译 AML
社区生态 嵌入式社区主导,主线内核广泛支持 Windows/Linux 服务器生态,UEFI 绑定
注意: 虽然 ARM 服务器领域开始出现 ACPI 支持(如 SBSA/SBBR 规范),但在绝大多数 ARM 嵌入式 Linux 项目中,设备树仍然是唯一选择。学习 DTS 编写是进入嵌入式 Linux 驱动开发的必经之路。
设备树与 ACPI 架构对比 设备树与 ACPI 架构对比 设备树 (ARM 嵌入式) / (根节点) ├── cpu@0 ├── memory@80000000 └── serial@ff000000 静态树结构,Bootloader 传递 DTB ACPI (x86 服务器/PC) RSDP → RSDT → XSDT DSDT (定义块) SSDT (补充表) AML 字节码解释执行 动态枚举,支持热插拔与电源管理

1.4 设备树的基本语法结构

一个典型的设备树源文件包含以下核心元素:

/dts-v1/;

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

    chosen {
        stdout-path = &uart0;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;  /* 256 MB */
    };

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

    uart0: serial@44e09000 {
        compatible = "ti,am3352-uart", "ti,omap3-uart";
        reg = <0x44e09000 0x2000>;
        interrupts = <72>;
        status = "okay";
    };
};
语法要点:
  • /dts-v1/; — 版本标识,必须位于文件头部
  • / { }; — 根节点,所有设备节点都是其子节点
  • node-name@unit-address — 节点命名规范,@ 后跟寄存器基地址
  • compatible — 驱动匹配的关键属性,格式为 "厂商,型号"
  • reg — 描述地址空间,格式为 <基址 长度>
  • &label — 引用已定义的节点标签,用于追加或修改属性

1.5 设备树在嵌入式 Linux 启动中的角色

设备树在系统启动过程中扮演着硬件信息传递者的角色,其完整流程如下:

  1. Bootloader 阶段: U-Boot 等 Bootloader 从存储介质(eMMC、SD卡、Flash)加载 DTB 文件到内存
  2. DTB 调整: Bootloader 可根据环境变量或硬件检测修改 DTB(如修改 mac-address、使能/禁用节点)
  3. 内核引导: Bootloader 将 DTB 物理地址通过寄存器传递给内核(ARM 架构使用 r2 寄存器)
  4. 内核解析: 内核在启动早期调用 unflatten_device_tree() 将 DTB 展开为 device_node 树
  5. 驱动匹配: 总线驱动遍历设备树,通过 of_match_table 匹配 compatible 属性,触发驱动 probe
  6. 资源获取: 驱动通过 of_* API 读取设备树中的 reg、interrupts、gpios 等资源信息
调试技巧: 在 Linux 内核启动时,可以通过 earlycon 和 printk 日志观察设备树解析过程。使用 dtc -I dtb -O dts -o dump.dts board.dtb 命令可以将 DTB 反编译为可读的 DTS 格式,方便调试。
设备树在 Linux 启动流程中的角色 设备树在 Linux 启动流程中的角色 Bootloader 加载 DTB 到内存 DTB 传递 r2 寄存器传地址 内核解析 展开 device_node 驱动匹配 of_match_table 📌 启动流程关键点: 1. Bootloader 负责加载 DTB,并可动态修改(如设置 MAC 地址、内存大小) 2. 内核通过 unflatten_device_tree() 将扁平 DTB 转为树形结构 3. 驱动通过 compatible 属性匹配,实现硬件与驱动的解耦

1.6 本章小结

设备树是 ARM 嵌入式 Linux 开发中不可或缺的硬件描述机制,它将硬件信息从内核源码中解耦出来,实现了"一次编译,多处运行"的目标。与 ACPI 相比,设备树更加轻量、简洁,更适合资源受限的嵌入式场景。掌握设备树的基本概念、编译流程和语法结构,是后续深入学习 DTS 编写和驱动开发的基础。