c++如何读取Linux内核生成的Device Tree二进制流【深度】
C++如何读取Linux内核生成的Device Tree二进制流【深度】

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
Linux用户态如何解析内核加载的dtb文件
Linux内核在启动过程中会加载并解析dtb(设备树二进制)文件,将其转换为内部数据结构(如struct device_node)。一个关键限制是:**用户态程序无法直接访问内核内存中的这些数据**。因此,若要在用户空间获取设备树信息,必须采用替代方案。主要有两种途径:一是直接读取启动时传入的原始dtb二进制文件;二是利用内核提供的标准接口——/proc/device-tree伪文件系统。
常见的错误做法是尝试使用mmap映射/sys/firmware/devicetree/base或/proc/device-tree下的文件。这并不可行,因为这些是特殊的只读文件,没有传统意义上的文件长度(使用stat()查询时st_size通常返回0)。正确的方法是使用read()系统调用来读取内容。
/proc/device-tree是推荐方案:它以目录树的形式直观地呈现所有设备树节点和属性。你无需手动解析二进制格式,直接进行文件操作即可,且兼容性良好(主流内核版本>=3.10均支持)。- 若确实需要原始
dtb文件(例如用于签名验证或离线分析),则需确认启动环境中是否保留了该文件。它可能位于U-Boot环境变量fdt_addr_r指向的内存地址,也可能被打包在initramfs中(例如/dtb路径下)。 - 尽量避免依赖
/sys/firmware/devicetree/base:在某些ARM64平台上该路径可能不可见,且部分Linux发行版默认不会挂载它。
使用C++递归遍历/proc/device-tree提取节点信息
这是最轻量且最可靠的方法。其逻辑非常直观:/proc/device-tree下的每个子目录对应设备树中的一个节点(device node);而目录中的每个普通文件则对应一个属性(property),文件内容即为该属性的值(注意:内容可能包含'\0'字符,因此必须使用read(),而非fgets等字符串函数)。
实现时需注意以下几个要点:
立即学习“C++免费学习笔记(深入)”;
- 使用
opendir()和readdir()遍历目录,注意跳过“.”和“..”这两个特殊条目。 - 对每个条目调用
stat()判断其类型:如果是目录(S_ISDIR()),则递归进入处理子节点;如果是普通文件(S_ISREG()),则读取其内容作为属性值。 - 属性值的末尾不会自动附加'\0',其真实长度需通过
stat()获取的st_size来确定。对于字符串类属性(如compatible),其内容本身会以'\0'结尾;但对于二进制属性(如reg),它则是原始字节流。 - 设备树路径可能较深,为输出清晰,建议使用栈结构或递归来控制缩进显示,避免在代码中硬编码层级限制。
以下是一个简单的代码片段,演示如何读取/proc/device-tree/cpus/cpu@0/compatible属性:
int fd = open("/proc/device-tree/cpus/cpu@0/compatible", O_RDONLY);
struct stat st;
fstat(fd, &st);
std::vector buf(st.st_size);
read(fd, buf.data(), st.st_size);
// 此时,buf[0]...buf[st.st_size-1] 就是原始的属性值字节
close(fd);
使用libfdt解析原始dtb文件(需链接libfdt库)
当必须处理原始dtb二进制文件时(例如从Flash存储直接dump,或需与内核启动参数进行一致性校验),libfdt库便成为事实上的标准工具。它被dtc(设备树编译器)、u-boot等广泛使用,头文件为,所有函数均以fdt_为前缀。
典型的解析流程如下:
- 首先,使用
fdt_open_into()或fdt_load()等函数,将完整的dtb文件加载到内存中。需特别注意:提供的dtb数据必须是完整且未被截断的二进制流。 - 接着,可使用
fdt_first_subnode()和fdt_next_subnode()这对函数组合来遍历设备树的子节点。 - 读取属性则使用
fdt_getprop()函数。它返回一个指向dtb内部缓冲区的指针,**该指针绝对不可手动free,其生命周期完全依赖于fdtblob本身的有效性**。属性的实际长度会通过一个输出参数(lenp)返回。
使用libfdt时,有几个容易出错的地方:
- 在进行任何其他操作之前,必须先调用
fdt_check_header()来校验dtb头的合法性,否则非法dtb文件很可能导致程序段错误(Segmentation Fault)。 fdt结构体本身不管理内存,因此承载dtb数据的缓冲区在整个使用周期内都必须保持有效,不能是栈上的临时变量,也不能被realloc等操作移动。- 若在多线程环境下使用,需自行加锁,因为
libfdt本身并非线程安全,同一fdt实例不支持并发访问。
为何不推荐使用libdevicetree或自行编写解析器
或许有人会问,是否存在其他库,或者是否可以自行编写解析器?通常不建议这样做。
libdevicetree这类非主流库往往缺乏持续维护,API可能不稳定。而自行编写解析器风险极高。dtb的二进制格式包含魔数(magic number)、总长度、结构体偏移量、字符串表偏移量等多个字段,且存在版本差异(如v17/v18)、对齐要求(通常为8字节)以及复杂的字符串表引用机制。即便跳过严格校验,仅想正确识别节点和属性的边界,也极易发生数组越界读取错误。
在实际开发中,大约95%的需求通过/proc/device-tree接口即可完美解决。剩余的5%场景,如bootloader调试或固件签名校验,才是libfdt的用武之地。若非要绕过这两条成熟路径,很可能在自行实现的fdt_next_tag()循环中陷入困境,或读取到乱码数据。
归根结底,真正的挑战并非“如何读取数据”这一动作,而是明确所需数据究竟位于哪一层:是启动时传给内核的原始硬件描述,还是内核解析后生成的运行时视图,亦或是驱动实际看到的platform_device资源?这三者的语义截然不同,绝不能混为一谈。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
ThinkPHP如何使用ThinkOrm封装_ThinkOrm数据库封装方法【指南】
一、引入 ThinkOrm 独立包并初始化连接 如果你正在寻找一个轻量、独立且能兼容多种数据库的ORM方案,又不想为了它而引入整个ThinkPHP框架,那么ThinkOrm的封装方案正好能派上用场。它本质上是一个剥离出来的PDO抽象层,开箱即用。具体怎么操作呢?咱们一步步来看。 首先,ThinkOr
ThinkPHP怎样监控Session状态_Session会话状态监控【会话】
ThinkPHP会话状态监控:五种立即可用的实战方法 在ThinkPHP项目里,你是否遇到过这样的困惑:用户会话好像突然失效了,数据莫名其妙丢失,或者你根本不确定Session到底有没有正常启动?这背后,往往是Session中间件配置、存储驱动异常,或者客户端Cookie出了问题。别担心,下面这五种
ThinkPHP使用Redis缓存驱动连接失败_PHP扩展安装与连接池配置
根本原因是Redis扩展未启用或长连接配置不当:需确认phpinfo中Redis Support已启用、TP配置开启persistent=true并设prefix防污染,Swoole等常驻框架须改用连接池,且必须手动ping检测连接存活。 说到ThinkPHP项目里Redis连接失败,很多开发者第一
PHP 中 foreach 循环内正确使用 elseif 判断字符串值
PHP 中 foreach 循环内正确使用 elseif 判断字符串值 在 PHP 的 foreach 循环中,使用 if elseif 条件语句判断 JSON 字段的字符串值时,务必将字符串字面量用单引号或双引号包裹。否则,PHP 会将其解释为未定义的常量,从而引发 Notice 级别错误,并可能
C#怎么使用隐式类型var C#var和显式类型的区别什么时候该用var什么时候不该用【语法】
C 怎么使用隐式类型var C var和显式类型的区别什么时候该用var什么时候不该用【语法】 var是编译期语法糖,编译时推断类型生成等效IL,非动态类型;适用于类型冗长、LINQ、泛型初始化等场景,但工厂方法返回object、数值精度敏感、需明确接口语义时应显式声明类型。 var 是编译期语法糖
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

