AArch64 启动流程总结
AArch64 启动流程总结
引导装载程序硬件状态要求
一、前置要求
引导装载程序需完成以下关键操作,以确保 Linux 内核在 AArch64 架构下正确启动:
- 设置和初始化 RAM
- 设置设备树数据
- 解压内核映像
- 调用内核映像
(一)RAM 初始化(强制)
- 目标:定位并初始化系统中所有供内核使用的 RAM,确保内核能够存储系统变量和数据。
- 实现方式:依赖设备特性,可通过自动算法检测 RAM、使用设备已知的 RAM 信息或其他定制方法。
(二)设备树数据准备(强制)
格式要求:设备树数据块(dtb)需满足以下条件:
- 8 字节对齐,大小不超过 2MB。
- 避免放置在需特定属性映射的 2MB 区域内(因内核以 2MB 粒度映射 dtb)。
历史版本差异:v4.2 之前的内核要求 dtb 位于内核映像下方 text_offset 字节处开始的首个 512MB 内存区域内。
(三)内核映像解压(可选)
- 依赖条件:AArch64 内核无自解压代码,若使用压缩内核(如 Image.gz),需由引导装载程序通过 gzip 等工具解压;否则直接使用非压缩映像。
(四)调用内核映像(强制)
内核映像头结构:内核映像包含 64 字节的小端模式头(v3.17 起统一为小端),关键字段如下:
-
code0/code1
:用于跳转到内核入口地址stext
,EFI 启动时先跳过这两个字段,由 EFI stub 处理后跳转。 -
text_offset
:映像装载偏移量,当image_size
为零时,默认值为 0x80000(v3.17 前)。 -
image_size
:映像实际大小,为零时引导装载程序需在内核末尾后保留尽可能多的空闲内存。 -
flags
:64 位标志位,包含字节序(位 0)、页大小(位 1-2)、物理位置(位 3)等信息。
-
内存布局要求:
- 内核映像需放置在 2MB 对齐的物理基址 +
text_offset
处,基址与映像起始地址之间的区域可自由使用。 - 至少预留
image_size
字节的连续内存供内核使用。
- 内核映像需放置在 2MB 对齐的物理基址 +
历史版本差异:v4.6 之前的内核无法使用映像物理偏移以下的内存,建议将映像靠近内存起始处。
二、内核启动前的系统状态要求
(一)CPU 寄存器与模式
主 CPU 寄存器:
-
x0
:设备树数据块(dtb)的物理地址。 -
x1-x3
:保留(置 0)。
-
CPU 模式:
- 所有中断需通过 PSTATE.DAIF 屏蔽(Debug、SError、IRQ、FIQ)。
- 处于 EL2(推荐,支持虚拟化扩展)或非安全 EL1 模式,所有 CPU 需以相同异常级进入内核。
(二)MMU 与缓存
MMU:必须关闭。
缓存:
- 指令缓存可开启或关闭。
- 内核映像所在内存区需清理至缓存一致性点(PoC),确保缓存一致性。
- 依赖虚拟地址维护的系统缓存可启用,非虚拟地址维护的缓存(不推荐)需禁用。
(三)架构计时器
- CNTFRQ:设置为计时器实际频率。
- CNTVOFF:所有 CPU 需设置一致的值。
- EL1 模式要求:若内核在 EL1 启动,需置位 CNTHCTL_EL2 中的 EL1PCTEN(位 0)。
(四)一致性与系统寄存器
一致性:所有 CPU 需处于同一一致性域,确保内存操作的一致性。
系统寄存器:
异常级内可写的系统寄存器需在更高异常级初始化,避免未知状态。
GICv3 控制器配置:
- 若存在 EL3:ICC_SRE_EL3.Enable(位 3)和 SRE(位 0)均置 1。
- 若内核运行在 EL1:ICC_SRE_EL2.Enable 和 SRE 均置 1(v3 模式)或置 0(兼容 v2 模式)。
设备树或 ACPI 需正确描述 GIC 版本(v2 或 v3)。
三、多 CPU 启动机制
(一)主 CPU 启动
- 直接跳转到内核映像的第一条指令,设备树中每个 CPU 节点需包含
enable-method
属性,支持以下两种方式:
(二)辅助 CPU 启动
1. spin-table 方式
条件:CPU 节点需包含
cpu-release-addr
属性,指向 64 位对齐的零初始化内存位置。流程:
- 辅助 CPU 在内核保留区(通过
/memreserve/
声明)轮询cpu-release-addr
,通过wfe
指令降低功耗。 - 主 CPU 通过
sev
指令唤醒辅助 CPU,当cpu-release-addr
非零时,辅助 CPU 跳转至该地址(需转换为自身端模式)。
- 辅助 CPU 在内核保留区(通过
2. psci 方式
- 条件:设备树需包含
psci
节点(遵循Documentation/devicetree/bindings/arm/psci.yaml
)。 - 流程:辅助 CPU 位于内核空间外,内核通过 PSCI(ARM 电源状态协调接口)的 CPU_ON 调用激活辅助 CPU。
(三)辅助 CPU 寄存器设置
-
x0-x3
:均置 0,保留供未来使用。
四、关键注意事项
- 版本兼容性:不同内核版本(如 v3.17、v4.2、v4.6)在内存布局、字节序、设备树要求上存在差异,需根据具体版本适配。
- 调试建议:启动前停止所有 DMA 设备,避免内存数据被破坏;参考 ARMv8 架构手册(如 DDI 0487A)理解缓存一致性和系统寄存器配置。
AArch64 内核映像头部信息详解
AArch64 内核映像头部为 64 字节固定格式,用于引导装载程序与内核的交互,核心字段如下(基于 v3.17+ 版本,小端模式):
一、字段解析
1. 基础字段
字段 | 类型 | 描述 |
---|---|---|
code0 |
u32 | 可执行代码,负责跳转到内核入口 stext (非 EFI 启动时直接执行)。 |
code1 |
u32 | 可执行代码,辅助 code0 完成跳转逻辑(通常为续接指令)。 |
text_offset |
u64 | 内核映像装载偏移量(相对于 2MB 对齐基址),用于计算内核实际加载地址。 |
image_size |
u64 | 内核映像实际大小(字节),为零时需保留末尾空闲内存供内核使用。 |
flags |
u64 | 标志位(关键位说明): - 位 0:字节序(0=小端,1=大端) - 位 1-2:内核页大小(0=未指定,1=4K,2=16K,3=64K) - 位 3:物理位置(0=靠近内存起始处,1=任意位置) |
magic |
u32 | 魔数 0x644d5241 (小端,对应 ASCII “ARM\x64”),用于验证映像有效性。 |
res5 |
u32 | 保留字段,EFI 启动时表示 PE 文件头偏移(指向 efi_stub_entry 入口)。 |
2. 实例参考
我们可以通过以下此脚本^1解析elf文件头信息:
解析arm64编译的头部信息如下:
1 | ❯ ./hexdump_head.py Image |
上面的信息是arm64 elf启动模式下的头部信息,magic
字段防止非法映像加载,flags
确保内核与硬件环境(如页大小、字节序)匹配
由上图可知,6.6 arm64内核为4k页大小,offset为0,其启动内存布局如下:
1 | Physical Memory Layout: |
二、code0/code1基本功能
code0和code1作为32位指令,其基本功能如下:
在非EFI启动内核作为指令直接执行
1
code0 (b指令) -> [head.S]primary_entry -> stext
在EFI启动内核模式下作为备用入口
1
EFI stub -> efi_stub_entry -> code0 -> primary_entry -> stext
场景 | 引导方式 | code0/code1 执行时机 |
关键依赖字段 | 典型使用场景 |
---|---|---|---|---|
非 ELF 启动 | 传统引导(U-Boot) | 直接跳转执行映像起始地址 | text_offset 、magic |
嵌入式设备、非 EFI 服务器 |
ELF 启动(EFI) | EFI Stub 机制 | Stub 代码执行后显式跳转至 code0 |
res5 (PE 头偏移) |
x86_64/AArch64 服务器 |
设计目标:
- 兼容性:通过
code0/code1
保留传统启动路径,同时通过res5
和 Stub 机制支持 EFI 现代启动标准。 - 最小化依赖:无论是否通过 EFI 启动,最终均通过
code0/code1
进入内核原生入口,避免重复实现启动逻辑。
安全性:magic
字段防止非法映像加载,flags
确保内核与硬件环境(如页大小、字节序)匹配。
注意点
code0通常是一条指令(b),直接跳转到primary_entry ,code1作为当前版本偏移量
EFI启动这些指令最初被跳过,最后还是被执行
graph TD A[Image头部] --> B{启动类型} B -->|普通启动| C[code0执行] B -->|EFI启动| D[EFI stub] D --> E[efi_stub_entry] E --> C C --> F[primary_entry] F --> G[stext]
五、总结
内核映像头部是连接引导装载程序与内核的关键桥梁:
- 非 EFI 启动:
code0/code1
作为直接入口,简化启动流程,适用于传统嵌入式或定制化硬件。 - EFI 启动:通过 Stub 代码适配 EFI 规范,最终仍依赖
code0/code1
进入内核,实现兼容性与标准化。
理解头部字段(尤其是code0/code1
和flags
)的作用,是调试 AArch64 内核启动问题(如加载地址错误、模式切换失败)的核心切入点。