一、设备树是什么:从一块开发板说起
我记得刚入行那会儿,做嵌入式Linux开发最头疼的事是什么?
不是写驱动,不是调中断,而是——换一块开发板,内核就要重新编译一遍。
为什么?因为那时候硬件信息是硬编码在代码里的。板子上的GPIO接了个LED,你得在arch/arm/mach-xxx/board-xxx.c里写死。换一颗Flash芯片?改代码。换一个网卡?改代码。甚至只是把某个外设的地址挪了挪位置,还是得改代码。
这合理吗?不合理。但当时大家都这么干。
1.1 设备树的起源
设备树(Device Tree)这个概念,最早来自PowerPC架构。2005年左右,PowerPC的开发者们受够了这种「改板子=改内核」的玩法。他们想:能不能把硬件描述从内核代码里抽出来,变成一份独立的数据文件?
这个想法后来被ARM架构看上了。ARM生态有多乱,做过的人都知道——每家芯片厂商都有自己的板子,每个板子都有自己的外设布局。如果每个板子都要维护一份board-xxx.c,那内核维护者得疯掉。
所以从Linux 3.x开始,ARM架构全面转向设备树。到后来,RISC-V、MIPS、X86(某些场景)也都跟进了。
核心思想:设备树就是把硬件描述从内核源码中剥离出来,变成一份独立的数据结构。内核启动时解析这份数据,动态构建设备模型。
1.2 为什么需要设备树
你想想看,如果没有设备树,会是什么场景?
- 代码膨胀:每个板子都要在arch/arm/mach-xxx/下加一堆board文件。Linux 3.0之前,ARM架构下有上百个board文件,很多都是重复的。
- 维护噩梦:换一个板子就要重新编译内核。用户拿到的内核镜像,跟硬件是绑死的。
- 碎片化严重:同样的驱动,因为板级信息不同,要写多个版本。我见过一个GPIO驱动,因为board文件里中断号写的位置不一样,硬生生搞出三个ifdef分支。
设备树解决了这些问题。它把硬件描述变成了一份文本文件(.dts),编译成二进制(.dtb),跟内核镜像分开存放。启动时,bootloader把dtb传给内核,内核解析后就知道:哦,这个板子有512MB内存,UART在0x10000000,网卡是RTL8169,中断号是32。
我个人习惯:把设备树想象成硬件的「身份证」。内核拿到这张身份证,就知道该怎么跟硬件打交道。换板子?换身份证就行,内核不用动。
1.3 设备树在Linux内核中的角色
设备树在内核里到底扮演什么角色?我用一张图来说明:
这张图展示了设备树的核心位置。它夹在硬件和内核之间,充当翻译官的角色。内核不直接跟硬件对话,而是通过设备树来了解硬件。
1.4 设备树到底长什么样
说了这么多,设备树文件到底长什么样?我贴一个最简单的例子:
// 一个简单的设备树源文件
/dts-v1/;
/ {
model = "MyBoard V1.0";
compatible = "vendor,myboard-v1", "vendor,myboard";
#address-cells = <1>;
#size-cells = <1>;
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB内存
};
uart0: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupts = <32>;
clock-frequency = <1843200>;
};
gpio0: gpio@20000000 {
compatible = "vendor,gpio-controller";
reg = <0x20000000 0x1000>;
#gpio-cells = <2>;
gpio-controller;
};
};
这段代码描述了一个简单的开发板:256MB内存,一个UART,一个GPIO控制器。每个设备都有它的地址、中断号、时钟频率等信息。
我曾经踩过一个坑:设备树里reg属性的地址写错了,导致内核访问外设时直接挂死。调试了整整两天才发现是地址偏移了一位。所以写设备树时,地址和大小一定要跟硬件手册再三核对。
1.5 设备树的核心价值
总结一下,设备树给嵌入式Linux带来了什么:
| 维度 | 没有设备树 | 有设备树 |
|---|---|---|
| 硬件描述 | 硬编码在C代码中 | 独立的数据文件 |
| 板级支持 | 每个板子一个board文件 | 每个板子一个.dts文件 |
| 内核编译 | 换板子必须重新编译 | 只需更换dtb文件 |
| 驱动开发 | 驱动与板级信息耦合 | 驱动与板级信息解耦 |
| 维护成本 | 高(代码冗余严重) | 低(描述与实现分离) |
说白了,设备树就是让「硬件描述」和「驱动代码」各司其职。硬件描述归硬件描述,驱动代码归驱动代码。两者通过设备树这个接口对接,互不干扰。
我的理解:设备树是嵌入式Linux走向工程化、标准化的重要一步。它让内核不再跟具体的板子绑定,让驱动可以跨平台复用。如果你做过裸机开发,就知道这有多重要——裸机程序里,硬件信息散落在各个头文件和宏定义里,改一个引脚定义要翻遍整个工程。设备树把这种混乱变成了有序。
1.6 小结
这一章我们聊了设备树的起源、为什么需要它、以及它在内核中的角色。核心就三句话:
- 设备树是硬件的「身份证」
- 它把硬件描述从内核代码中剥离出来
- 它让驱动开发与板级支持解耦
下一章,我们会深入设备树的语法和结构,看看.dts文件到底怎么写、怎么编译、怎么调试。嗯,到时候我会分享一些实战中遇到的坑,保证让你少走弯路。
可可精品资料库