1. 设备树(DTS)基础概念:什么是设备树、为什么需要设备树、设备树在Linux内核中的作用

1.1 什么是设备树?

设备树,英文叫 Device Tree,简称 DT。说白了,它就是一套描述硬件信息的数据结构。

我刚开始接触嵌入式 Linux 时,对设备树也是一头雾水。那时候内核里到处都是板级文件,每个开发板都要写一堆 platform_device 的代码。后来设备树出现了,一切都变了。

设备树用树形结构来描述硬件。根节点是系统本身,子节点是各种外设。每个节点里放着设备的属性,比如寄存器地址、中断号、时钟频率等等。

举个例子,一个简单的设备树片段长这样:

/dts-v1/;

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

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

    uart@10000000 {
        compatible = "ns16550";
        reg = <0x10000000 0x1000>;
        interrupts = <0 33 4>;
    };
};

你看,这就像一棵倒挂的树。根是 /,下面挂着 cpu 和 uart。每个节点都有 compatible 属性,用来匹配驱动。

核心要点:设备树是硬件描述语言,不是代码。它告诉内核「我有什么硬件」,而不是「怎么驱动硬件」。

1.2 为什么需要设备树?

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

答案是:硬编码。

每个开发板都要在 arch/arm/mach-xxx 目录下写一堆板级文件。这些文件里充斥着 platform_deviceresourcei2c_board_info 之类的结构体。我当年维护过一个项目,光是板级文件就有 3000 多行。每次换一个硬件版本,都要改代码、重新编译内核。

为什么会这样?因为内核和硬件信息是绑死的。你换一个 Flash 芯片,改一下 GPIO 引脚,就得动内核源码。

设备树解决了这个问题。它把硬件描述从内核源码中剥离出来。内核只负责解析设备树,然后根据设备树里的信息去匹配驱动、申请资源。

这样做的好处很明显:

  • 硬件和内核解耦:换硬件只需改 dts 文件,不用改内核代码
  • 同一内核支持多板:一个内核镜像可以跑在不同硬件上,只要设备树不同就行
  • 减少内核体积:不用把几百个板级文件都编译进内核
  • 社区统一标准:ARM、PowerPC、RISC-V 都用同一套机制

我的经验:有一次客户临时改了 Flash 分区大小。我只需要改一下 dts 文件里的 partition 节点,重新编译设备树,烧进去就完事了。内核都不用动。这在以前简直不敢想。

1.3 设备树在 Linux 内核中的作用

设备树在内核启动过程中扮演着「硬件导游」的角色。内核启动时,Bootloader 会把设备树二进制文件(dtb)加载到内存,然后传给内核。内核解析 dtb,构建出设备树结构,然后根据这个结构去初始化硬件。

具体来说,设备树在内核中的作用有这几个:

  1. 硬件资源描述:告诉内核有哪些外设、它们的地址、中断、时钟等
  2. 驱动匹配:通过 compatible 属性,让内核找到对应的驱动
  3. 平台配置:比如内存大小、启动参数、引脚复用等
  4. 运行时信息传递:内核可以通过 of_* 系列 API 动态读取设备树信息

我画了一张图,帮你理解设备树在内核中的位置:

设备树在Linux内核中的角色 硬件层(CPU、内存、UART、I2C、SPI、GPIO...) 设备树(DTS/DTB) Linux内核(解析设备树 → 匹配驱动 → 初始化硬件) 用户空间应用程序

从这张图你可以看到,设备树是硬件和内核之间的桥梁。它把硬件信息翻译成内核能理解的格式。

1.4 设备树相关的文件格式

你可能会听到 DTS、DTSI、DTB、DTC 这些词。它们是什么关系?

缩写 全称 说明
DTS Device Tree Source 设备树源文件,文本格式,人类可读
DTSI Device Tree Source Include 设备树头文件,用于公共部分复用
DTB Device Tree Blob 设备树二进制文件,内核实际使用的格式
DTC Device Tree Compiler 设备树编译器,把 DTS 编译成 DTB

编译命令很简单:

dtc -I dts -O dtb -o myboard.dtb myboard.dts

注意:我曾经犯过一个低级错误——直接修改了 dtb 文件。dtb 是二进制格式,不能用文本编辑器改。一定要改 dts 源文件,然后重新编译。

1.5 设备树的核心语法

设备树的语法其实不复杂。记住几个关键点就行:

  • 节点:用 node-name@unit-address 命名,比如 uart@10000000
  • 属性:键值对形式,值可以是字符串、数字、数组等
  • 标签:用 &label 引用节点,方便复用
  • 包含:用 #include 引入 dtsi 文件

举个例子:

// 定义一个标签
uart0: uart@10000000 {
    compatible = "ns16550";
    reg = <0x10000000 0x1000>;
    interrupts = <0 33 4>;
    status = "okay";
};

// 在其他地方引用
&uart0 {
    clock-frequency = <24000000>;
};

这里 uart0 是标签,后面可以用 &uart0 来追加属性。这种写法在复用公共 dtsi 文件时特别有用。

我的习惯:我会把 SoC 公共部分写在 soc.dtsi 里,板级差异写在 board.dts 里。这样换板子时只需要改 board.dts,SoC 部分完全不用动。

1.6 小结

设备树不是什么高深的东西。它就是一套硬件描述规范,让内核知道「我有什么硬件」。你想想看,如果没有设备树,每换一个硬件就要改内核代码,那得多痛苦。

嗯,这一章我们讲清楚了设备树是什么、为什么需要它、它在内核中扮演什么角色。下一章我们会深入设备树的语法细节,包括节点、属性、中断映射这些实战内容。

记住:设备树是描述,不是代码。它告诉内核「有什么」,而不是「怎么做」。


公众号:蓝海资料掘金营,微信deep3321