第3章:设备树文件结构:dts文件、dtsi文件、dtb文件、dtc编译器、编译与反编译实战
设备树这东西,刚接触时容易一头雾水。一堆后缀名,dts、dtsi、dtb,到底啥关系?
说白了,设备树就是一套描述硬件信息的文本文件。内核启动时读它,才知道你板子上接了啥外设。
我刚开始做BSP时,也搞混过这些概念。有一次把dtsi当成dts直接编译,折腾了半天。嗯,今天咱们就把这些文件类型彻底理清楚。
3.1 文件类型总览
设备树涉及四种核心文件/工具:
| 文件/工具 | 全称 | 作用 |
|---|---|---|
| .dts | Device Tree Source | 设备树源文件,描述具体板级硬件 |
| .dtsi | Device Tree Source Include | 设备树头文件,存放通用/SoC级描述 |
| .dtb | Device Tree Blob | 编译后的二进制文件,内核直接使用 |
| dtc | Device Tree Compiler | 编译器,把dts/dtsi转成dtb |
核心关系:dts + dtsi → dtc编译 → dtb → 内核解析
3.2 dts文件与dtsi文件
dts是板级描述文件。每个具体的板子,都有一个对应的dts文件。
比如我做过一个i.MX6ULL的项目,板子叫myboard,那就有一个myboard.dts。
dtsi是公共描述文件。它存放SoC通用的硬件信息,比如CPU核数、中断控制器、时钟等。
为什么要有dtsi?你想想看,同一款SoC可能用在几十种板子上。如果每个dts都重复写一遍CPU描述,那得多累?
dtsi通过#include被dts引用。举个例子:
// myboard.dts
#include "imx6ull.dtsi"
/ {
model = "MyBoard i.MX6ULL Board";
compatible = "myboard,imx6ull";
memory {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB内存
};
&uart1 {
status = "okay";
};
};
而imx6ull.dtsi里,会定义SoC级别的节点:
// imx6ull.dtsi
/ {
#address-cells = <1>;
#size-cells = <1>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0x0>;
};
};
uart1: uart@02020000 {
compatible = "fsl,imx6ul-uart";
reg = <0x02020000 0x4000>;
interrupts = <0 26 4>;
status = "disabled";
};
};
我的习惯:dtsi里节点默认status = "disabled",板级dts里再按需打开。这样复用性最好。
3.3 dtb文件
dtb是二进制文件。内核启动时,uboot会把dtb加载到内存,然后传给内核。
dtb不能直接编辑。你想改硬件描述?必须改dts,重新编译。
我曾经犯过一个错:直接修改dtb的二进制数据,结果内核解析时直接崩溃。嗯,后来老老实实改dts了。
3.4 dtc编译器
dtc是设备树编译器。它把dts/dtsi编译成dtb。
Linux内核源码里自带dtc,路径在scripts/dtc/目录下。
基本用法:
# 编译dts为dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译dtb为dts
dtc -I dtb -O dts -o myboard.dts myboard.dtb
参数说明:
-I:输入格式(dts或dtb)-O:输出格式(dts或dtb)-o:输出文件名
注意:反编译得到的dts,节点名和属性名可能和原始dts不完全一致。比如一些标签名会丢失。所以反编译主要用于调试,不建议作为修改的起点。
3.5 编译与反编译实战
咱们来动手试试。假设你有一个myboard.dts文件。
第一步:编译
dtc -I dts -O dtb -o myboard.dtb myboard.dts
如果dts语法正确,会生成myboard.dtb。没有报错就是成功。
第二步:反编译验证
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb
然后对比myboard.dts和myboard_decompiled.dts。你会发现结构基本一致,但有些标签名可能变了。
第三步:检查dtb内容
可以用hexdump查看dtb的二进制内容:
hexdump -C myboard.dtb | head -20
dtb文件以魔数0xd00dfeed开头。我每次看到这个魔数,都觉得设计者挺幽默的。
避坑指南:我曾经在编译时忘记包含dtsi文件,结果dtb里缺少SoC核心节点,内核启动到一半就挂了。所以编译前,务必确认#include路径正确。
3.6 知识体系结构图
下面这张图,帮你理清设备树文件之间的关系:
3.7 小结
这一章咱们把设备树的文件结构理清楚了。
- dts是板级描述,dtsi是SoC级公共描述
- dtc负责编译,生成dtb二进制文件
- dtb是内核最终使用的格式
- 反编译可以用于调试,但别依赖它做修改
我个人建议,刚开始学设备树时,多动手编译、反编译几次。把dts和dtb的对应关系摸透了,后面学节点、属性就轻松多了。
小技巧:在Linux内核源码目录下,执行make dtbs可以一键编译所有dts。但调试阶段,我更喜欢手动调用dtc,这样能清楚看到每个步骤的输出。