当前位置:首页>Linux>【深度剖析】:Linux 内核如何解析设备树(Device Tree)

【深度剖析】:Linux 内核如何解析设备树(Device Tree)

  • 2026-09-10 05:16:12
【深度剖析】:Linux 内核如何解析设备树(Device Tree)
Hello,大家好,我是程序媛MM。

本文约2800字,今天继续沿着《一份靠谱的BSP开发学习路线》来接续学习BSP开发所需的知识--Linux内核如何解析设备树。本文将从内核启动流程切入,深度剖析内核解析设备树的核心原理、关键数据结构、函数调用流程与执行逻辑,帮我们彻底掌握设备树在内核中的工作机制。

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


在嵌入式 Linux 开发中,设备树(Device Tree,DT) 已经彻底取代了传统的板级硬件编码描述,成为 ARM、RISC-V 等架构描述硬件的标准方案。内核无需重新编译,仅通过更换设备树二进制文件(.dtb),就能适配不同硬件平台。而这一切的核心,就是 Linux 内核对设备树文件的加载、解析、绑定、设备注册全流程。

一 设备树基础知识回顾


设备树是一种描述硬件资源的数据结构,源文件为.dts(板级)和.dtsi(芯片级),通过dtc工具编译为.dtb(扁平设备树)。

它描述了CPU、内存、中断控制器、I2C/SPI 控制器、外设、GPIO、时钟等所有硬件信息,内核通过解析这些信息,自动匹配并初始化驱动,无需在内核中硬编码硬件参数。

内核解析设备树的核心目标:

从.dtb中提取硬件拓扑结构;

构建内核内部的设备节点树;

完成驱动与设备的匹配(平台总线 / 设备树匹配);

初始化硬件资源(中断、地址、时钟、GPIO 等)。

二 设备树在内核中的加载时机


设备树解析早于大多数驱动初始化,属于内核启动的早期阶段,整体流程如下:

[1].BootLoader 传递 dtb:U-Boot 将内核镜像(zImage/Image)和 dtb 文件加载到内存指定地址,通过寄存器将 dtb 物理地址传递给内核;

[2].内核自解压:内核解压后,获取 dtb 在内存中的物理起始地址;

[3].早期设备树解析:内核启动初期,在虚拟内存初始化完成前,完成 dtb 的初步解析;

[4].完整设备树构建:建立内核内部的device_node节点树;

[5].平台设备注册:遍历节点树,生成平台设备,与驱动匹配。

说明:设备树解析是内核启动的基石,所有硬件驱动的初始化都依赖解析后的设备树信息。

三 内核设备树核心数据结构


内核使用一套专用数据结构存储设备树信息,是解析流程的核心载体:

[1]. struct device_node

设备树节点的内核抽象,最核心结构,对应 dts 中的一个节点(如cpu@0、uart@02020000)。

struct device_node {    const char *name;           // 节点名称(如uart)    const char *type;           // 节点类型    phandle phandle;            // 节点句柄,用于跨节点引用    struct device_node *parent; // 父节点    struct list_head child;     // 子节点链表    struct property *properties;// 节点属性链表    ...};

[2]. struct property

描述设备树节点的属性(如compatible、reg、interrupts、status)。

struct property {    char *name;        // 属性名    unsigned int length;// 属性值长度    void *value;       // 属性值    struct property *next;// 下一个属性    ...};

[3]. struct platform_device

设备树解析完成后,最终会转换为平台设备,接入 Linux 设备模型,与驱动完成匹配。

四 内核解析设备树全流程深度拆解


按照内核启动顺序,分5个阶段详解设备树解析的完整逻辑:

【阶段1】:DTB 文件合法性校验

内核首先获取 U-Boot 传递的 dtb 物理地址,对 dtb 文件头部进行校验:

检查魔数0xd00dfeed(设备树固定标识);

校验 dtb 版本、总大小、结构体偏移量;

确认 dtb 未损坏、格式兼容当前内核。

若校验失败,内核会直接 panic,无法继续启动。

【阶段2】:扁平设备树(FDT)早期映射

dtb 是扁平二进制格式,内核无法直接使用,需要将其映射到虚拟地址空间:

内核获取 dtb 物理地址;

调用early_init_dt_map()将 dtb 映射为虚拟地址;

保留 dtb 占用的内存,避免被内核覆盖。

这一步仅完成地址映射,不解析节点结构。

【阶段3】:构建内核内部节点树

这是设备树解析的最关键步骤,内核遍历 dtb 二进制数据,递归生成device_node链表树。

核心函数:__unflatten_device_tree()

遍历 dtb 结构:从头至尾解析节点开始标记、属性、子节点、节点结束标记;

递归创建节点:为每个 dts 节点创建device_node,挂载到父节点的子链表;

解析属性:为每个节点创建property结构,存储属性名和值;

处理特殊节点:

>>aliases节点:解析别名,简化节点引用;

>>chosen节点:获取bootargs等启动参数;

>>memory节点:解析物理内存布局;

>>cpus节点:解析 CPU 核心信息。

执行完成后,内核内存中就拥有了与 dts 完全一致的树形结构,所有硬件信息都可通过节点访问。

【阶段4】:内存、中断、时钟等核心资源解析

内核优先解析系统必需的硬件资源,为后续初始化铺路:

内存解析:从memory节点提取物理内存地址和大小,初始化内存管理;

中断解析:解析interrupt-parent、interrupts属性,建立中断映射表;

时钟解析:解析时钟控制器节点,为外设提供时钟源;

总线解析:解析平台总线、AHB、APB 等总线拓扑。

这些资源是内核运行的基础,必须在驱动加载前完成解析。

【阶段5】:设备树节点转换为平台设备

内核遍历所有设备树节点,筛选出可注册的设备节点(status="okay"且包含compatible属性),将其转换为platform_device,并注册到平台总线。

核心逻辑:

匹配compatible属性:驱动中定义of_device_id表,与节点compatible字符串匹配;

提取硬件资源:从reg属性提取物理地址,从interrupts提取中断号;

注册设备:调用platform_device_register(),触发驱动的probe函数。

至此,设备树完成使命,驱动开始初始化硬件。

五 设备树解析核心函数调用流程


为了更清晰地掌握代码层级,这里给出 ARM64 架构下设备树解析的标准函数调用路径:

start_kernel()  → setup_arch() // 架构初始化入口    → unflatten_device_tree() // 解扁平设备树(构建节点树)      → __unflatten_device_tree() // 核心解析函数    → arm64_memblock_init() // 解析内存节点    → platform_bus_init() // 平台总线初始化      → of_platform_populate() // 遍历节点,创建平台设备

所有关键逻辑都集中在drivers/of/目录下(of = open firmware,设备树标准来源),是内核设备树解析的核心代码库。

六 关键解析规则:compatible 匹配机制


compatible是设备树最核心的属性,驱动与设备匹配的唯一依据。

节点定义:compatible = "vendor,device-name", "generic-name";

驱动定义:of_device_id表包含匹配字符串;

匹配规则:内核从左到右依次匹配,优先精确匹配,其次通用匹配。

示例:

// 设备树节点uart@02020000 {    compatible = "fsl,imx6ull-uart", "fsl,imx-uart";};// 驱动匹配表static const struct of_device_id imx_uart_ids[] = {    { .compatible = "fsl,imx-uart" },    { /* 匹配成功 */ }};

内核解析到节点后,自动匹配到 UART 驱动,执行初始化。

总结整个匹配流程如下:

设备树节点  ↓ (compatible: "fsl,imx6ull-uart", "fsl,imx-uart")内核扫描所有设备节点  ↓遍历所有平台驱动的 of_match_table  ↓找到驱动里的 { .compatible = "fsl,imx-uart" }  ↓字符串完全一致 → 匹配成功!  ↓自动调用驱动的 probe 函数  ↓驱动从设备树获取资源并初始化硬件

七 设备树解析常见问题


[1].dtb 未传递 / 损坏:内核无法找到设备树,启动 panic;

[2].compatible 不匹配:驱动无法加载,硬件不工作;

[3].reg 属性地址错误:驱动访问非法物理地址,内核崩溃;

[4].interrupts 配置错误:中断无法触发,驱动无响应;

[5].status="disabled":节点被屏蔽,内核不会解析注册。

这些问题都可以通过内核日志(dmesg) 和设备树文件对比快速定位。

八 总结


Linux 内核解析设备树,是一个从二进制 dtb 到内核设备树、再到平台设备的完整转换流程:

U-Boot 将 dtb 加载到内存并传递给内核;

内核校验、映射 dtb,递归构建device_node节点树;

解析内存、中断、时钟等核心系统资源;

遍历节点,通过compatible匹配驱动,注册平台设备;

驱动获取硬件资源,完成硬件初始化。

设备树解析是嵌入式 Linux 的核心底层逻辑,理解这一流程,不仅能解决硬件适配、驱动调试问题,更能深入理解 Linux 设备模型的设计思想。

以上为全文内容。

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

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

“谢谢你看到这里”

这里是女程序员的笔记本

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

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

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

最新文章

随机文章