当前位置:首页>Linux>Linux设备树(Device Tree)全面解析

Linux设备树(Device Tree)全面解析

  • 2026-09-21 12:14:50
Linux设备树(Device Tree)全面解析
Hello,大家好,我是程序媛MM。

本文约3800字,在嵌入式Linux开发中,设备树(Device Tree,简称DT)是贯穿内核、驱动与硬件的核心组件,尤其在ARM、RISC-V等架构中,更是实现硬件与驱动解耦的关键技术。对于嵌入式开发者而言,掌握设备树不仅能解决驱动移植的痛点,更能大幅提升硬件适配的效率。本文整理了设备树的起源、核心组成、工作原理,以及实战编写与调试方法。

关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具,文末有文档清单。


一 为什么需要设备树?—— 解决传统嵌入式开发的痛点


在设备树出现之前,嵌入式Linux内核中充斥着大量板级支持包(BSP)硬编码代码,这些代码主要用于描述特定开发板的硬件信息,比如寄存器地址、中断号、GPIO引脚配置等,集中在`arch/arm/mach-xxx/`目录下。这种模式存在三大核心痛点:

[1].内核冗余严重:不同开发板即便基于同一CPU架构,也需要编写独立的板级代码,导致内核源码臃肿,维护成本极高;

[2].可移植性差:新增或更换硬件平台时,需修改内核源码并重新编译,无法实现内核镜像的复用;

[3].耦合度极高:硬件描述与驱动逻辑深度绑定,驱动开发者需频繁修改硬件相关的硬编码,开发效率低下。

为解决这些问题,设备树应运而生。其核心目标是将硬件描述与内核代码分离,让同一个内核镜像(针对特定CPU架构),只需加载不同的设备树二进制文件,就能适配多种不同的硬件平台,大幅简化内核移植与硬件适配流程。

说明:设备树的本质是“硬件描述文件”,它不参与驱动逻辑实现,仅负责告诉内核“硬件是什么、在哪里、如何配置”,实现“一次编译内核,多平台适配”。

二 设备树核心组成:4个关键文件与核心语法


设备树的完整工作链路涉及4类核心文件,以及一套标准化的语法规则,掌握这些是编写设备树的基础。

[1]. 四类核心文件(从源码到二进制)

设备树的开发流程遵循“编写→编译→加载→解析”的链路,对应以下4类文件,其关系类似C语言的“源码→编译器→可执行文件”:

>>设备树源文件(.dts):人类可读的文本文件,是设备树的核心源码,用于描述特定开发板的完整硬件拓扑结构和配置,比如CPU、内存、外设的连接关系与参数。通常一个开发板对应一个.dts文件,命名格式如`imx6ull-xxx.dts`(基于NXP i.MX6ULL芯片的开发板)。

>>设备树头文件(.dtsi):类似C语言的头文件,用于提炼多个.dts文件的公共部分(如SoC内部通用外设、总线控制器的描述),通过`#include`指令被.dts文件引用,实现代码复用。例如`imx6ull.dtsi`包含了i.MX6ULL芯片的核心硬件描述,所有基于该芯片的开发板.dts文件都可引用它。

>>设备树编译器(dtc):将.dts和.dtsi文件编译为机器可识别的二进制文件的工具,相当于C语言的gcc编译器。编译命令格式为:`dtc -I dts -O dtb -o 目标.dtb 源.dts`,其中`-I`指定输入格式,`-O`指定输出格式,`-o`指定输出文件。

>>设备树二进制文件(.dtb):由dtc编译生成的二进制文件,是最终被Bootloader加载并传递给内核的硬件描述数据。其文件结构包含四部分:头部(标识文件信息)、内存保留块、结构块(节点信息)、字符串块(属性名称),内核启动时会解析该文件获取硬件信息。

[2]. 核心语法:节点与属性

设备树以树形结构组织硬件信息,核心语法元素是节点(Node)和属性(Property),所有硬件描述都围绕这两个元素展开,以下结合实例详解关键语法:

>>节点:硬件的“容器”

节点对应一个硬件设备或总线(如CPU、I2C控制器、LED外设),以树形结构嵌套,根节点`/`是所有节点的父节点,唯一且必须存在。节点的标准格式为:

dtsnode-label: node-name@unit-address {    属性键 = 属性值;    子节点 { ... };};

各部分含义:

node-label(标签):可选,用于快速引用该节点,格式为`标签名:`,后续可通过`&标签名`引用该节点,避免重复编写路径;

node-name@unit-address(节点名+地址):节点的唯一标识,`node-name`是设备类型(如i2c、led、uart),`unit-address`是设备的基地址(如寄存器地址、I2C从地址),用于区分同一总线上的多个同类设备,例如`i2c@11000000`表示基地址为0x11000000的I2C控制器;

子节点:嵌套在父节点中,对应挂载在该设备/总线上的外设,例如I2C控制器节点下可嵌套温度传感器子节点。

>>属性:硬件的“说明书”

属性是节点的核心,以“键值对”形式存在,用于描述节点的硬件特性和配置参数,分为标准属性(由设备树规范定义,具有固定含义)和自定义属性(由驱动开发者定义,通常带厂商前缀)。

常用标准属性(必掌握):

compatible:最核心属性,用于驱动与设备节点的匹配,值为字符串或字符串列表,格式通常为“厂商,设备型号”,例如`compatible = "ti,tmp102"`表示该节点对应TI公司的TMP102温度传感器,驱动程序通过匹配该属性与设备绑定;

reg:描述设备的地址范围(如寄存器地址、内存地址),值为32位整数列表,格式为`<地址 长度>`,例如`reg = <0x10000000 0x100>`表示基地址为0x10000000,长度为0x100(256字节)的寄存器空间;

interrupts:描述设备的中断信息,值为整数列表,包含中断控制器、中断号、触发方式等,例如`interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>`表示中断类型为SPI中断,中断号为5,高电平触发;

status:描述设备的使能状态,常用值为`"okay"`(使能)和`"disabled"`(禁用),未指定时默认禁用;

#address-cells和#size-cells:仅存在于父节点,用于描述子节点`reg`属性的格式,`#address-cells`指定子节点`reg`中地址部分的32位整数个数,`#size-cells`指定长度部分的32位整数个数,默认均为1。

自定义属性:通常以厂商前缀开头,用于传递驱动所需的自定义配置,例如`fsl,clk-freq = <100000000>`表示飞思卡尔(FSL)芯片的时钟频率为100MHz,`temp-high-threshold = <30000>`表示温度传感器的高温阈值为30℃(单位:毫摄氏度)。

>>关键语法补充

引用节点:通过`&标签名`引用已定义的节点,用于关联父设备、中断控制器等,例如`interrupt-parent = <&gic>`表示该设备的中断父节点是中断控制器gic(需提前为gic节点定义标签);

版本声明:.dts文件开头需添加`/dts-v1/;`,声明设备树版本为v1,否则编译会报错;

注释:与C语言一致,单行注释用`//`,多行注释用`/* ... */`。

>>完整设备树片段示例

dts/dts-v1/; // 设备树版本声明#include"imx6ull.dtsi"// 引用SoC公共头文件/ {    model = "NXP i.MX6ULL 开发板"; // 开发板型号    compatible = "nxp,imx6ull-xxx", "nxp,imx6ull"; // 平台兼容属性    #address-cells = <1>; // 子节点reg地址部分占1个32位整数    #size-cells = <1>;    // 子节点reg长度部分占1个32位整数    soc { // SOC节点,包含所有内部外设        compatible = "simple-bus";        ranges; // 子节点地址无需转换        // LED控制器节点(标签led_ctl)        led_ctl: led-controller@10000000 {            compatible = "nxp,imx6ull-led-ctl";            reg = <0x10000000 0x100>; // 寄存器地址+长度            interrupt-parent = <&gic>; // 引用中断控制器            interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;            status = "okay"; // 使能设备        };        // I2C控制器节点(标签i2c1)        i2c1: i2c@11000000 {            compatible = "nxp,imx6ull-i2c";            reg = <0x11000000 0x100>;            status = "okay";            // I2C总线上的温度传感器子节点            tmp102@48 {                compatible = "ti,tmp102";                reg = <0x48>; // I2C从地址                temp-alert-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; // 自定义属性            };        };    };};

三 设备树编译到与驱动匹配


设备树的完整工作流程贯穿嵌入式系统启动的全过程,从文件编译到驱动初始化,共分为5个关键步骤,清晰理解流程能帮助开发者定位适配问题:

步骤1:编译设备树

开发者编写.dts文件(引用必要的.dtsi文件)后,使用dtc编译器将其编译为.dtb二进制文件。编译命令示例:

dtc -I dts -O dtb -o imx6ull-xxx.dtb imx6ull-xxx.dts

编译成功后生成.dtb文件,若语法错误(如节点未闭合、属性格式错误),dtc会提示具体错误位置,需修改后重新编译。

步骤2:加载设备树

系统启动时,Bootloader(如U-Boot)首先将.dtb文件从存储介质(SD卡、Flash、网络)加载到内存的指定位置,同时加载Linux内核镜像。

步骤3:传递设备树地址

Bootloader启动内核时,通过约定的机制将.dtb文件在内存中的物理地址传递给内核(例如ARM架构中通过r2寄存器传递),告知内核“硬件描述文件在哪里”。

步骤4:内核解析设备树

Linux内核启动早期(setup_arch阶段),会解压并解析.dtb文件,在内存中构建一个结构化的设备树数据库(Flattened Device Tree,FDT),遍历所有节点,提取硬件资源信息(地址、中断、GPIO等),并构建内核设备模型。

步骤5:驱动匹配与设备初始化

这是设备树发挥作用的核心步骤,具体流程如下:

>>内核遍历设备树数据库中的所有节点,检查每个节点的`compatible`属性;

>>内核在已编译的驱动模块或内核源码中,查找`of_match_table`(设备树匹配表)中包含该`compatible`字符串的驱动;

>>找到匹配的驱动后,内核调用该驱动的`probe`函数;

>>驱动在`probe`函数中,通过设备树API(`of_*`系列函数,如`of_get_property`、`of_iomap`、`of_get_gpio`)解析节点属性,获取硬件资源(如寄存器地址、中断号);

>>驱动利用解析到的资源完成硬件初始化,并注册设备(如字符设备、网络设备),最终实现硬件与驱动的联动。

注意:驱动与设备节点的匹配核心是`compatible`属性,两者必须完全一致(区分大小写),否则驱动无法匹配设备,设备无法正常工作。

四 设备树编写与调试技巧


掌握理论后,实战中常遇到编译报错、驱动不匹配、硬件无法识别等问题,以下是高频实战要点与调试技巧,帮助开发者避坑:

[1]. 编写规范

>>节点命名遵循“设备类型@地址”格式,标签名简洁明了(如`led_ctl`、`i2c1`),便于引用;

>>`compatible`属性格式统一为“厂商,设备型号”,避免自定义无意义字符串,可参考Linux内核`Documentation/devicetree/bindings/`目录下的绑定文档(设备树绑定文档定义了特定设备的属性规范);

>>尽量复用.dtsi文件中的公共节点,避免重复编写,减少维护成本;

>>`reg`属性的地址和长度需与硬件手册一致,地址错误会导致驱动无法访问设备寄存器。

[2]. 常见报错与解决方法

>>编译报错:“syntax error”:语法错误,如节点未闭合、属性后缺少分号、`<>`使用错误,根据dtc提示的行号,检查语法格式;

>>驱动不匹配:内核日志中出现“no matching driver found”,检查节点`compatible`属性与驱动`of_match_table`中的字符串是否一致,区分大小写;

>>硬件无法识别:节点`status`属性未设为`"okay"`,或`reg`地址错误,可通过内核日志查看设备树解析情况;

>>中断异常:`interrupts`属性格式错误,或`interrupt-parent`引用错误,需核对硬件手册中的中断号和中断控制器节点。

[3]. 调试工具与命令

>>dtc工具:除了编译,还可反编译.dtb文件为.dts文件,用于查看内核加载的设备树内容,命令:

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

>>内核日志:通过`dmesg | grep OF:`查看设备树解析过程的日志,定位解析错误(如节点加载失败、属性解析异常);

>>/proc/device-tree:系统启动后,内核会将解析后的设备树信息挂载到该目录下,可通过`ls`、`cat`命令查看节点和属性,例如`cat /proc/device-tree/model`可查看开发板型号,`ls /proc/device-tree/soc`可查看SOC节点下的所有子节点。

五 总结


设备树的核心价值是“硬件与驱动解耦”,通过标准化的硬件描述,实现内核镜像复用和快速移植,是嵌入式Linux开发的必备技能。本文梳理了设备树的核心概念、组成、工作流程及实战技巧,掌握这些内容,可应对大部分基础的设备树编写与适配需求。

以上为全文内容。

往期文章(欢迎订阅技术分享栏目全部文章):

【从零开始撸内核驱动源码】:以ttyserial(串口驱动)为例,串联字符设备驱动基础知识点的学习计划
Linux内核源码顶层 Makefile分析并单独编译调试内核自带的驱动
【从零开始撸内核驱动源码】:ttynull驱动
Linux内核驱动安装失败问题调试及解决方法
Linux内核驱动源码走读之编译内核及外部驱动实操指南

“谢谢你看到这里”

这里是女程序员的笔记本

 15年+嵌入式软件工程师兼二胎宝妈

分享读书心得、工作经验,自我成长和生活方式。

希望我的文字能对你有所帮助

最新文章

随机文章