1. 设备树的前世今生:为什么需要设备树?
做嵌入式Linux开发这么多年,我经常被问到同一个问题:「设备树到底是个啥?为啥以前没有它,现在却非用不可?」
说实话,我刚入行那会儿也没有设备树。那时候写驱动,最头疼的就是改板级文件。每次换一块新板子,都得把内核源码翻个底朝天。嗯,今天我们就聊聊这段历史。
1.1 板级文件时代的噩梦
在设备树出现之前,Linux内核是怎么描述硬件的?
答案很简单——硬编码。每个开发板都在内核源码里有一个对应的 board-xxx.c 文件。比如你的板子叫 imx6q-sabresd,那内核里就有一个 arch/arm/mach-imx/mach-imx6q.c。
这个文件里写什么呢?我给大家看个简化版:
// 伪代码:板级文件
static struct resource uart1_resources[] = {
{
.start = 0x02020000,
.end = 0x02020fff,
.flags = IORESOURCE_MEM,
},
{
.start = 58,
.end = 58,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device uart1_device = {
.name = "imx-uart",
.id = 0,
.num_resources = ARRAY_SIZE(uart1_resources),
.resource = uart1_resources,
};
static void __init my_board_init(void)
{
platform_device_register(&uart1_device);
// 注册GPIO、I2C、SPI... 几百行代码
}
你想想看,每个板子都得写这么一套。我当时维护过三个不同厂商的板子,每个板子的 mach-xxx.c 文件都有上千行。最崩溃的是什么?
- 代码冗余:同一款SoC,换个板子就得复制粘贴改地址
- 维护地狱:Linux内核版本升级,板级文件接口变了,全部重写
- 碎片化严重:厂商各自为政,内核社区根本管不过来
1.2 设备树的诞生:从PowerPC到ARM
设备树(Device Tree)其实不是Linux原创的。它最早来自Open Firmware,是PowerPC架构上用来描述硬件的一种方式。
2011年左右,ARM架构的碎片化问题已经到了不可收拾的地步。Linus Torvalds本人都在邮件列表里发飙了——「ARM的板级文件就是一堆垃圾!」
于是,ARM社区决定引入设备树。说白了,就是把硬件的描述信息从C代码里剥离出来,放到一个独立的文本文件里。这个文件就是 .dts(Device Tree Source)。
它的样子是这样的:
// imx6qdl-sabresd.dtsi
/ {
soc {
aips1: aips-bus@02000000 {
uart1: serial@02020000 {
compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
reg = <0x02020000 0x4000>;
interrupts = <0 26 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6QDL_CLK_UART_IPG>,
<&clks IMX6QDL_CLK_UART_SERIAL>;
clock-names = "ipg", "per";
status = "disabled";
};
};
};
};
看到区别了吗?原来写在C代码里的寄存器地址、中断号、时钟信息,现在全写在一个树形结构的文本里。驱动代码只需要通过API去解析这个树,就能拿到所有硬件信息。
1.3 设备树在Linux内核中的角色定位
设备树到底扮演什么角色?我用一句话概括:它是硬件描述与驱动代码之间的契约。
具体来说,设备树承担了三个核心职责:
| 职责 | 说明 | 我的理解 |
|---|---|---|
| 硬件描述 | 告诉内核:CPU是什么、内存多大、外设挂在哪里 | 相当于硬件的「身份证」 |
| 配置参数 | 提供驱动需要的运行时参数,比如时钟频率、引脚复用 | 相当于驱动的「配置文件」 |
| 板级差异隔离 | 同一款SoC的不同板子,只需改dts文件,不用改驱动 | 这才是最爽的地方 |
核心要点:设备树不是内核的一部分,它是在内核启动时由Bootloader加载到内存中的二进制数据(DTB)。内核启动后,会解析这个DTB,动态构建出平台设备(platform_device)和设备节点(device_node)。
1.4 从板级文件到设备树的演进路线
这个演进过程不是一蹴而就的。我把它分成三个阶段:
- 混沌期(2000-2010):全是板级文件,每个厂商自己玩自己的。内核里充斥着
#ifdef CONFIG_BOARD_XXX这样的条件编译。 - 过渡期(2011-2014):ARM社区强制要求新板子必须使用设备树。但老板子还在用板级文件,两套方案并存。那段时间我经常要同时维护两种风格的代码。
- 成熟期(2015至今):板级文件基本被淘汰。所有主流ARM SoC都使用设备树。RISC-V架构也沿用了这套方案。
我个人觉得,这个演进最核心的驱动力就两个字——解耦。把硬件描述和驱动逻辑解耦,把板级差异和SoC共性解耦。你想想看,没有设备树的时候,换一块板子就得重新编译整个内核。现在呢?只需要换一个 .dtb 文件就行。
1.5 设备树的核心知识体系
为了让大家有个整体认知,我画了一张图。这张图涵盖了设备树从编写到使用的完整链路:
一个小技巧:刚开始学设备树的时候,别急着看语法。先理解这个链路——DTS怎么变成DTB,DTB怎么被内核解析,驱动怎么从设备树拿资源。把流程走通了,语法自然就记住了。
1.6 为什么设备树能活下来?
其实在设备树之前,也有过其他方案,比如ACPI(高级配置与电源管理接口)。但ACPI太复杂了,适合x86这种标准化平台。嵌入式平台千奇百怪,需要的是轻量、灵活、可读性强的方案。
设备树恰好满足了这些需求:
- 文本格式:用文本编辑器就能改,不用重新编译内核
- 树形结构:硬件本身就是树形的(SoC→总线→外设),天然匹配
- 可覆盖:
.dtsi文件可以包含,.dts文件可以覆盖,复用性极强 - 社区支持:内核社区强制推行,厂商不配合都不行
我记得2012年第一次用设备树的时候,心里还犯嘀咕:「这玩意儿靠谱吗?」结果用了一个月就真香了。以前改个GPIO引脚要翻半天代码,现在改一行dts就行。
说白了,设备树解决的不是技术问题,而是工程管理问题。它让硬件描述变得可维护、可复用、可版本控制。这才是它能在嵌入式Linux领域站稳脚跟的根本原因。