Linux设备树到底是啥?一张图看懂硬件适配的「翻译官」 电子说
你有没有想过:同一份 Linux 内核镜像,为啥能在不同型号的开发板上跑起来?比如一块 ARM 架构的开发板,今天换个显示屏、明天加个传感器,内核不用重新编译就能识别新硬件 —— 这背后,设备树(Devicetree) 功不可没。
很多嵌入式工程师刚接触设备树时,总被“节点”“属性”“绑定规范” 这些术语绕晕。其实设备树的本质特别简单:它就是硬件和内核之间的“翻译官”,把硬件的“长相” 和 “能力” 写成标准化的文件,让内核不用 “硬编码” 就能读懂硬件。
今天咱们用“人话 + 流程图” 拆解设备树,从 “为什么需要它” 到 “内核怎么用它”,一次讲透核心逻辑。
在设备树出现前,Linux 适配硬件靠的是 “硬编码”—— 把硬件参数(比如串口地址、中断号)直接写进内核代码里。比如要支持一款新开发板,工程师得:
1.在内核中新增一个“板级文件”,写死该板子的所有硬件配置;
2.编译内核时选择对应板子的配置,生成专属镜像;
3.要是换个硬件(比如把串口从 UART1 换成 UART2),就得修改代码、重新编译。
这种方式的痛点太明显了:一款硬件对应一个内核镜像,嵌入式厂商要维护几十上百个镜像,成本极高。
而设备树的出现,彻底解决了这个问题:它把硬件描述从内核中“剥离” 出来,做成独立的 DTB 文件(设备树二进制文件)。内核启动时读取 DTB,就能动态识别硬件 —— 从此实现 “一个内核镜像适配 N 种硬件”。
设备树的结构特别像一棵“硬件家谱”,最核心的是 3 个概念:节点(Node)、属性(Property)、路径(Path)。咱们用一个简单的例子看懂:
/* 设备树源码(DTS文件)示例 *// { // 根节点:代表整个硬件系统compatible = "ti,omap3-beagleboard", "ti,omap3"; // 属性:告诉内核这是哪款硬件chosen { // 子节点:专门存储运行时配置bootargs = "console=ttyS0,115200"; // 属性:内核命令行(指定串口控制台)};soc { // 子节点:代表SoC(系统级芯片)compatible = "simple-bus"; // 属性:说明这是“简单内存映射总线”uart0: serial@4806a000 { // 子节点:串口设备(@后是基地址)compatible = "ti,omap3-uart"; // 属性:告诉内核用什么驱动reg = <0x4806a000 0x1000>; // 属性:地址范围(基地址+大小)interrupts = <72>; // 属性:中断号};};};
简单理解:
•节点:对应一个硬件模块(如根节点 = 整个系统、uart0 = 串口),用节点名@地址命名(地址可选,用于区分同类型设备);
•属性:描述硬件的具体参数,格式是键=值(值可以是字符串、数字、二进制),比如compatible是“设备兼容性标识”,reg是“内存 / IO 地址”;
•路径:像文件路径一样定位节点,比如串口节点的路径是/soc/uart0。
记住一个关键原则:设备树只描述“硬件有什么、参数是多少”,不包含任何驱动逻辑—— 驱动靠 “匹配设备树属性” 来关联硬件。
设备树的生命周期从“编译” 到 “内核使用”,分为 3 个关键阶段。咱们结合流程图,一步步看内核是如何通过设备树识别并控制硬件的。
工程师写的是DTS 文件(设备树源码,人类可读),但内核只能识别DTB 文件(设备树二进制,机器可读)。这个转换靠工具dtc(Device Tree Compiler)完成:
dtc -I dts -O dtb -o my_board.dtb my_board.dts
最终生成的 DTB 文件,会和内核镜像一起放在开发板的启动分区(比如 boot 分区)。
开发板上电后,先运行引导程序(如 U-Boot),引导程序做两件关键的事:
1.初始化硬件(比如内存、串口);
2.把 DTB 文件加载到内存的指定地址,然后启动内核,并告诉内核 “DTB 在内存的哪里”。
这一步就像:引导程序把“硬件家谱”(DTB)递给内核,说 “这是你要管理的硬件,先看看说明书”。
这是最核心的阶段,内核通过 3 个关键步骤,把 DTB 中的 “硬件描述” 变成可操作的 “设备实例”。咱们用流程图 + 通俗解释拆解:

咱们把每个阶段掰开揉碎讲:
内核启动后,首先要确定自己跑在什么硬件上(比如是 BeagleBoard 还是树莓派),这一步靠根节点的 compatible 属性。
比如根节点的compatible = "ti,omap3-beagleboard", "ti,omap3",这个属性是“从具体到通用” 的列表:
•第一个值“ti,omap3-beagleboard”:精确匹配 “TI 的 omap3 系列 BeagleBoard 开发板”;
•第二个值“ti,omap3”:兼容 “TI 的 omap3 系列所有板子”。
内核会遍历自己的“平台描述库”,找到和compatible最匹配的项—— 比如找到 BeagleBoard 的初始化逻辑,就执行对应的硬件初始化(如设置时钟、电源)。
设备树中的/chosen节点是专门给内核传参数的“通道”,最常用的是bootargs属性(内核命令行)。
比如bootargs = "console=ttyS0,115200 loglevel=8",意思是:
•console=ttyS0,115200:把串口 0(UART0)作为控制台,波特率 115200;
•loglevel=8:显示所有级别的内核日志(方便调试)。
内核会解析这些参数,完成基础配置—— 比如初始化串口控制台,让开发者能通过串口看到内核启动日志。
这是设备树的最终目的:内核根据 DTB 中的节点,动态创建 “设备实例”,再让驱动去匹配这些设备。
关键函数是of_platform_populate(),它的逻辑很简单:
1.从指定节点(默认是根节点)开始,遍历所有子节点;
2.对每个包含compatible属性的节点,创建一个“平台设备”(platform_device);
3.驱动通过of_match_table(设备树匹配表),根据compatible属性找到对应的设备,完成“驱动 - 设备” 绑定。
举个例子:串口节点uart0的compatible = "ti,omap3-uart",内核会:
•创建一个名为serial@4806a000的平台设备;
•串口驱动的of_match_table中,正好有“ti,omap3-uart” 这一项,于是驱动和设备绑定;
•绑定后,驱动就能通过设备树中的reg(地址)、interrupts(中断号),控制串口硬件收发数据。
1.设备树能替代驱动吗?
不能!设备树只描述硬件参数,驱动才是控制硬件的“大脑”。比如设备树告诉内核 “串口在 0x4806a000 地址”,但怎么发数据、收数据,还得靠串口驱动实现。
2.compatible 属性写错了会怎样?
驱动找不到设备!比如把“ti,omap3-uart” 写成 “ti,omap4-uart”,串口驱动的匹配表中没有这个值,设备就会处于 “未绑定” 状态,无法使用。
3.DTB 文件放错位置会怎样?
内核启动失败!引导程序如果没加载 DTB,或者内核没找到 DTB,会报 “Cannot find device tree” 错误,然后卡住 —— 因为内核不知道自己要管理什么硬件。
其实设备树的核心价值,就在于“标准化”:
•对硬件厂商:按规范写 DTS,不用改内核代码;
•对内核开发者:按规范写驱动,不用适配每款硬件;
•对嵌入式工程师:换硬件只换 DTB,不用重新编译内核。
记住一句话:设备树是“硬件的说明书”,驱动是 “读懂说明书并操作硬件的人”—— 两者配合,才能让 Linux 在千变万化的硬件上跑起来。
如果看完还是有点晕,建议找一款简单的开发板,打开它的 DTS 文件,对照本文的流程逐行看:根节点的 compatible、chosen 节点的 bootargs、外设节点的 reg 和 interrupts—— 慢慢就会发现,设备树其实没那么复杂~
全部0条评论
快来发表一下你的评论吧 !