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_device、resource、i2c_board_info 之类的结构体。我当年维护过一个项目,光是板级文件就有 3000 多行。每次换一个硬件版本,都要改代码、重新编译内核。
为什么会这样?因为内核和硬件信息是绑死的。你换一个 Flash 芯片,改一下 GPIO 引脚,就得动内核源码。
设备树解决了这个问题。它把硬件描述从内核源码中剥离出来。内核只负责解析设备树,然后根据设备树里的信息去匹配驱动、申请资源。
这样做的好处很明显:
- 硬件和内核解耦:换硬件只需改 dts 文件,不用改内核代码
- 同一内核支持多板:一个内核镜像可以跑在不同硬件上,只要设备树不同就行
- 减少内核体积:不用把几百个板级文件都编译进内核
- 社区统一标准:ARM、PowerPC、RISC-V 都用同一套机制
我的经验:有一次客户临时改了 Flash 分区大小。我只需要改一下 dts 文件里的 partition 节点,重新编译设备树,烧进去就完事了。内核都不用动。这在以前简直不敢想。
1.3 设备树在 Linux 内核中的作用
设备树在内核启动过程中扮演着「硬件导游」的角色。内核启动时,Bootloader 会把设备树二进制文件(dtb)加载到内存,然后传给内核。内核解析 dtb,构建出设备树结构,然后根据这个结构去初始化硬件。
具体来说,设备树在内核中的作用有这几个:
- 硬件资源描述:告诉内核有哪些外设、它们的地址、中断、时钟等
- 驱动匹配:通过
compatible属性,让内核找到对应的驱动 - 平台配置:比如内存大小、启动参数、引脚复用等
- 运行时信息传递:内核可以通过
of_*系列 API 动态读取设备树信息
我画了一张图,帮你理解设备树在内核中的位置:
从这张图你可以看到,设备树是硬件和内核之间的桥梁。它把硬件信息翻译成内核能理解的格式。
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