一、Overlay概念与背景:为什么需要动态加载?传统设备树的局限性
各位同学,咱们今天聊点实在的。
做嵌入式Linux开发,设备树(Device Tree)这东西,大家肯定不陌生。但说实话,我在早期做项目时,对设备树的态度就是——写一次,烧进去,再也不动。直到有一次,我被一个硬件改版折腾得够呛,才真正意识到:传统设备树,其实挺死板的。
1.1 传统设备树的工作方式
传统设备树,说白了就是一个描述硬件信息的静态数据结构。它把CPU、内存、外设、中断、GPIO这些东西,统统用树形节点表示出来。内核启动时,Bootloader把设备树二进制(DTB)传给内核,内核解析后,就知道该怎么初始化硬件了。
流程大概是这样的:
设备树源文件(.dts) → 编译 → 设备树二进制(.dtb) → Bootloader加载 → 内核解析 → 硬件初始化
嗯,看起来挺顺的。但问题在哪呢?
问题就在于——这个DTB是固定的。一旦编译好,硬件就不能变了。你想想看,如果产品有多个硬件版本,或者用户想插个扩展板,怎么办?
核心痛点:传统设备树是静态的、全局的、一次性的。它不支持运行时修改,也不支持模块化组合。
1.2 我在项目中踩过的坑
我记得有一次,做一个工业控制板。主板上有几个扩展接口,可以接不同的功能模块——有的接摄像头,有的接传感器,有的接电机驱动。硬件设计上,这些模块共用同一组引脚,通过I2C或SPI通信。
按传统做法,我得把所有可能的硬件配置都写到一个dts文件里。结果呢?
- dts文件变得巨长,光节点就几百个
- 很多外设其实根本不会同时存在
- 每次硬件改版,都得重新编译整个设备树
- 最要命的是——引脚冲突。两个模块共用同一组引脚,但dts里同时使能了,内核直接崩掉
我当时就在想:能不能像Linux内核模块那样,需要什么硬件,就动态加载什么设备树片段?
嗯,这就是Overlay的由来。
1.3 Overlay是什么?
设备树Overlay,说白了就是设备树的“补丁”或“插件”。它允许你在系统运行时,动态地添加、修改或删除设备树中的节点。
打个比方:
- 传统设备树 = 一张完整的建筑蓝图,盖好就不能改
- Overlay = 乐高积木,你可以随时拼一块新的上去
Overlay的编译和加载流程:
Overlay源文件(.dts) → 编译 → Overlay二进制(.dtbo) → 内核动态加载 → 硬件生效
注意看,这里多了一个“动态加载”的步骤。而且Overlay文件是.dtbo后缀,不是.dtb。
1.4 为什么需要动态加载?
我总结了一下,主要有这几个场景:
| 场景 | 传统做法 | Overlay做法 |
|---|---|---|
| FPGA动态重配置 | 必须提前在dts里预留所有可能的外设 | FPGA加载后,动态添加对应的设备树节点 |
| 扩展板/子卡 | 每个扩展板都得重新编译整个设备树 | 插上扩展板,加载对应的Overlay即可 |
| 硬件版本差异 | 用多个dts文件,编译多个dtb | 一个基础dtb + 多个Overlay,按需组合 |
| 热插拔外设 | 不支持,必须重启 | 支持,动态加载/卸载 |
| 调试与开发 | 改一次硬件,烧一次固件 | 直接加载Overlay,不用重启 |
说白了,Overlay解决的就是“硬件变化”这个核心问题。你不需要为每一种硬件组合都准备一个完整的设备树,只需要准备一个基础设备树,再加上若干个Overlay片段就行。
1.5 传统设备树的局限性
咱们再深入聊聊,传统设备树到底有哪些局限?
- 静态性:设备树在编译时就固定了,运行时不能改。内核启动后,你再想加一个I2C设备?对不起,不行。
- 全局性:一个dts文件描述整个板子的硬件。如果两个外设共用引脚,dts里必须手动处理冲突,否则编译都过不了。
- 不可组合:没法把多个独立的设备树片段拼在一起。比如A模块的dts和B模块的dts,不能直接合并,因为可能有节点ID冲突。
- 调试困难:改一次硬件,就得重新编译、烧写、重启。我当年调试一个SPI设备,一天重启了上百次,人都麻了。
- 维护成本高:产品线多了,每个硬件版本都得维护一个完整的dts文件。稍微改个引脚,就得开分支。
注意:传统设备树并不是一无是处。对于固定硬件的产品(比如手机、路由器),它依然是最佳选择。Overlay主要解决的是硬件可变的场景。
1.6 Overlay的核心思想
Overlay的核心思想其实很简单:分而治之,动态组合。
你把一个完整的设备树,拆成两部分:
- 基础设备树(Base DTB):描述板上固定的硬件,比如CPU、内存、时钟、串口等
- Overlay片段(DTBO):描述可变的、可插拔的硬件,比如扩展板、FPGA外设等
系统启动时,先加载基础设备树。然后,根据实际硬件情况,动态加载一个或多个Overlay。Overlay可以添加新节点,也可以修改已有节点(比如修改引脚复用、调整时序参数)。
我画了一张图,帮你理解这个关系:
你看,基础设备树是固定的,Overlay是可选的。系统启动时,内核先加载基础设备树。然后,根据实际插了什么硬件,再加载对应的Overlay。这样,一个基础设备树就能应对多种硬件组合。
我的建议:刚开始接触Overlay时,别想得太复杂。你就把它当成“设备树的补丁文件”。基础设备树是主文件,Overlay是补丁。补丁可以随时打上,也可以随时撤销。
1.7 一个简单的例子
咱们来看一个最简单的Overlay长什么样:
// 基础设备树片段 (base.dts)
/dts-v1/;
/ {
model = "MyBoard";
compatible = "mycompany,myboard";
i2c0: i2c@ff000000 {
compatible = "mycompany,i2c";
reg = <0xff000000 0x1000>;
status = "okay";
};
};
// Overlay片段 (overlay.dts)
/dts-v1/;
/plugin/;
&i2c0 {
temperature-sensor@48 {
compatible = "mycompany,tmp102";
reg = <0x48>;
};
};
注意看,Overlay文件开头多了个/plugin/;,这是告诉编译器:这是一个Overlay,不是完整的设备树。然后通过&i2c0引用基础设备树中的节点,往里面添加子节点。
编译命令也不一样:
# 编译基础设备树
dtc -I dts -O dtb -o base.dtb base.dts
# 编译Overlay
dtc -I dts -O dtb -o overlay.dtbo overlay.dts
Overlay编译出来是.dtbo后缀,不是.dtb。这个细节要注意,我当年就因为这个后缀搞错过,内核死活不认。
1.8 小结
好了,咱们把这一章的核心内容捋一捋:
- 传统设备树是静态的、全局的、一次性的,适合固定硬件
- Overlay是设备树的动态补丁,允许运行时添加/修改硬件描述
- 核心价值:解决硬件可变、模块化组合、热插拔、调试效率等问题
- 使用方式:基础DTB + 多个DTBO,按需加载
下一章,咱们会深入Overlay的语法细节,看看/plugin/;到底做了什么,以及如何正确地编写Overlay文件。嗯,到时候我会分享一些我在项目中踩过的坑,保证让你少走弯路。