Linux kernel 启动流程分析5---第一阶段dtb的验证
一、概述
1.1、kernel启动流程第一阶段简单说明
kernel 启动流程第一阶段
+-----------------------------------+
| 入口: stext |
+-----------------------------------+
|
v
+-----------------------------------+
| 设置为 SVC 模式, 关闭所有中断 |
+-----------------------------------+
|
v
+-----------------------------------+
| 获取 CPU ID, 提取相应的 proc_info |
+-----------------------------------+
|
v
+-----------------------------------+
| 验证 tags 或者 dtb |
+-----------------------------------+
|
v
+-----------------------------------+
| 创建页表项 |
+-----------------------------------+
|
v
+-----------------------------------+
| 配置 r13 寄存器, 设置跳转函数 |
+-----------------------------------+
|
v
+-----------------------------------+
| 使能 MMU (Memory Management Unit) |
+-----------------------------------+
|
v
+-----------------------------------+
| 跳转到 start_kernel (第二阶段) |
+-----------------------------------+
解释每一步:
-
入口:
stext
- 这是内核启动的第一个函数,ARM 架构的内核启动代码通常会在汇编文件中定义
ENTRY(stext)
作为入口点,CPU 会从此处开始执行内核的初始化代码。
- 这是内核启动的第一个函数,ARM 架构的内核启动代码通常会在汇编文件中定义
-
设置为 SVC 模式,关闭所有中断
- 在引导阶段,ARM 处理器默认是运行在 User 模式,但是为了执行特权操作,必须将处理器模式切换到 SVC 模式。同时,在此阶段关闭中断,避免在内核初始化期间中断干扰。
-
获取 CPU ID,提取相应的
proc_info
- 通过获取 CPU 的标识符(通常是通过某种机制如
CPUID
指令),可以从设备树或者其他硬件资源中获取到与当前 CPU 相关的配置信息(如架构、特性等)。这些信息将用于后续的处理器配置。
- 通过获取 CPU 的标识符(通常是通过某种机制如
-
验证 tags 或者 dtb
- Tags 或 DTB (Device Tree Blob) 是在引导过程中传递给内核的一些配置信息,内核需要验证这些信息的有效性。通过这些信息,内核可以识别硬件配置及设备信息。
-
创建页表项
- 在内核启动时,需要设置虚拟内存管理(MMU)。此时,内核创建并初始化页表,确保各个虚拟地址正确映射到物理内存。
-
配置 r13 寄存器,设置跳转函数
- 寄存器
r13
通常用于保存栈指针等关键信息。在此步骤中,内核会配置好栈指针,并设置跳转地址,以便在开启 MMU 后能够正确跳转到其他函数。
- 寄存器
-
使能 MMU (Memory Management Unit)
- 启用 MMU 以开启虚拟内存管理。通过 MMU,可以实现内存地址的映射,从而支持虚拟内存、进程隔离等功能。此时,通过设置相应的控制寄存器,开启 MMU。
-
跳转到
start_kernel
(第二阶段)- 在完成上述所有硬件初始化后,内核会跳转到
start_kernel
,这是内核的第二阶段,之后的任务会在此函数中完成,例如内核线程的创建、调度初始化等。
- 在完成上述所有硬件初始化后,内核会跳转到
1.2、疑问
主要带着以下几个问题去理解
dtb是什么?为什么要验证dtb的合法性?
如何验证dtb的合法性?
3、对应代码实现
__HEAD
ENTRY(stext)
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
bl __vet_atag
二、如何验证了一个dtb是否合法
2.1、原理说明
在生成dtb的时候会在头部上添加一个幻数magic,而验证dtb是否合法主要也就是看这个dtb的magic是否和预期的值一致。
主要是检查其头部的 magic number(幻数)。DTB 文件在编译时会包含一个特定的头部结构,这个结构包括了一个预定义的 magic number,它是用来标识 DTB 文件的有效性和正确性的。我们可以通过检查这个魔数来判断文件是否符合预期格式,是否可以作为合法的设备树文件。
1.验证原理说明
DTB 文件的头部通常包含以下结构:
- Magic Number(幻数):这是一个固定的标识符,用于验证 DTB 文件的有效性。
- 版本信息:通常包括版本号和设备树的大小等信息。
- 其他元数据:可能包括设备树文件的其它元信息,如配置、节点信息等。
DTB 文件的魔数是 0xD00DFeed
,这是一个标准的标识符,表示该文件是有效的设备树二进制文件。验证 DTB 合法性的一个最基本的检查步骤,就是验证文件头部的魔数是否和预期的一致。
2.验证步骤
验证一个 DTB 文件是否合法的过程大致可以分为以下几个步骤:
-
检查文件的开头:
- DTB 文件的前4个字节应该包含
0xD00DFeed
这个魔数(magic number)。 - 如果魔数不一致,说明该文件不是一个有效的设备树二进制文件。
- DTB 文件的前4个字节应该包含
-
使用工具检查魔数:
- 我们可以使用常见的十六进制查看工具(如
xxd
)或专门的二进制分析工具来检查 DTB 文件的开头是否包含正确的魔数。
- 我们可以使用常见的十六进制查看工具(如
3.具体操作步骤
以下是几种常见的验证方法:
使用 xxd
或 hexdump
查看文件的魔数
可以用 xxd
或 hexdump
等工具来查看 DTB 文件的前几个字节,确保它们是正确的魔数:
xxd input.dtb | head -n 1
输出应该类似于:
00000000: d00d feed 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........
在这个输出中,d00d feed
就是我们需要检查的魔数。如果文件头部的魔数不匹配 0xD00DFeed
,那么它就不是一个有效的 DTB 文件。
使用 dtc
命令反编译 DTB 文件
dtc
(Device Tree Compiler)是一个常用的工具,可以用来编译和反编译设备树文件。虽然 dtc
主要用于将 DTB 文件转换为可读的 DTS 文件,但它也会在解析过程中检查魔数,如果文件不合法,dtc
会报错。
dtc -I dtb -O dts -o output.dts input.dtb
如果文件没有问题,dtc
会成功生成一个对应的 output.dts
文件。如果 DTB 文件无效,它会在解析过程中输出类似下面的错误信息:
FATAL ERROR: Please fix your device tree source file!
或者,可能直接报告魔数不匹配的错误。
使用 fdtdump
(如果可用)
fdtdump
是一个用于分析 DTB 文件的工具,它将设备树的内容以可读的方式打印出来。如果魔数不正确,它会报错并提示文件格式无效。
fdtdump input.dtb
手动检查文件头部
你也可以通过 xxd
或 hexdump
等工具手动查看文件的前4个字节,检查它们是否与 0xD00DFeed
一致。
例如,使用 xxd
:
xxd input.dtb | head -n 1
输出的前四个字节应当是:
00000000: d00d feed 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........
如果这四个字节是 d00d feed
,则文件是有效的 DTB 文件。如果不是,则说明文件格式不正确。
2.2、dtb结构如下
DTB 文件通常按照以下顺序进行组织:
- DTB Header:设备树二进制文件的头部。
- Alignment Gap:对齐填充(如果需要的话)。
- Memory Reserve Map:内存保留映射区域。
- Alignment Gap:对齐填充(如果需要的话)。
- Device Tree Structure:设备树结构体(即节点信息等)。
- Alignment Gap:对齐填充(如果需要的话)。
- Device Tree String:设备树字符串(通常用于节点名和属性名等)。
各部分详细解释
1. DTB Header
DTB 文件的头部包含文件的基本信息。它是一个固定格式的结构,通常由以下字段组成:
- magic: 用于标识设备树文件的魔数,通常为
0xd00dfeed
。 - version: 设备树的版本号,通常是一个表示当前设备树格式版本的数字。
- total_size: 整个设备树的总大小,单位是字节。
- off_dt_struct: 设备树结构的偏移量,从文件头部开始,指向设备树结构体。
- off_dt_strings: 设备树字符串的偏移量,指向设备树中所有的字符串。
- off_mem_rsvmap: 内存保留映射的偏移量,指向内存保留区域。
- version_compat: 设备树的兼容版本信息。
设备树的头部主要用于描述文件的整体结构以及各个部分的位置和大小。
2. Alignment Gap
这部分是用来填充对齐的空白区域。由于硬件架构和内存访问的要求,设备树的不同部分可能需要按照特定的字节对齐方式进行排列。对齐填充确保各个部分按要求对齐。
3. Memory Reserve Map
内存保留映射部分是一个描述哪些内存区域被保留的部分。内存保留映射包含了多个内存保留区域,每个区域通常由起始地址和大小构成。这些内存区域在启动过程中不会被操作系统或设备树节点占用,它们可能用于内核自身的使用,或者保留给特定的硬件设备。
- 该部分的格式通常是:
- address: 保留区域的起始地址。
- size: 保留区域的大小。
4. Device Tree Structure
设备树结构部分包含了设备树的实际数据,即各种节点、属性和配置。这些数据通常以树形结构组织,每个节点代表一个硬件设备或资源。
每个节点包含以下信息:
- 节点名称:描述硬件设备的名称。
- 属性:该节点的相关属性,例如设备地址、中断信息、时钟等。
- 子节点:每个节点可能还有子节点,形成树形结构。
设备树的节点通常遵循以下格式:
- 节点名称:节点的名称,通常是一个字符串。
- 属性数量:该节点包含的属性数量。
- 属性内容:节点的各个属性,可能包括设备的配置、状态等。
5. Device Tree String
设备树字符串部分包含了设备树中的所有字符串数据。这些字符串数据用于节点名称、属性名称等地方。为了节省空间,设备树中的字符串通常是重复使用的,而不是每次都存储一份。
该部分是一个字符串池(string pool),包含了设备树中所有字符串的实际数据。节点和属性都使用偏移量来引用这个字符串池中的具体字符串。
DTB 结构总结
- DTB Header: 包含设备树文件的元数据。
- Alignment Gap: 确保数据对齐的空白区域。
- Memory Reserve Map: 描述内存保留区域的信息。
- Device Tree Structure: 设备树的节点及其属性,表示硬件设备和配置。
- Alignment Gap: 确保数据对齐的空白区域。
- Device Tree String: 存储设备树中所有字符串的区域。
内存布局示意图
+---------------------+------------------------+------------------------+-----------------------+
| DTB Header | Alignment Gap (optional) | Memory Reserve Map | Alignment Gap (optional)|
|---------------------|------------------------|------------------------|-----------------------|
| 0xd00dfeed | 4-byte padding | Reserve Area | 4-byte padding |
| Version Info | ... | Address | Size | ... |
| Size Info | | ... | |
| Offsets & Data | | | |
+---------------------+------------------------+------------------------+-----------------------+
| Device Tree Structure (Nodes, Properties, etc.) |
| (Tree Structure: Nodes -> Properties -> Child Nodes) |
+---------------------------------------------------------------------------------+
| Alignment Gap (optional) |
+---------------------------------------------------------------------------------+
| Device Tree String (String Pool) |
| (All Strings: Node Names, Property Names, etc.) |
+---------------------------------------------------------------------------------+
2.3、dtb header结构如下:
1.DTB Header 结构体
struct dtb_header {
uint32_t magic; // 魔数,用于标识设备树文件
uint32_t totalsize; // 整个设备树的总大小
uint32_t off_dt_struct; // 设备树结构体的偏移量
uint32_t off_dt_strings; // 字符串区的偏移量
uint32_t off_mem_rsvmap; // 内存保留区域的偏移量
uint32_t version; // 设备树的版本号
uint32_t version_compat; // 兼容版本号(在新版本中保持兼容性)
};
2.各字段详细说明:
-
magic (uint32_t magic)
- 这是一个用于标识设备树文件的魔数。其值通常为
0xd00dfeed
,用于确认文件是否是合法的设备树二进制文件。魔数的存在帮助解析器在读取文件时判断文件格式是否正确。
- 这是一个用于标识设备树文件的魔数。其值通常为
-
totalsize (uint32_t totalsize)
- 这个字段表示整个设备树二进制文件的总大小(字节数)。它包括了头部、设备树结构体部分、字符串部分以及内存保留部分的大小。解析器可以通过这个字段知道整个文件的大小。
-
off_dt_struct (uint32_t off_dt_struct)
- 这是一个偏移量,指向设备树结构体的开始位置。设备树结构体包含了硬件设备的信息,描述了设备节点、属性等内容。
-
off_dt_strings (uint32_t off_dt_strings)
- 这是一个偏移量,指向设备树字符串部分的开始位置。该区域包含了设备树中的所有字符串(如节点名称、属性名称等)。这些字符串通常是重复使用的,避免了多次存储相同的字符串。
-
off_mem_rsvmap (uint32_t off_mem_rsvmap)
- 这是一个偏移量,指向内存保留区域的开始位置。内存保留区域用于描述哪些内存区域是被保留的(例如,内核可能会保留某些内存段,用于后续操作)。这些内存区域在启动过程中不会被操作系统或设备树节点占用。
-
version (uint32_t version)
- 该字段表示设备树的版本号。设备树格式可能会随时间有所变化,因此使用版本号来标识不同的格式版本。通常,设备树的版本号是整数。
-
version_compat (uint32_t version_compat)
- 这个字段用于表示设备树的兼容版本号。设备树格式可能随着时间变化而不完全兼容,
version_compat
字段帮助实现向后兼容,保证旧版的设备树也能在新版本的内核中运行。
- 这个字段用于表示设备树的兼容版本号。设备树格式可能随着时间变化而不完全兼容,
3.DTB Header 示例:
假设设备树头部是这样的一段数据:
魔数 (magic): 0xd00dfeed
总大小 (totalsize): 0x000000f0 (240 bytes)
设备树结构体偏移 (off_dt_struct): 0x00000020 (32 bytes)
字符串偏移 (off_dt_strings): 0x00000150 (336 bytes)
内存保留区域偏移 (off_mem_rsvmap): 0x000000f4 (244 bytes)
版本 (version): 0x00000002 (版本 2)
兼容版本 (version_compat): 0x00000001 (兼容版本 1)
三、代码分析
具体就是分析__vet_atags的实现。
已经知道r2上存放的是dtb的地址指针,而代码中所要做的,就是要通过这个地址指针,获取前四个字节,去和dtb应有的幻数,也就是0xd00dfeed(大端)或者0xedfe0dd0(小端)进行比较。匹配的话,则说明这是一个合法的dtb。
arch/arm/kernel/head-common.S
__vet_atags:
tst r2, #0x3 @ aligned?保证dtb的地址是四字节对齐的
bne 1f
ldr r5, [r2, #0] @获取dtb的前四个字节,存放在r5寄存器中
#ifdef CONFIG_OF_FLATTREE
ldr r6, =OF_DT_MAGIC @ is it a DTB?,获取dtb的幻数,0xd00dfeed(大端)或者0xedfe0dd0(小端)
cmp r5, r6 @前四个字节和幻数进行对比
beq 2f @匹配,则说明是一个合法的dtb文件,跳到2
#endif
bne 1f @不匹配,跳到1
2: ret lr @ atag/dtb pointer is ok,直接返回,此时r2存放了dtb的地址
1: mov r2, #0@错误返回,此时,r2上是0
ret lr
ENDPROC(__vet_atags)
DTB的幻数,也就是OF_DT_MAGIC定义如下:
arch/arm/kernel/head-common.S
#ifdef CONFIG_CPU_BIG_ENDIAN
#define OF_DT_MAGIC 0xd00dfeed
#else
#define OF_DT_MAGIC 0xedfe0dd0 /* 0xd00dfeed in big-endian */
#endif
综上,验证dtb的工作完成。