设备树中的reg属性是描述硬件地址资源的核心机制,它的格式由父节点的#address-cells和#size-cells两个属性共同决定。#address-cells表示地址用几个32位单元,#size-cells表示长度用几个32位单元。32位系统通常是1个地址单元加1个大小单元,64位系统则需要2个地址单元。地址映射是从子节点到根节点逐层翻译的过程,通过每层的ranges属性将本地总线地址翻译为父总线地址,最终得到CPU物理地址。驱动中通过of_address_to_resource或ioremap来获取和映射这些地址。
reg属性描述设备的地址资源,格式是"地址 + 长度"的多组数据
#address-cells和#size-cells是父节点的属性,决定子节点reg的格式
64位地址用2个cells表示高32位和低32位,高32位在前
地址翻译:子节点地址 → 经父节点ranges翻译 → 逐层向上直到根节点
驱动中用of_address_to_resource获取物理地址,ioremap映射到虚拟地址
一、面试题
面试官问:"设备树中的reg属性你理解吗?#address-cells和#size-cells是什么意思?地址映射是怎么工作的?"
二、面试官考察点
考察方向
第一,是否真正理解设备树的地址表示机制,还是只会复制粘贴别人的dts;第二,是否搞清楚了#address-cells和#size-cells属于哪个节点,这是一个经典的易错点;第三,是否理解地址翻译的层级关系和ranges属性的作用;第四,是否有实际的驱动开发经验,知道驱动代码中怎么获取和使用这些地址。
三、回答思路(对话式展开)
先说说reg属性是干什么的吧,格式是什么样的?
reg属性是用来描述一个设备的地址资源的,也就是这个设备在它所在的总线地址空间里占据哪一段地址范围。格式是一个由多组"地址 + 长度"组成的数组,用尖括号括起来。比如常见的32位处理器平台,一个设备的寄存器基地址是0x12340000,大小是4KB,那reg就写成<0x12340000 0x1000>。
那#address-cells和#size-cells呢?它们和reg是什么关系?
这两个属性是父节点的属性,用来规定子节点的reg怎么解析。这个点挺容易搞混的,我刚开始学时也经常搞错。#address-cells表示子节点reg中的"地址"部分用几个32位单元来表示,#size-cells表示"长度"部分用几个32位单元。
举个例子,父节点如果设了#address-cells = <1>,#size-cells = <1>,那每个子节点的reg就是"一个地址加一个长度"的格式。如果是64位地址系统,#address-cells就等于2,高32位在前,低32位在后。
1. 具体例子说明
举一个典型的32位ARM平台的设备树片段:
在这个例子里,uart节点的reg是<0x12340000 0x4000>,意思是它在soc总线上的地址是0x12340000,大小是0x4000(16KB)。因为soc节点的#address-cells是1,#size-cells是1,所以每组就是一个地址加一个长度。
2. 64位地址的情况
如果是ARM64平台,地址是64位的,就要用两个cells来表示地址:
这里要注意地址的字节序。设备树中都是大端格式,高32位在前,低32位在后。所以<0x0 0x80000000>表示的地址就是0x0000000080000000,也就是2GB的位置。如果地址超过4GB,比如0x1_00000000,就写成<0x1 0x00000000>。
3. 地址映射的过程
地址映射就是把设备节点中的本地地址,通过各级总线的ranges属性,一层层翻译到CPU能直接访问的物理地址。这个过程是从子节点向根节点逐步推进的:
ranges属性是地址翻译的关键。它的格式是:子地址 + 父地址 + 长度。每一项描述一段地址空间的映射关系。如果ranges是空的(只有关键字没有值),表示子地址空间和父地址空间是完全一样的,1比1映射,不需要翻译。
举个有地址翻译的例子,比如PCIe总线:
4. 驱动中怎么使用reg地址
驱动代码中,通常用下面的步骤获取和使用设备树中的地址:
5. 几个容易出错的点
那你写设备树的时候,在reg相关的地方踩过什么坑吗?
有几个印象比较深的坑。第一个是#address-cells和#size-cells搞反了归属,刚开始总以为是当前节点的,其实是父节点的,决定子节点的reg格式。第二个是64位地址的高低位顺序,一开始把低32位写前面了,结果地址完全不对。第三个是ranges属性写了但格式不对,子地址单元数和父地址单元数搞混,翻译出来的地址就错了。
还有一个是reg的索引顺序,如果一个设备有好几段地址空间,reg里写的顺序就是驱动里用索引0、1、2获取的顺序,不能搞反,不然ioremap出来的地址对应的寄存器段不对。
四、面试官追问(5个)
可以。reg可以包含多组地址和长度,组和组之间是连续排列的。比如有两段地址空间就写reg =
可以。如果子节点描述的是一个单独的地址点而不是一段范围,#size-cells就设为0,这时候reg只有地址部分没有长度部分。比如中断控制器的CPU接口,只有一个地址没有长度概念,或者某些配置寄存器的单地址描述。
当子总线的地址空间和父总线不一样的时候需要ranges来做翻译。比如PCIe、USB等外部总线,总线内部的地址和CPU物理地址不是一个空间。如果子总线的地址空间和父总线完全一样(1比1映射),可以写一个空的ranges属性。如果节点不是总线节点,只是个普通设备,就不需要ranges。
思路很像。interrupts的格式也是由父中断控制器的#interrupt-cells决定的,也是父节点决定子节点的属性格式。这是设备树的一个设计思路:子节点描述自己的资源,资源的格式由父总线(或父控制器)来规定。
设备树编译成dtb后,节点和属性以扁平结构存储在内核指定的内存区域。内核启动时解析dtb,建立设备树节点树结构。驱动匹配设备后,通过of_系列函数读取属性值,按父节点的cells数来解析成地址和长度。of_address_to_resource函数内部会完成逐级地址翻译的工作。
五、失分表达
"#address-cells是当前节点的属性" —— 经典错误,这是父节点的属性,用来描述子节点reg的格式
"reg就是一个地址加一个长度" —— 太绝对,要看父节点的cells设置,可以是多个cells,也可以有多组
"设备树中的地址就是物理地址" —— 不一定,子节点的地址是相对于父总线的,需要经过翻译才是物理地址
"64位地址低32位在前" —— 设备树用大端格式,高32位在前,低32位在后
"ranges没用,有reg就行" —— 不理解地址翻译的概念,ranges是总线地址映射的关键
六、准备动作
亲手写几个设备树节点,练习reg的不同格式(32位、64位、多段地址)
理解设备树地址翻译的原理,画一个地址翻译的层级图帮助记忆
熟悉驱动中获取设备树地址的几种常用函数和区别
回顾自己项目中设备树相关的调试经历,总结几个踩过的坑
了解ePAPR规范中关于设备树寻址的基本约定
七、核心关键词
设备树DTS、reg属性、#address-cells、#size-cells、地址映射、ranges属性、地址翻译、物理地址、of_address_to_resource、of_iomap、ioremap、64位地址、总线地址空间

扫码关注




























