【Linux驱动开发】第10天:设备树零基础入门——DTS/DTB/DTC全解+编译流程
目录
- 为什么需要设备树?传统驱动的终极痛点
- DTS/DTB/DTC 大白话定义+核心区别
- 三者关系+完整编译流程图
- 最简单的DTS示例+语法解析
- 设备树编译与反编译实操命令
- 内核如何加载和使用设备树
- 核心总结+面试必背考点
1. 为什么需要设备树?传统驱动的终极痛点
在设备树出现之前,Linux内核采用硬编码的方式描述硬件信息:
- 所有寄存器地址、中断号、GPIO号都直接写死在驱动代码里
- 换一个开发板,就要修改驱动代码重新编译
- 一个ARM内核要支持几十上百个开发板,就要在代码里写几百个硬件信息的分支
- 内核变得无比臃肿,维护成本极高
设备树的诞生,就是为了彻底解决这个问题:
把硬件描述信息从内核代码中抽离出来,用一个独立的文件来描述硬件。内核只需要一份通用的代码,通过读取这个文件来识别不同的硬件。
大白话类比
- 传统驱动:厨师把所有客人的口味都写在菜谱里,来了一个客人就要改一次菜谱
- 设备树:厨师用通用菜谱,客人自己带一张写着自己口味的纸条,厨师照着纸条做菜
2. DTS/DTB/DTC 大白话定义+核心区别
设备树体系由三个核心部分组成:DTS、DTB、DTC,三者分工明确,缺一不可。
2.1 三者大白话定义
| 缩写 | 全称 | 中文名称 | 大白话解释 |
|---|---|---|---|
| DTS | Device Tree Source | 设备树源文件 | 人类可读的文本文件,用C语言风格的语法描述硬件信息,相当于Word文档 |
| DTC | Device Tree Compiler | 设备树编译器 | 把人类可读的DTS文本文件,编译成内核能识别的二进制文件,相当于Word转PDF的转换器 |
| DTB | Device Tree Blob | 设备树二进制文件 | DTC编译后的二进制文件,内核启动时加载并解析,相当于PDF文件 |
2.2 核心区别对比表
| 对比项 | DTS | DTC | DTB |
|---|---|---|---|
| 文件类型 | 纯文本文件 | 可执行程序 | 二进制文件 |
| 可读性 | 人类可读 | 不可读 | 不可读 |
| 作用 | 描述硬件信息 | 编译DTS为DTB | 内核加载解析 |
| 编辑方式 | 任何文本编辑器 | 无需编辑 | 不能直接编辑 |
| 后缀名 | .dts、.dtsi | 无后缀 | .dtb |
2.3 补充:什么是.dtsi文件?
.dtsi是设备树头文件,相当于C语言的.h头文件,用于存放多个DTS文件共享的公共硬件信息。
- 比如同一个芯片的所有开发板,芯片内部的外设信息是相同的,可以放在
.dtsi文件中 - 每个开发板自己的
.dts文件只需要包含这个.dtsi,然后添加自己独有的硬件信息即可 - 大大提高了代码复用性,减少了重复代码
3. 三者关系+完整编译流程图
3.1 三者核心关系
DTS(源文件) --DTC编译--> DTB(二进制文件) --内核加载解析--> 识别硬件3.2 完整编译与运行流程图
3.3 关键流程说明
- bootloader的作用:bootloader(如U-Boot)负责在启动内核之前,将DTB文件加载到内存中,并将DTB的内存地址传递给内核
- 内核解析DTB:内核启动时,根据bootloader传递的地址,解析DTB文件,创建对应的platform_device设备
- 总线匹配:platform总线将创建的设备和已注册的驱动进行匹配,匹配成功后调用probe函数
4. 最简单的DTS示例+语法解析
下面是一个最小可运行的DTS文件,我会逐行解释每个部分的含义,让你快速掌握设备树的基本语法。
4.1 最简单的DTS示例
/dts-v1/; // 设备树版本号,必须是第一行 / { // 根节点,所有设备节点都在根节点下面 compatible = "myvendor,myboard"; // 板级兼容属性,内核用它来匹配板级支持包 model = "My Custom Development Board"; // 开发板名称 // 自定义LED设备节点 led@12340000 { compatible = "myvendor,my-led"; // 设备兼容属性,和驱动的of_device_id匹配 reg = <0x12340000 0x1000>; // 寄存器基地址和长度 status = "okay"; // 设备状态:okay表示启用,disabled表示禁用 }; // 自定义UART设备节点 uart@12341000 { compatible = "myvendor,my-uart"; reg = <0x12341000 0x1000>; interrupts = <5>; // 中断号 status = "okay"; }; };4.2 核心语法解析
1. 节点(Node)
- 设备树用树形结构描述硬件,每个硬件对应一个节点
- 节点格式:
节点名@地址 { ... }; - 节点名:描述设备类型,如
led、uart、gpio @地址:设备的寄存器基地址,用于区分同类型的不同设备
2. 属性(Property)
- 每个节点包含多个属性,用于描述设备的具体信息
- 属性格式:
属性名 = 属性值; - 常见属性:
compatible:最重要的属性,用于和驱动匹配,格式为"厂商,设备名"reg:描述设备的寄存器地址范围,格式为<基地址 长度>interrupts:描述设备使用的中断号status:描述设备状态,okay表示启用,disabled表示禁用model:设备的人类可读名称
3. 根节点
- 所有节点的父节点,用
/表示 - 根节点的
compatible属性用于匹配板级支持包 - 根节点的
model属性用于描述开发板名称
5. 设备树编译与反编译实操命令
5.1 安装DTC编译器
Ubuntu系统下直接安装:
sudoapt-getinstalldevice-tree-compiler5.2 编译DTS为DTB
# 基本语法:dtc -I dts -O dtb -o 输出文件.dtb 输入文件.dtsdtc-Idts-Odtb-omyboard.dtb myboard.dts5.3 反编译DTB为DTS
这是调试设备树最常用的命令,可以把内核正在使用的DTB文件反编译成可读的DTS文件:
# 基本语法:dtc -I dtb -O dts -o 输出文件.dts 输入文件.dtbdtc-Idtb-Odts-omyboard.dts myboard.dtb5.4 查看开发板当前使用的设备树
在运行中的Linux系统中,可以通过/proc/device-tree目录查看内核解析后的设备树:
# 查看根节点的compatible属性cat/proc/device-tree/compatible# 查看所有设备节点ls/proc/device-tree/# 查看LED节点的reg属性cat/proc/device-tree/led@12340000/reg|hexdump-C6. 内核如何加载和使用设备树
6.1 两种加载方式
方式1:DTB编译进内核
- 将DTB文件和内核镜像编译在一起,生成一个单一的镜像文件
- 优点:简单,不需要单独管理DTB文件
- 缺点:修改设备树需要重新编译内核
方式2:DTB单独加载(推荐)
- DTB文件和内核镜像分开存储
- bootloader在启动内核时,将DTB加载到内存中,并将地址传递给内核
- 优点:修改设备树只需要重新编译DTB,不需要重新编译内核
- 现代Linux系统全部采用这种方式
6.2 U-Boot传递DTB地址的命令
# 加载内核镜像到内存0x80800000地址tftp 0x80800000 zImage# 加载DTB文件到内存0x83000000地址tftp 0x83000000 myboard.dtb# 启动内核,传递DTB地址bootz 0x80800000 - 0x830000006.3 内核解析DTB的过程
- 内核启动时,从bootloader获取DTB的内存地址
- 验证DTB的完整性和有效性
- 解析DTB的根节点和所有子节点
- 为每个设备节点创建对应的
platform_device结构体 - 将
platform_device注册到platform总线 - platform总线进行设备和驱动的匹配
- 匹配成功后调用驱动的
probe函数
7. 核心总结+面试必背考点
核心总结
- 设备树的核心作用是将硬件描述信息从内核代码中抽离出来,解决传统驱动硬编码的问题
- DTS是人类可读的设备树源文件,DTB是内核能识别的二进制文件,DTC是编译两者的编译器
- 设备树采用树形结构描述硬件,每个硬件对应一个节点,每个节点包含多个属性
compatible属性是设备和驱动匹配的唯一依据,格式为"厂商,设备名"- 现代Linux系统采用bootloader单独加载DTB的方式,修改设备树不需要重新编译内核
面试必背考点
- 什么是设备树?它解决了什么问题?
- DTS、DTB、DTC分别是什么?三者的关系是什么?
- 什么是.dtsi文件?它的作用是什么?
- 设备树的基本语法结构是什么?
compatible属性的作用是什么?格式是什么?- 内核如何加载和解析设备树?
- bootloader在设备树启动过程中起到什么作用?
- 如何反编译DTB文件为DTS文件?
