Linux设备树到底是啥?一张图看懂硬件适配的「翻译官」
你有没有想过:同一份Linux内核镜像,为啥能在不同型号的开发板上跑起来?比如一块ARM架构的开发板,今天换个显示屏、明天加个传感器,内核不用重新编译就能识别新硬件——这背后,设备树(Devicetree)功不可没。
很多嵌入式工程师刚接触设备树时,总被“节点”“属性”“绑定规范”这些术语绕晕。其实设备树的本质特别简单:它就是硬件和内核之间的“翻译官”,把硬件的“长相”和“能力”写成标准化的文件,让内核不用“硬编码”就能读懂硬件。
今天咱们用“人话+流程图”拆解设备树,从“为什么需要它”到“内核怎么用它”,一次讲透核心逻辑。
一、先搞懂:没有设备树时,Linux有多“难”?
在设备树出现前,Linux适配硬件靠的是“硬编码”——把硬件参数(比如串口地址、中断号)直接写进内核代码里。比如要支持一款新开发板,工程师得:
1.在内核中新增一个“板级文件”,写死该板子的所有硬件配置;
2.编译内核时选择对应板子的配置,生成专属镜像;
3.要是换个硬件(比如把串口从UART1换成UART2),就得修改代码、重新编译。
这种方式的痛点太明显了:一款硬件对应一个内核镜像,嵌入式厂商要维护几十上百个镜像,成本极高。
而设备树的出现,彻底解决了这个问题:它把硬件描述从内核中“剥离”出来,做成独立的DTB文件(设备树二进制文件)。内核启动时读取DTB,就能动态识别硬件——从此实现“一个内核镜像适配N种硬件”。
二、设备树的核心:3层结构,像给硬件画“家谱”
设备树的结构特别像一棵“硬件家谱”,最核心的是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 = <0x4806a0000x1000>; //属性:地址范围(基地址+大小)interrupts = <72>; //属性:中断号};};};
简单理解:
•节点:对应一个硬件模块(如根节点=整个系统、uart0 =串口),用节点名@地址命名(地址可选,用于区分同类型设备);
•属性:描述硬件的具体参数,格式是键=值(值可以是字符串、数字、二进制),比如compatible是“设备兼容性标识”,reg是“内存/ IO地址”;
•路径:像文件路径一样定位节点,比如串口节点的路径是/soc/uart0。
记住一个关键原则:设备树只描述“硬件有什么、参数是多少”,不包含任何驱动逻辑——驱动靠“匹配设备树属性”来关联硬件。
三、内核怎么用设备树?3步流程+ 1张图讲透
设备树的生命周期从“编译”到“内核使用”,分为3个关键阶段。咱们结合流程图,一步步看内核是如何通过设备树识别并控制硬件的。
第一步:设备树文件的“变身”(编译阶段)
工程师写的是DTS文件(设备树源码,人类可读),但内核只能识别DTB文件(设备树二进制,机器可读)。这个转换靠工具dtc(Device Tree Compiler)完成:
dtc -Idts -O dtb -o my_board.dtbmy_board.dts
最终生成的DTB文件,会和内核镜像一起放在开发板的启动分区(比如boot分区)。
第二步:启动时传递DTB(引导阶段)
开发板上电后,先运行引导程序(如U-Boot),引导程序做两件关键的事:
1.初始化硬件(比如内存、串口);
2.把DTB文件加载到内存的指定地址,然后启动内核,并告诉内核“DTB在内存的哪里”。
这一步就像:引导程序把“硬件家谱”(DTB)递给内核,说“这是你要管理的硬件,先看看说明书”。
第三步:内核解析DTB,创建设备(内核初始化阶段)
这是最核心的阶段,内核通过3个关键步骤,把DTB中的“硬件描述”变成可操作的“设备实例”。咱们用流程图+通俗解释拆解:
咱们把每个阶段掰开揉碎讲:
阶段1:平台识别——内核先搞清楚“我跑在哪个板子上”
内核启动后,首先要确定自己跑在什么硬件上(比如是BeagleBoard还是树莓派),这一步靠根节点的compatible属性。
比如根节点的compatible = "ti,omap3-beagleboard", "ti,omap3",这个属性是“从具体到通用”的列表:
•第一个值“ti,omap3-beagleboard”:精确匹配“TI的omap3系列BeagleBoard开发板”;
•第二个值“ti,omap3”:兼容“TI的omap3系列所有板子”。
内核会遍历自己的“平台描述库”,找到和compatible最匹配的项——比如找到BeagleBoard的初始化逻辑,就执行对应的硬件初始化(如设置时钟、电源)。
阶段2:运行时配置——内核获取“启动参数”
设备树中的/chosen节点是专门给内核传参数的“通道”,最常用的是bootargs属性(内核命令行)。
比如bootargs = "console=ttyS0,115200 loglevel=8",意思是:
•console=ttyS0,115200:把串口0(UART0)作为控制台,波特率115200;
•loglevel=8:显示所有级别的内核日志(方便调试)。
内核会解析这些参数,完成基础配置——比如初始化串口控制台,让开发者能通过串口看到内核启动日志。
阶段3:创建设备——内核把“硬件描述”变成“可操作设备”
这是设备树的最终目的:内核根据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(中断号),控制串口硬件收发数据。
四、记住3个关键问题,避免踩坑
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——慢慢就会发现,设备树其实没那么复杂~
本文 zblog模板 原创,转载保留链接!网址:https://www.wbaas.cn/fengrong/1329.html
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。
