跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

深入探究 Linux 设备树

综述由AI生成设备树用于解决 ARM 架构下硬件描述碎片化问题,替代了硬编码的 board file 模式。通过 DTS 源码编译为 DTB 二进制文件,由 Bootloader 传入内核。内核解析 DTB 生成 device_node 并转换为 platform_device,实现驱动与硬件的匹配。驱动通过 of_系列函数读取设备树中的标准资源(reg, interrupts)及自定义属性。设备树使内核镜像能支持多种硬件,提升了可维护性。

协议工匠发布于 2026/3/15更新于 2026/5/2226 浏览
深入探究 Linux 设备树

引言

在嵌入式 Linux 开发中,硬件资源的描述至关重要。驱动代码负责操作设备,而设备结构体(struct device)则描述硬件资源。问题在于,这些硬件信息如何进入内核并与驱动配对?

在单片机裸机编程中,硬件地址硬编码在代码里。但在 Linux 中,为了保证驱动的通用性,避免在代码中出现具体硬件地址是核心原则。因此,需要一种机制将硬件信息从驱动代码中剥离。

这就是本文的主题——设备树。

1. 为什么要引入设备树

1.1 当时的环境

2000 年代末期,ARM 架构迎来井喷式发展。相比 x86 架构的标准化,ARM 呈现碎片化状态。各家 SOC 厂商(高通、三星、TI 等)推出不同芯片,同一款 SOC 也可能有不同的外设配置。Linux 想统一支持所有设备,但当时 ARM 设备基本靠硬编码告诉内核硬件信息。

这导致 Linux 内核 arch/arm 目录下代码量急速膨胀。支持新板子需添加专属文件。

1.2 board file 的缺陷

早期 ARM Linux 采用 board file(板级支持文件模式),即每个设备板子都有独立的 board-xxx.c 文件。文件中硬编码了内存映射、时钟配置、GPIO 引脚复用等信息。

这种模式在 2010 年左右暴露出问题:

  1. 内核里有数千个 board file,arch/arm 目录下文件数量激增,大量重复代码。
  2. 修改通用驱动需同步改几十个 board file,合并冲突频繁。
  3. 一个编译好的内核只能支持特定板子,无法像 x86 那样一个内核跑所有 PC。
  4. 厂商维护分支,上游合并慢,主线内核支持新硬件滞后。

Linus Torvalds 曾批评 ARM 社区的碎片化,要求推动根本性变革。

1.3 设备树的引入

社区最终借鉴 PowerPC 架构的 Open Firmware 机制。核心想法是把硬件描述从 C 源码中剥离出来,用独立的数据文件描述硬件,由 bootloader 传入内核,让内核在运行时动态解析。

使用 .dts(Device Tree Source)文件描述硬件树状结构,编译成 .dtb(Device Tree Blob)文件传给内核。

好处包括:删除重复代码,一个内核镜像支持多种硬件,易读性和可维护性提升,便于上游合并。

2. 初识设备树

设备树是由节点和属性构成的树。

2.1 三个 D

  1. DTS:源码文件 .dts,修改硬件配置主要修改它。
  2. DTC:编译器,把 .dts 编译成二进制文件。
  3. DTB:编译出来的二进制文件 .dtb,烧录到 flash 或放在文件系统。Bootloader 加载后传给内核。

2.2 类似于头文件包含的关系

Linux 采用分层和包含机制。以 NXP i.MX6ULL 芯片为例。

2.2.1 SOC 级文件 (.dtsi)

由芯片原厂提供,如 imx6ull.dtsi,描述芯片内部固有的硬件资源。物理地址和中断号不会变。

文件体现继承与重写策略。例如引用父类标签只修改需要的部分,或删除不存在的节点。

2.2.2 板级文件 (.dts)

开发板厂商编写描述文件,如 imx6ull-14x14-evk.dts。文件开头引用 SOC 级文件,开发者只需关注板子上的外设配置。

通常在最外层使用 &标签名方式引用节点开启默认关闭的外设,或在根节点内部添加外接新设备。

2.3 如何与驱动匹配

在没有设备树时,platform_device 名字必须和 platform_driver 名字一样。

在设备树世界里,匹配规则主角变成了 compatible 属性。只要设备树节点和驱动程序的 compatible 属性相同即可匹配。

compatible 属性通常是字符串列表,格式为'厂商,芯片型号 - 模块名'。

驱动程序中会定义相应的 compatible 属性数组,Platform 总线据此匹配。

3. 内核如何处理设备树

内核在启动过程中充当中间人,把设备树从文本变成结构体。

3.1 从 flash 到 RAM

Bootloader(U-Boot)把内核镜像和 .dtb 文件加载到内存。启动内核时,把 .dtb 文件的内存首地址通过寄存器传给内核。

3.2 展开

内存里的 .dtb 是二进制数据。内核在 setup_arch 阶段执行 unflatten_device_tree 函数,解析二进制数据,把它们展开成内核里的结构体链表。

为设备树中的每一个节点创建一个 struct device_node 结构体。

3.3 生成设备

内核调用核心函数 of_platform_populate(),遍历 device_node 树,把看起来像是设备的节点转换成 Linux 驱动模型中的 struct platform_device。

只有挂载在根节点或 simple-bus 下的子节点才会被转换。

内核解析 reg 属性转换成 IORESOURCE_MEM,interrupts 属性转换成 IORESOURCE_IRQ,最后调用 platform_device_register 注册到总线上。

4. 驱动怎么读取设备树

在 probe 函数里,通常需要获取硬件资源和设备树里面的一些自定义配置。Linux 内核提供了一套以 of_开头的函数。

4.1 获取标准资源

reg 和 interrupts 已由 of_platform_populate 自动转换。直接用 Platform 子系统的标准接口。

// 在驱动的 probe 函数中
static int my_driver_probe(struct platform_device *pdev)
{
    struct resource *res;
    int irq;

    // 获取寄存器地址,第三个参数 0 表示第 0 组 reg
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENOMEM;

    // 拿到物理地址后,要把它映射成虚拟地址才能用
    void __iomem *base_addr = devm_ioremap_resource(&pdev->dev, res);

    // 获取中断号,参数 0 表示第 0 个中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    //......
}

获取寄存器地址函数原型:

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);

获取中断号函数原型:

int platform_get_irq(struct platform_device *dev, unsigned int num);

4.2 获取自定义属性

对于自定义属性,内核没有自动转换,需要手动查表,函数都在 <linux/of.h> 中。

4.2.1 读取数字
struct device_node *np = pdev->dev.of_node;
u32 delay_time;
if (of_property_read_u32(np, "my-delay-ms", &delay_time) == 0) {
    printk("get delay time: %d\n", delay_time);
} else {
    printk("未找到该属性\n");
}
4.2.2 读取字符串
const char *str;
of_property_read_string(np, "my-name", &str);
4.2.3 读取数组
u32 data_array[3];
of_property_read_u32_array(np, "my-data", data_array, 3);

4.3 两个特殊的 API

4.3.1 获取 GPIO

现代内核推荐使用更高级的 GPIO Descriptor (gpiod) 接口。

struct gpio_desc *rst_gpio;
rst_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
4.3.2 查找节点
struct device_node *target_node;
target_node = of_find_node_by_path("/backlight/lcd-backlight");
if (target_node) {
    // 找到后,就可以用 of_property_read_xxx 去读它的属性了
}

5. 在系统运行时查看设备树

Linux 内核通过 procfs 文件系统展现解析好的设备树结构。

5.1 设备树在哪

设备树在文件系统中的实体位于 /proc/device-tree。实际上这是一个软链接,指向/sys/firmware/devicetree/base。

可以使用 tree 命令查看设备树层级。

5.2 如何验证修改的设备树是否生效

在 DTS 中添加节点后,可在 /proc/device-tree 下找到该节点。注意 name 属性即使未定义也会自动存在。

对于数值型属性(reg, interrupts, gpios 等),需用 hexdump 命令按十六进制打印,因为 cat 命令可能因空字符显示异常。

5.3 验证设备是否生成

在 /sys/bus/platform/devices/目录下执行 ls 命令,能看到熟悉的设备名。这说明设备树不仅解析成功,而且已经成功注册为板载设备。

6. 总结

设备树的引入彻底结束了 ARM 社区代码脏乱差的时代。对于驱动开发者来说,它让代码变得更加纯粹——驱动只管逻辑,硬件交给设备树。

目录

  1. 引言
  2. 1. 为什么要引入设备树
  3. 1.1 当时的环境
  4. 1.2 board file 的缺陷
  5. 1.3 设备树的引入
  6. 2. 初识设备树
  7. 2.1 三个 D
  8. 2.2 类似于头文件包含的关系
  9. 2.2.1 SOC 级文件 (.dtsi)
  10. 2.2.2 板级文件 (.dts)
  11. 2.3 如何与驱动匹配
  12. 3. 内核如何处理设备树
  13. 3.1 从 flash 到 RAM
  14. 3.2 展开
  15. 3.3 生成设备
  16. 4. 驱动怎么读取设备树
  17. 4.1 获取标准资源
  18. 4.2 获取自定义属性
  19. 4.2.1 读取数字
  20. 4.2.2 读取字符串
  21. 4.2.3 读取数组
  22. 4.3 两个特殊的 API
  23. 4.3.1 获取 GPIO
  24. 4.3.2 查找节点
  25. 5. 在系统运行时查看设备树
  26. 5.1 设备树在哪
  27. 5.2 如何验证修改的设备树是否生效
  28. 5.3 验证设备是否生成
  29. 6. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 企业微信视频号去水印解析机器人搭建指南
  • AI 驱动代码审查与错误检测工具深度评测
  • Graylog 开源日志管理平台使用指南
  • Buzz 离线语音转文字工具:基于 Whisper 的本地部署指南
  • MinIO 开源版本部署实战:避开许可证陷阱
  • Buzz 离线语音转文字工具安装与使用指南
  • 大模型检索增强生成(RAG)技术综述
  • HunyuanOCR 接入 RPA 机器人:UiPath 与影刀兼容性测试
  • Python Web 开发:Flask 框架核心概念与实战
  • RabbitMQ 通配符模式详解
  • 大模型应用开发中的高级 RAG 技术详解
  • core-js 包结构与配置策略:Polyfill 解决前端兼容性问题
  • 使用 Mac Mini 部署 OpenClaw 打造金融 AI 分析助手
  • OpenClaw 飞书机器人搭建流程
  • MySQL 事务核心概念与隔离级别实战
  • Python 爬虫基础教程:从原理到实战代码详解
  • MS-SWIFT 多模态实战:云端 GPU 快速部署 AI 绘画
  • 基于 Spring Boot 的电影交流平台设计与实现
  • Python 爬虫开发入门:从基础原理到实战应用
  • 基于 Microi 吾码低代码框架构建 Vue 高效应用

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online