1. 设备树基础概念

1.1 什么是设备树

设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形节点来记录系统中的处理器、内存、外设、中断控制器、时钟、GPIO 等硬件组件及其连接关系。设备树源文件(.dts)经过设备树编译器(dtc)编译为二进制设备树 Blob(.dtb),在系统启动时由 Bootloader 传递给内核。

核心要点:设备树本质上是“硬件拓扑的静态描述文件”,它让同一份内核镜像可以适配不同硬件平台,而无需重新编译内核。
设备树结构示意图 设备树结构示意图 / (根节点) cpus memory soc cpu@0 serial@1c000 i2c@1d000 gpio@1e000 每个节点包含属性(reg、compatible、interrupts 等),描述硬件资源的具体参数。 节点通过父子关系形成树状结构,根节点“/”是整棵树的起点。

1.2 为什么需要设备树

在设备树出现之前,Linux 内核使用大量的 板级文件(board file) 来定义硬件信息。每个 ARM 平台都需要在 arch/arm/mach-* 目录下编写数百行 C 代码来描述硬件。这种方式存在严重问题:

对比维度 传统板级文件方式 设备树方式
硬件描述位置 内核源码 arch/arm/mach-* 独立 .dts 文件,与内核解耦
适配新板卡 需修改内核源码,重新编译内核 只需编写新 .dts,编译 .dtb,无需改内核
内核镜像通用性 每个板卡需要专用内核镜像 同一内核镜像可启动不同板卡
社区维护成本 大量重复代码,难以维护 硬件描述集中管理,易于维护
启动流程 Bootloader 固定传递硬件参数 Bootloader 动态加载 .dtb 到内存
关键驱动因素:ARM 架构在 Linux 3.x 时代开始强制要求使用设备树,以解决“ARM 内核碎片化”问题。设备树使得 SoC 厂商只需提供 .dtsi 包含文件,板卡厂商通过 #include 方式复用,大幅降低硬件适配工作量。
设备树与传统方式对比流程图 传统方式 vs 设备树方式 传统方式:板级文件 修改 arch/arm/mach-xxx/board-xxx.c 重新编译内核,生成 zImage 每块板卡一个专用内核镜像 设备树方式 编写 myboard.dts,引用 soc.dtsi dtc 编译为 myboard.dtb 同一内核 + 不同 .dtb = 多板卡适配 vs

1.3 设备树在 Linux 内核中的角色

设备树在 Linux 内核启动和运行过程中扮演着 硬件信息提供者 的核心角色。内核通过设备树完成以下关键任务:

  1. 启动阶段硬件探测: 内核在启动早期解析 .dtb,获取内存布局、CPU 数量、中断控制器、定时器等基础硬件信息。
  2. 驱动匹配与绑定: 设备驱动通过 compatible 属性与设备树节点匹配,实现“驱动-设备”的自动绑定。
  3. 资源获取: 驱动从设备树节点读取 reg(寄存器地址)、interrupts(中断号)、clocks(时钟)等资源信息。
  4. 运行时动态配置: 内核提供 of_* 系列 API(如 of_property_read_u32),驱动可在运行时查询设备树属性。
注意:设备树是静态描述,不参与运行时硬件状态变更。如果硬件在运行过程中发生变化(如热插拔 PCIe 设备),设备树仅提供初始拓扑,动态变化由内核的运行时机制处理。
设备树在内核中的角色流程图 设备树在内核启动与运行中的角色 Bootloader 传递 .dtb 到内核入口 内核早期解析 .dtb 内存/CPU 初始化 中断/时钟/定时器 外设驱动匹配 驱动通过 of_match_table 匹配 compatible 属性,调用 probe 函数获取 reg、interrupts 等资源

1.4 设备树相关文件类型

文件扩展名 全称 说明
.dts Device Tree Source 设备树源文件,文本格式,描述具体板卡的硬件
.dtsi Device Tree Source Include 设备树包含文件,通常由 SoC 厂商提供,定义 SoC 公共硬件
.dtb Device Tree Blob 编译后的二进制文件,Bootloader 传递给内核
.dtbo Device Tree Blob Overlay 设备树叠加层,用于运行时动态修改设备树
实践建议:调试设备树时,最常用的命令是 dtc -I dtb -O dts -o output.dts input.dtb,可以将二进制 .dtb 反编译为可读的 .dts 源文件,便于检查节点结构和属性值。

1.5 一个简单的设备树示例

/dts-v1/;

/ {
    model = "Example Board v1";
    compatible = "vendor,example-board", "vendor,soc";

    #address-cells = <1>;
    #size-cells = <1>;

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;  /* 1GB RAM */
    };

    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        serial@1c000 {
            compatible = "vendor,uart";
            reg = <0x1c000 0x1000>;
            interrupts = <0 33 4>;
            clock-frequency = <24000000>;
        };

        gpio@1e000 {
            compatible = "vendor,gpio";
            reg = <0x1e000 0x1000>;
            interrupts = <0 34 4>;
            gpio-controller;
            #gpio-cells = <2>;
        };
    };
};
关键属性解读:
compatible:驱动匹配的关键字符串,格式通常为 "厂商,设备型号"。
reg:描述地址和大小,格式由 #address-cells 和 #size-cells 决定。
interrupts:描述中断号、触发类型等,格式依赖中断控制器。
ranges:定义父地址空间到子地址空间的映射关系。