Linux 内核:设备树(1)dtb格式

背景

dtb作为二进制文件被加载到内存中,然后由内核读取并进行解析,如果对dtb文件的格式不了解,那么在看设备树解析相关的内核代码时将会寸步难行,而阅读源代码才是了解设备树最好的方式。

所以,如果需要更透彻的了解设备树解析的细节,第一步就是需要了解设备树的格式。

注:本文部分参考:官方文档

dtb的由来

设备树的一般操作方式是:开发人员根据开发需求编写dts文件,然后使用dtc将dts编译成dtb文件。

dts文件是文本格式的文件,而dtb是二进制文件,在linux启动时被加载到内存中,接下来我们需要来分析设备树dtb文件的格式。

dtb格式总览

dtb的格式是这样的:

dtb header

但凡涉及到数据的记录,就一定会有一个总的描述部分,就像磁盘的超级块,书的目录,dtb当然也不例外,这个描述头部就是dtb的header部分,通过这个header部分,用户可以快速地了解到整个dtb的大致信息。

header可以用这么一个结构体来描述:

struct fdt_header {
fdt32_t magic; /* magic word FDT_MAGIC */
fdt32_t totalsize; /* total size of DT block */
fdt32_t off_dt_struct; /* offset to structure */
fdt32_t off_dt_strings; /* offset to strings */
fdt32_t off_mem_rsvmap; /* offset to memory reserve map */
fdt32_t version; /* format version */
fdt32_t last_comp_version; /* last compatible version */ /* version 2 fields below */
fdt32_t boot_cpuid_phys; /* Which physical CPU id we're
booting on */
/* version 3 fields below */
fdt32_t size_dt_strings; /* size of the strings block */ /* version 17 fields below */
fdt32_t size_dt_struct; /* size of the structure block */
};

magic

设备树的魔数,魔数其实就是一个用于识别的数字,表示设备树的开始,linux dtb的魔数为 0xd00dfeed.

totalsize

这个设备树的size,也可以理解为所占用的实际内存空间。

off_dt_struct

offset to dt_struct,表示整个dtb中structure部分所在内存相对头部的偏移地址

off_dt_strings

offset to dt_string,表示整个dtb中string部分所在内存相对头部的偏移地址

off_mem_rsvmap

offset to memory reserve map,dtb中memory reserve map所在内存相对头部的偏移地址,

version

设备树的版本,截至目前的最新版本为17.

last_comp_version

最新的兼容版本

boot_cpuid_phys

这部分仅在版本2中存在,后续版本不再使用。

size_dt_strings

表示整个dtb中string部分的大小

size_dt_struct

表示整个dtb中struct部分的大小

alignment gap

中间的alignment gap部分表示对齐间隙,它并非是必须的,它是否被提供以及大小由具体的平台对数据对齐和的要求以及数据是否已经对齐来决定。

memory reserve map

memory reserve map:描述保留的内存部分,这个map的数据结构是这样的:

{
uint64_t physical_address;
uint64_t size;
}

这部分存储了此结构的列表,整个部分的结尾由一个数据为0的结构来表示(即physical_address和size都为0,总共16字节)。

这一部分的数据并非是节点中的memory子节点,而是在设备开始之前(也就是第一个花括号之前)定义的,例如:

/dts-v1/
/memreserve/ 0x10000000 0x100000
/*在结构提中的表示为 physical_address=0x10000000,size=0x100000 */
{
...
}

这一部分的作用是告诉内核哪一些内存空间需要被保留而不应该被系统覆盖使用,因为在内核启动时常常需要动态申请大量的内存空间,只有提前进行注册,用户需要使用的内存才不会被系统征用而造成数据覆盖。

值得一提的是,对于设备树而言,即使不指定保留内存,系统也会默认为设备树保留相应的内存空间。

同时,这一部分需要64位(8字节)对齐。

device-tree structure

device-tree structure:每个节点都会被描述为一个struct,节点之间可以嵌套,因此也会有嵌套的struct。

structure的的结构是这样的:

  • 一个node开始信号,OF_DT_BEGIN_NODE,内容为:0x00000001
  • 对于版本1-3而言,这一部分是节点的全路径,以/开头,而对于版本16及以上,这部分只是unit name(root 除外,它没有unit name),unit name是以0结尾的字符串
  • 可选的对齐字节
  • 对于每个属性字段:
    • 由OF_DT_PROP标识,数据为0x00000003
    • 32位的数据,表示属性的size
    • 32位的数据,表示属性名在string block中的偏移地址
    • 属性中的value data.
  • 如果有子节点,递归地对子节点进行描述。
  • 节点结束信号,OF_DT_END_NODE ,数据为0x00000002.

    每个节点的信息都按照上述结构被描述,需要注意的是,所有用于描述一个特定节点的属性都必须在任何子节点之前定义,虽然设备树的层次结构不会因此产生二义性,但是linux kernel的解析程序要求这么做。

device-tree strings

device-tree strings:在dtb中有大量的重复字符串,比如"model","compatile"等等,为了节省空间,将这些字符串统一放在某个地址,需要使用的时候直接使用索引来查看。

需要注意的是,属性部分格式为key = value,key部分被放置在strings部分,而value部分的字符串并不会放在这一部分,而是直接放在structure中。

dtb文件解析示例

光说不练假把式,下面我就使用一个简单的示例来剖析dtb的文件格式。

下述示例仅仅是一个演示demo,不针对任何平台,为了演示方便,编写了一个非常简单的dts文件。

/dts-v1/;
/ {
compatible = "hd,test_dts", "hd,test_xxx";
#address-cells = <0x1>;
#size-cells = <0x1>;
model = "HD test dts"; chosen {
stdout-path = "/ocp/serial@ffff";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
led1:led@2000000 {
compatible = "test_led";
#address-cells = <0x1>;
#size-cells = <0x1>;
reg = <0x200 0x4>;
};
};

编译当前dts文件,获取对应的dtb文件。

鉴于dtb文件为二进制文件,普通编辑器打开显示乱码,我们使用ultraEdit查看,它将数据以16进制形式显示:

整个dtb文件还是比较简单的,图中的红色框出的部分为header部分的数据,可以看到:

第1个四字节对应magic,数据为 D00DFEED.
第2四字节对应totalsize,数据为 000001BC,可以由整张图片看出,这个dtb文件的大小由0x0~0x1bb,大小刚好是0x1bc
第3个四字节对应off_dt_struct,数据为00000038。
第4个四字节对应off_dt_strings,数据为00000174,可以由整张图片看到,从0x174开始刚好是字符串开始的地方
第5个四字节对应off_mem_rsvmap,数据为00000028
第6个四字节对应version,数据为00000011,十进制为17
第7个四字节对应last_comp_version,数据为00000010,十进制为16,表示兼容版本16
第8个四字节对应boot_cpuid_phys,数据为00000000,仅在版本2中使用,这里为0
第9个四字节对应size_dt_strings,数据为00000048,表示字符串总长。
第10个四字节对应size_dt_struct,数据为0000013c,表示struct部分总长度。

整个头部为40字节,16进制为0x28,从头部信息中off_mem_rsvmap部分可以得到,reserve memory起始地址为0x28,上文中提到,这一部分使用一个16字节的struct来描述,以一个全为0的struct结尾。

后16字节全为0,可以看出,这里并没有设置reserve memory。

structure 部分

上文回顾:每一个属性都是以 key = value的形式来描述,value部分可选。

偏移地址来到0x00000038(0x28+0x10),接下来8个字节为00000003,根据上述structure中的描述,这是OF_DT_PROP,即标示属性的开始。

接下来4字节为00000018,表明该属性的value部分size为24字节。

接下来4字节是当前属性的key在string 部分的偏移地址,这里是00000000,由头部信息中off_dt_strings可以得到,string部分的开始为00000174,偏移地址为0,所以对应字符串为"compatible".

之后就是value部分,这部分的数据是字符串,可以直接从图片右侧栏看出,总共24字节的字符串"hd,test_dts", "hd,test_xxx",因为字符串之间以0结尾,所以程序可以识别出这是两个字符串。

可以看出,到这里,compatible = "hd,test_dts", "hd,test_xxx";这个属性就被描述完了,对于属性的描述还是非常简单的。

按照固有的规律,接下来就是对#address-cells = <0x1>的解析,然后是#size-cells = <0x1>...

然后就是递归的子节点chosen,memory@80000000等等都是按照上文中提到的structure解析规则来进行解析,最后以00000002结尾。

与根节点不同的是,子节点有一个unit name,即chosen,memory@80000000这些名称,并非节点中的.name属性。

而整个结构的结束由00000009来描述。

一般而言,在32位系统中,dtc在编译dts文件时会自动考虑对齐问题,所以对于设备树的对齐字节,我们只需要有所了解即可,并不会常接触到。

好了,关于linux设备树dtb文件格式的讨论就到此为止啦,如果朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言

关于linux设备树在内核启动时的解析可以参考我的另外两篇博客:

linux设备树的解析--dtb转换成device_node

linux设备树的解析--device_node转换成platform_deviec

原创博客,转载请注明出处!

祝各位早日实现项目丛中过,bug不沾身.

Linux 内核:设备树(1)dtb格式的更多相关文章

  1. linux设备驱动程序-设备树(0)-dtb格式

    linux设备树dtb格式 设备树的一般操作方式是:开发人员根据开发需求编写dts文件,然后使用dtc将dts编译成dtb文件. dts文件是文本格式的文件,而dtb是二进制文件,在linux启动时被 ...

  2. Linux内核 设备树操作常用API【转】

    转自:https://www.linuxidc.com/Linux/2017-02/140818.htm 一文中介绍了设备树的语法,这里主要介绍内核中提供的操作设备树的API,这些API通常都在&qu ...

  3. Linux内核 设备树操作常用API

    Linux设备树语法详解一文中介绍了设备树的语法,这里主要介绍内核中提供的操作设备树的API,这些API通常都在"include/of.h"中声明. device_node 内核中 ...

  4. linux设备驱动程序-设备树(1)-dtb转换成device_node

    linux设备驱动程序-设备树(1)-dtb转换成device_node 本设备树解析基于arm平台 从start_kernel开始 linux最底层的初始化部分在HEAD.s中,这是汇编代码,我们暂 ...

  5. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  6. Linux dts 设备树详解(一) 基础知识

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...

  7. Linux 获取设备树源文件(DTS)里描述的资源

    Linux 获取设备树源文件(DTS)里的资源 韩大卫@吉林师范大学 在linux使用platform_driver_register() 注册 platform_driver 时, 需要在 plat ...

  8. (转)Linux内核基数树应用分析

    Linux内核基数树应用分析 ——lvyilong316 基数树(Radix tree)可看做是以二进制位串为关键字的trie树,是一种多叉树结构,同时又类似多层索引表,每个中间节点包含指向多个节点的 ...

  9. Linux 获取设备树源文件(DTS)里的资源【转】

    本文转载自:http://blog.csdn.net/keleming1/article/details/51036000 http://www.cnblogs.com/dyllove98/archi ...

  10. Linux 获取设备树源文件(DTS)里描述的资源【转】

    转自:http://www.linuxidc.com/Linux/2013-07/86839.htm 转自:http://blog.sina.com.cn/s/blog_636a55070101mce ...

随机推荐

  1. 电路笔记03—kcl、kvl,独立源,受控源

    电路笔记03-kcl.kvl,独立源,受控源 听起来简单,做起来需要思考.所以做作业,思考很有 必要.电路的功率守恒,4种受控源,用两类约束列方程.电路分析力最难的一部分,怎么把一个量用其它量表示,后 ...

  2. Linux上OcenBase单机版部署及基本信息查询

    OceanBase单机版部署可以通过在线和离线两种方式部署.在线部署可以通过yum源或者apt源部署,直接拉取官方源码即可.实际使用中,大部分环境连不了外网,本文介绍离线方式安装. 下载"O ...

  3. Golang 爬虫01

    目录 学习地址: 目录站: 爬虫概念: 工作流程: 百度贴吧爬虫实现: go实战代码 单进程 实现过程: 并发爬取 实现过程: 学习地址: https://www.bilibili.com/video ...

  4. CSS自适应网页(CSS第一篇)

    ​CSS的属性: 用浏览器自带的审查元素对一些页面进行调整,快捷键是F12. 网页允许宽度自适应: 在代码的头部加入一行viewport元标签. <meta name="viewpor ...

  5. Oracle数据库下的DDL、DML、DQL、TCL、DCL

    首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1 ...

  6. 从油猴脚本管理器的角度审视Chrome扩展

    从油猴脚本管理器的角度审视Chrome扩展 在之前一段时间,我需要借助Chrome扩展来完成一个需求,当时还在使用油猴脚本与浏览器扩展之间调研了一波,而此时恰好我又有一些做的还可以的油猴脚本 TKSc ...

  7. 用pageOffice控件实现 office word文档 强制留痕编辑Word

    OA办公中,业务需要多人编辑word文档,需要强制留痕功能,用来查看文档编辑过程中的具体修改痕迹. 怎么实现word文档的强制留痕呢? 1 实现方法 通过pageOffice实现简单的在线打开编辑wo ...

  8. IceRPC之调度管道->快乐的RPC

    作者引言 很高兴啊,我们来到了IceRPC之调度管道->快乐的RPC, 基础引导,有点小压力,打好基础,才能让自已不在迷茫,快乐的畅游世界. 调度管道 Dispatch pipeline 了解如 ...

  9. Vue cli之组件的嵌套

    前面显示Home.vue页面组件的内容时,我们是在App.vue通过import导入使用的.这个过程就是组件的嵌套使用.那么我们除了App.vue可以导入其他页面以外,也可以通过在Home.vue中导 ...

  10. docker创建容器数据持久化资源限制基础命令

    1. docker简介和核心概念 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化.容器是完全使 ...