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​ 字节的连续内存供内核使用。
  • 历史版本差异: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 跳转至该地址(需转换为自身端模式)。

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
❯ ./hexdump_head.py Image

=== ARM64 内核头部解析 ===

[基本信息]
魔数(Magic): ARMd (0x644d5241)
代码段1(code0): 0xfa405a4d
代码段2(code1): 0x146f2827

[加载信息]
文本段偏移(text_offset): 0x0000000000000000
映像大小(image_size): 0x00000000029d0000 (43,843,584 bytes)

[标志位解析(flags: 0x000000000000000a)]
字节序: 小端
页大小: 4KB
物理位置: 任意位置

[保留字段]
res2: 0x0000000000000000
res3: 0x0000000000000000
res4: 0x0000000000000000
res5: 0x00000040 (PE COFF offset)

[内存布局建议]
- 内核应该被加载到2MB对齐的地址 + 0x0
- 需要预留至少 43,843,584 bytes的连续物理内存

上面的信息是arm64 elf启动模式下的头部信息,magic​ 字段防止非法映像加载,flags​ 确保内核与硬件环境(如页大小、字节序)匹配

由上图可知,6.6 arm64内核为4k页大小,offset为0,其启动内存布局如下:

1
2
3
4
5
6
7
8
9
10
Physical Memory Layout:
+------------------+
| 2MB aligned base |
+------------------+ text_offset (v3.17:0x80000 / after:0x0)
| Kernel Image |
| code0/code1 |
| ... |
+------------------+ image_size
| Available Memory |
+------------------+

二、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 服务器

设计目标:

  1. 兼容性:通过 code0/code1​ 保留传统启动路径,同时通过 res5​ 和 Stub 机制支持 EFI 现代启动标准。
  2. 最小化依赖:无论是否通过 EFI 启动,最终均通过 code0/code1​ 进入内核原生入口,避免重复实现启动逻辑。

安全性magic​ 字段防止非法映像加载,flags​ 确保内核与硬件环境(如页大小、字节序)匹配。

注意点

  1. code0通常是一条指令(b),直接跳转到primary_entry ,code1作为当前版本偏移量

  2. 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 内核启动问题(如加载地址错误、模式切换失败)的核心切入点。