1. 设备树基础概念:什么是设备树、为什么需要设备树、设备树在Linux内核中的角色

1.1 什么是设备树?一个很直白的解释

设备树,英文叫 Device Tree,缩写是 DT。说白了,它就是一份描述硬件信息的“清单”。

你想想看,一块板卡上有什么CPU、什么外设、中断怎么连、GPIO接在哪个引脚上——这些信息总得有个地方告诉内核吧?设备树就是干这个的。

我习惯把它比作“硬件的身份证”。内核启动时,通过读取这份身份证,就知道自己跑在什么硬件上,该初始化哪些驱动。

设备树的本质:一种描述硬件资源的数据结构,以树形层级组织,从根节点往下展开,每个节点代表一个硬件设备或总线。

设备树的源文件是 .dts(Device Tree Source),编译后生成 .dtb(Device Tree Blob),内核直接加载 .dtb 来识别硬件。

1.2 为什么需要设备树?—— 一段历史故事

在设备树出现之前,Linux 内核是怎么知道硬件信息的?

答案是:硬编码。每个板卡都在内核源码里写一堆 arch/arm/mach-xxx 的板级文件。那时候,每换一块板子,就得重新编译内核。

我记得刚入行那会儿,公司有七八块不同的 ARM 板卡。每次内核升级,光维护这些板级文件就够头疼的。更别提新加一块板卡,得复制粘贴一大段代码,稍不留神就改出问题。

为什么会这样?因为内核和硬件信息是“紧耦合”的。换硬件就得改内核源码,这显然不科学。

设备树解决了这个问题:

  • 解耦硬件描述与内核代码 —— 硬件信息从内核源码里抽出来,放到独立的 .dts 文件中
  • 同一内核镜像支持多块板卡 —— 启动时加载不同的 .dtb 即可
  • 社区统一标准 —— ARM、RISC-V、PowerPC 都用同一套描述方式

我的经验:早期项目里,一块板卡从原型到量产,硬件改过三次引脚分配。如果没有设备树,每次改硬件都得重新编译内核。有了设备树,改改 .dts 文件,重新编译一下,几分钟搞定。这效率提升,谁用谁知道。

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

设备树在内核启动过程中扮演什么角色?我画了一张图,你看完就明白了。

设备树在Linux内核启动中的角色 设备树在Linux内核启动中的角色 Bootloader 加载 .dtb 到内存 内核早期启动 解析设备树,建立设备模型 驱动匹配 compatible 匹配 设备树核心功能:资源描述、中断映射、时钟配置 设备初始化:注册设备、分配资源、调用probe 用户空间可见:/sys/firmware/devicetree 关键点 设备树是 “硬件描述” 不是“硬件驱动”

从这张图可以看到,设备树在内核启动中贯穿了好几个阶段:

  1. Bootloader 加载 .dtb —— 把编译好的设备树二进制文件放到内存指定位置
  2. 内核早期解析 —— 内核启动时,从内存中读取设备树,建立设备模型
  3. 驱动匹配 —— 驱动通过 compatible 属性与设备树节点匹配
  4. 资源分配 —— 中断号、寄存器地址、时钟频率等信息从设备树中获取
  5. 设备初始化 —— 匹配成功后,调用驱动的 probe 函数
  6. 用户空间可见 —— 通过 /sys/firmware/devicetree 可以查看设备树内容

我曾经踩过的坑:有一次调试一块新板卡,内核死活起不来。查了两天,最后发现是 Bootloader 传给内核的设备树地址不对。内核拿到的是乱码,自然解析失败。所以,设备树地址传递是第一个要确认的点。

1.4 设备树的核心数据结构

设备树的结构其实很简单,就是一棵树。每个节点有属性,属性有值。

看一个最简单的例子:

/dts-v1/;

/ {
    model = "MyBoard";
    compatible = "vendor,myboard";

    cpu@0 {
        compatible = "arm,cortex-a7";
        reg = <0x00>;
    };

    serial@1c00000 {
        compatible = "ns16550";
        reg = <0x01c00000 0x1000>;
        interrupts = <0 12 4>;
    };
};

这里有几个关键概念:

概念 说明 示例
根节点 用 / 表示,整棵树的起点 / { ... };
节点 每个硬件设备或总线 cpu@0, serial@1c00000
属性 描述节点的特征 compatible, reg, interrupts
compatible 驱动匹配的关键字段 "arm,cortex-a7"
reg 地址和大小信息 <0x01c00000 0x1000>

我的习惯:写设备树时,我一般先画一张硬件框图,把CPU、总线、外设的层级关系理清楚。然后再动手写 .dts。这样不容易漏节点,也不会搞错父子关系。

1.5 设备树与驱动的“握手”过程

设备树和驱动是怎么配合的?我简单说一下流程:

  • 驱动里声明一个 of_device_id 表,里面写上支持的 compatible 字符串
  • 内核启动时,遍历设备树的所有节点
  • 对每个节点,查找是否有驱动注册了对应的 compatible
  • 匹配成功,调用驱动的 probe 函数
  • 驱动在 probe 里通过 of_ 系列 API 读取设备树中的资源信息

说白了,设备树就是驱动和硬件之间的“翻译官”。驱动不需要知道硬件具体怎么接的,只需要从设备树里读配置就行。

一句话总结:设备树让硬件描述与内核代码分离,让同一份内核镜像可以适配多块板卡。这是嵌入式 Linux 从“单板定制”走向“多板统一”的关键一步。

嗯,这一章的内容就到这里。设备树的概念虽然简单,但它是后面所有章节的基础。你把这个根扎稳了,后面讲多板卡统一管理、设备树复用、overlay 这些高级话题时,就不会觉得吃力。