1. 设备树基础概念

1.1 什么是设备树(Device Tree)

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用节点(node)和属性(property)的树形层次来向操作系统内核传递平台硬件信息。在嵌入式 Linux 系统中,设备树文件通常以 .dts(Device Tree Source)为扩展名,经过编译生成 .dtb(Device Tree Blob)二进制文件,由 Bootloader 加载并传递给内核。

设备树的核心思想是:将硬件描述从内核代码中剥离出来,使同一份内核镜像可以支持多种硬件平台,而无需为每种板卡重新编译内核。

关键术语速查表
术语 全称 说明
DTS Device Tree Source 设备树源文件,文本格式,由开发者编写
DTC Device Tree Compiler 设备树编译器,将 DTS 编译为 DTB
DTB Device Tree Blob 设备树二进制文件,供内核解析
DTBO Device Tree Blob Overlay 设备树叠加层,用于运行时动态修改设备树
FDT Flattened Device Tree 扁平化设备树,DTB 在内存中的展开形式

一个典型的设备树节点结构如下:

/dts-v1/;

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

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

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

    leds {
        compatible = "gpio-leds";
        led0 {
            label = "beaglebone:green:usr0";
            gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "heartbeat";
        };
    };
};
提示:设备树中的 compatible 属性是内核匹配驱动的最关键字段,格式通常为 "厂商,型号",内核通过该字符串查找对应的 platform_driver。

1.2 为什么需要设备树

在设备树出现之前(Linux 2.6 ~ 3.x 早期),ARM 架构的嵌入式平台采用 board file 方式描述硬件。每个板卡对应一个 arch/arm/mach-xxx/board-xxx.c 文件,其中硬编码了 GPIO、中断、时钟、I2C 设备等所有硬件信息。这种方式带来了严重的问题:

  • 代码冗余:同一 SoC 的不同板卡需要大量重复的 board file,内核源码树中 board file 数量爆炸式增长。
  • 维护困难:每次新增板卡都需要修改内核源码,无法做到内核镜像与硬件平台解耦。
  • 社区分裂:ARM 架构的板级代码占用了内核维护者大量精力,导致主线内核对新硬件的支持滞后。
  • 启动参数混乱:硬件资源(如 GPIO 编号、中断号)在 board file 中硬编码,不同板卡之间无法复用。
历史教训:在 Linux 3.x 时代,ARM 架构的 board file 数量超过 2000 个,每次内核发布都有大量板级代码需要同步更新,维护成本极高。设备树的引入彻底改变了这一局面。

设备树解决了上述问题的核心路径:

  1. 硬件描述与内核代码分离:硬件信息写在 .dts 文件中,内核只负责解析,不再硬编码板级细节。
  2. 单一内核镜像支持多平台:Bootloader 根据实际硬件加载对应的 .dtb 文件,内核无需重新编译。
  3. 社区统一规范:设备树规范由 ePAPR(Embedded Power Architecture Platform Requirements)标准定义,后被 Devicetree.org 接管,成为嵌入式 Linux 硬件描述的事实标准。
  4. 驱动可移植性提升:驱动通过设备树中的 compatible 字符串匹配硬件,不再依赖板级代码中的静态定义。
设备树解决的核心问题:硬件描述与内核解耦 传统方式:Board File board-am335x-evm.c board-beaglebone.c board-phycore.c board-xxx.c ... (2000+) ❌ 代码冗余 · 维护困难 · 不可复用 设备树方式:Device Tree am335x-evm.dts am335x-boneblack.dts am335x-phycore.dts am335x-xxx.dts ... (N 个) ✅ 硬件描述分离 · 内核镜像复用 · 社区统一 同一份内核镜像 + 不同的 .dtb = 支持不同的硬件平台

1.3 设备树与 ACPI 的区别

ACPI(Advanced Configuration and Power Interface)是 x86/x86_64 平台广泛使用的硬件描述和电源管理标准。设备树与 ACPI 虽然都承担着向操作系统传递硬件信息的职责,但二者在设计哲学、使用场景和实现方式上有本质区别。

对比维度 设备树(Device Tree) ACPI
主要平台 ARM、RISC-V、PowerPC 等嵌入式平台 x86/x86_64、ARM64(服务器)
设计哲学 静态硬件描述,编译时确定,启动时加载 动态硬件枚举,支持热插拔、电源管理、运行时配置
数据格式 树形节点 + 属性,文本源文件(DTS)编译为二进制(DTB) AML(ACPI Machine Language)字节码,由 BIOS/UEFI 生成
可编程性 无运行时脚本能力,纯数据描述 AML 是一种解释执行的虚拟机语言,可执行控制方法
电源管理 依赖驱动和 PM 框架,设备树本身不提供电源管理逻辑 内置完整的电源管理状态机(C-states、P-states、D-states)
热插拔支持 原生不支持,需借助 Overlay 或驱动层实现 原生支持 PCIe、USB 等总线热插拔事件处理
硬件枚举时机 Bootloader 加载 DTB,内核启动时一次性解析 UEFI 固件在启动时构建 ACPI 表,内核按需解析
社区与标准 Devicetree.org 维护,ePAPR 规范 UEFI Forum 维护,ACPI 规范
驱动匹配方式 compatible 字符串匹配 HID(Hardware ID)和 CID(Compatible ID)匹配
核心差异总结:设备树是“静态硬件清单”,ACPI 是“动态硬件管理框架”。设备树适合硬件固定、资源有限的嵌入式系统;ACPI 适合需要复杂电源管理和热插拔的通用计算平台。

在实际工程中,选择设备树还是 ACPI 通常由平台架构决定:

  • ARM 嵌入式(Cortex-A/M 系列):几乎全部使用设备树,包括 NXP i.MX、TI AM335x、Rockchip RK3588、Allwinner 等。
  • x86 桌面/服务器:强制使用 ACPI,设备树在 x86 平台极少使用。
  • ARM64 服务器:部分平台(如 Ampere Altra、华为鲲鹏)同时支持 ACPI 和设备树,但 ACPI 是首选。
  • RISC-V:目前以设备树为主,ACPI 支持正在推进中。
设备树 vs ACPI:平台适用场景对比 设备树适用场景 ARM Cortex-A/M 嵌入式系统 RISC-V 开发板与 SoC PowerPC 嵌入式平台 FPGA 软核处理器(MicroBlaze/Nios II) 硬件固定 · 资源受限 · 无需热插拔 ACPI 适用场景 x86/x86_64 桌面与笔记本 x86/x86_64 服务器与数据中心 ARM64 服务器(首选) 需要复杂电源管理的嵌入式 x86 平台 热插拔 · 电源管理 · 运行时配置 选择依据:平台架构 + 硬件复杂度 + 电源管理需求

1.4 设备树在嵌入式 Linux 启动流程中的位置

理解设备树在整个系统启动流程中的角色,有助于把握其重要性。一个典型的嵌入式 Linux 启动流程如下:

  1. ROM Bootloader:芯片上电后执行固化在 ROM 中的启动代码,加载 SPL(Secondary Program Loader)或第一阶段 Bootloader。
  2. SPL / U-Boot SPL:初始化 DDR 时钟和基本外设,加载完整的 U-Boot。
  3. U-Boot(或其它 Bootloader):完成更复杂的硬件初始化,从存储介质(eMMC、SD 卡、NAND Flash)读取内核镜像(zImage 或 Image)和设备树文件(.dtb)。
  4. 内核启动:U-Boot 将内核镜像和设备树加载到内存中,设置启动参数,跳转到内核入口点。
  5. 内核解析设备树:内核在启动早期调用 unflatten_device_tree() 将 DTB 展开为内核内部的 device_node 树,然后遍历节点创建 platform_device、i2c_client、spi_device 等。
  6. 驱动匹配与初始化:总线驱动根据设备树中的 compatible、reg、interrupts 等属性匹配对应的驱动,完成硬件初始化。
关键点:设备树是内核与 Bootloader 之间的“硬件契约”。Bootloader 负责提供正确的 DTB,内核负责正确解析。如果 DTB 与硬件不匹配,内核可能无法正确初始化外设,甚至无法启动。
设备树在嵌入式 Linux 启动流程中的位置 ROM Bootloader SPL / U-Boot SPL U-Boot Linux 内核 用户空间 加载内核镜像 + .dtb 到内存 解析 DTB → 创建设备 → 匹配驱动 设备树是 Bootloader 与内核之间的硬件信息传递桥梁

1.5 本章小结