Linux内核分析之基础知识-4

韩乔落

第4章:ARM64 启动流程

从 EL3 到 Linux 内核的旅程


本章概述

ARM64 (AArch64) 的启动流程与 x86_64 有显著差异。本章详细讲解 ARM64 特有的启动机制,包括异常级别、设备树和 ATF。

ARM64 启动方式对比

启动方式 引导程序 典型设备 参数传递
UEFI UEFI 固件 → GRUB 服务器、工控 设备树/ACPI
ATF ATF → U-Boot 嵌入式 设备树
直接引导 U-Boot/RPi Boot 树莓派等 设备树
kexec 运行中内核 热重启 自定义

4.1 ARM64 架构基础

4.1.1 异常级别 (Exception Levels)

ARM64 定义了 4 个异常级别 (EL),从 EL0 到 EL3:

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
29
30
31
32
33
34
┌─────────────────────────────────────────────────────────────┐
│ ARM64 异常级别 │
├─────────────────────────────────────────────────────────────┤
│ │
│ EL3 (最高特权) ───────────────────────────── │
│ │ │
│ │ 功能: │
│ │ - 安全监控器 (Secure Monitor) │
│ │ - 安全世界和非安全世界切换 │
│ │ - 由 ARM Trusted Firmware 实现 │
│ │ │
│ EL2 (虚拟化) ────────────────────────────── │
│ │ │
│ │ 功能: │
│ │ - 虚拟机监控器 (Hypervisor) │
│ │ - Stage 2 页表 (虚拟化) │
│ │ - 类似 x86 的 VMX/SVM │
│ │ │
│ EL1 (操作系统内核) ──────────────────────── │
│ │ │
│ │ 功能: │
│ │ - 操作系统内核运行 │
│ │ - Stage 1 页表 │
│ │ - 特权操作 │
│ │ │
│ EL0 (应用程序) ──────────────────────────── │
│ │
│ 功能: │
│ - 用户空间应用程序运行 │
│ - 受限访问资源 │
│ │
└─────────────────────────────────────────────────────────────┘

权限: EL3 > EL2 > EL1 > EL0

4.1.2 CPU 模式和寄存器

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
29
30
/* CurrentEL 寄存器值 */
#define CurrentEL_EL1 0x04 // 在 EL1
#define CurrentEL_EL2 0x08 // 在 EL2
#define CurrentEL_EL3 0x0C // 在 EL3

/* ARM64 启动时可能处于的异常级别 */
enum arm64_boot_el {
BOOT_CPU_MODE_EL1 = (1 << 0), // 从 EL1 启动
BOOT_CPU_MODE_EL2 = (1 << 1), // 从 EL2 启动
};

/* 读取当前异常级别 */
static inline u64 read_current_el(void)
{
u64 el;
asm volatile("mrs %0, CurrentEL" : "=r"(el));
return el;
}

/* 切换到 EL1 */
static inline void drop_to_el1(void)
{
asm volatile(
"mov x0, #(1 << 3)\n" // EL1
"msr SPSEL, x0\n" // 选择 SP_EL1
"isb\n"
"msr ELR_EL2, x2\n" // 设置返回地址
"eret\n" // 退出到 EL1
);
}

4.1.3 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
29
30
31
ARM64 VMSA (虚拟内存系统架构):

┌─────────────────────────────────────────────────────────────┐
│ 虚拟地址空间 │
├─────────────────────────────────────────────────────────────┤
│ │
│ TTBR0_EL1 (用户空间): │
│ 0x0000_0000_0000_0000 ───────────────────────── │
│ 到 │
│ 0x0000_ffff_ffff_ffff (128 TB, 48位) │
│ 或 │
│ 0x0000_7fff_ffff_ffff (256 TB, 49位) │
│ 或 │
│ 0x0003_ffff_ffff_ffff (512 TB, 50位) │
│ │
│ TTBR1_EL1 (内核空间): │
│ 0xffff_0000_0000_0000 ───────────────────────── │
│ 到 │
│ 0xffff_ffff_ffff_ffff │
│ │
│ 内核空间布局 (48位 VA): │
│ 0xffff_0000_0000_0000 - 0xffff_ffff_ffff_ffff │
│ │
│ 包括: │
│ - 直接映射物理内存 │
│ - vmalloc/ioremap 空间 │
│ - vmemmap (struct page 映射) │
│ - PCI/IO 空间 │
│ - 内核代码/数据 │
│ │
└─────────────────────────────────────────────────────────────┘

4.2 ARM64 启动规范

4.2.1 Boot Protocol 要求

Linux 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
29
/* Documentation/arm64/booting.txt */

/*
* 启动状态要求:
*
* 1. CPU 必须处于以下状态之一:
* - EL2 (带虚拟化)
* - EL1 (不带虚拟化)
*
* 2. MMU 必须关闭
*
* 3. 数据缓存 (D-cache) 必须关闭
*
* 4. 指令缓存 (I-cache) 可以开启或关闭
*
* 5. 中断必须屏蔽
*
* 6. SMP 系统中只有主 CPU 运行
*
* 7. 寄存器状态:
* x0 = 设备树 blob 的物理地址 (DTB)
* x1 = 0 (保留)
* x2 = 0 (保留)
* x3 = 0 (保留)
*
* 8. 系统控制寄存器:
* - SCTLR_ELx.EE = 0 (小端序)
* - 栈指针必须设置
*/

4.2.2 设备树传递

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* 设备树 blob (DTB) 格式 */
struct fdt_header {
uint32_t magic; /* 0xd00dfeed */
uint32_t totalsize;
uint32_t off_dt_struct; /* 设备树结构偏移 */
uint32_t off_dt_strings; /* 字符串块偏移 */
uint32_t off_mem_rsvmap; /* 内存保留映射偏移 */
uint32_t version;
uint32_t last_comp_version;
uint32_t boot_cpuid_phys; /* 启动 CPU 的物理 ID */
uint32_t size_dt_strings; /* 字符串块大小 */
uint32_t size_dt_struct; /* 设备树结构大小 */
};

/* 设备树节点示例 */
/ {
model = "ARM Foundation Platform v8";
compatible = "arm,foundation-aarch64-v8";
#address-cells = <2>;
#size-cells = <2>;

chosen {
bootargs = "console=ttyAMA0 root=/dev/vda rw";
};

cpus {
#address-cells = <2>;
#size-cells = <0>;

cpu@0 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x0>;
enable-method = "spin-table";
cpu-release-addr = <0x0 0x0000fff8>;
};
};

memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x80000000>;
};

uart {
compatible = "arm,pl011";
reg = <0x0 0x09000000 0x0 0x1000>;
interrupts = <0 1 4>;
clock-frequency = <24000000>;
current-speed = <115200>;
};
};

4.3 内核入口:primary_entry

4.3.1 内核镜像头部

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
29
/* arch/arm64/kernel/head.S */

.text
.head

/*
* 内核头部 - 必须符合引导加载程序的要求
* DO NOT MODIFY - 这是引导加载程序预期的格式
*/
__HEAD
/*
* 第一条指令必须是跳转到内核入口
*/
b primary_entry

/*
* 魔数和版本信息
*/
.quad 0 /* 加载偏移 */
le64sym _kernel_size_le /* 内核镜像大小 (小端) */
le64sym _kernel_flags_le /* 内核标志 */
.quad 0 /* 保留 */
.quad 0 /* 保留 */
.quad 0 /* 保留 */
.ascii ARM64_IMAGE_MAGIC /* "ARM\x64" */
.long .Lpe_header_offset /* PE 头偏移 */

/* EFI PE 头 */
__EFI_PE_HEADER

4.3.2 primary_entry 完整流程

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/* arch/arm64/kernel/head.S */

/*
* 内核启动入口点
*
* 寄存器约定:
* x19 - MMU 启动状态
* x20 - CPU 启动模式
* x21 - FDT 指针 (从 x0 复制)
*/
SYM_CODE_START(primary_entry)
bl record_mmu_state /* 记录 MMU 状态到 x19 */
bl preserve_boot_args /* 保存启动参数 (x0-x3) */

/* 设置早期栈 */
adrp x1, early_init_stack
mov sp, x1
mov x29, xzr /* 清零帧指针 */

/*
* 创建初始身份映射页表
* 这个页表将物理地址映射到相同的虚拟地址
*/
adrp x0, init_idmap_pg_dir
mov x1, xzr
bl __pi_create_init_idmap

/*
* 根据缓存状态进行缓存操作
*/
cbnz x19, 0f /* 如果 MMU 已开启 */
dmb sy /* 内存屏障 */
/* 缓存无效化代码... */
b 1f
0:
/* 缓存清理代码... */
1:
/*
* 初始化异常级别
* 如果在 EL2,配置后降到 EL1
*/
mov x0, x19 /* 传递 MMU 状态 */
bl init_kernel_el
mov x20, x0 /* 保存 CPU 启动模式 */

/*
* CPU 初始化
* 设置系统控制寄存器等
*/
bl __cpu_setup

/*
* 跳转到主切换代码
* 启用 MMU 并切换到虚拟地址
*/
b __primary_switch
SYM_CODE_END(primary_entry)

4.4 MMU 初始化

4.4.1 record_mmu_state

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* 记录当前 MMU 状态 */
SYM_CODE_START_LOCAL(record_mmu_state)
mrs x19, CurrentEL /* 读取当前异常级别 */
cmp x19, #CurrentEL_EL2
b.ne 0f

/* 在 EL2,读取 SCTLR_EL2 */
mrs x19, sctlr_el2
b 2f
0:
/* 在 EL1,读取 SCTLR_EL1 */
mrs x19, sctlr_el1
2:
/*
* 处理字节序
* 如果字节序不正确,切换并禁用 MMU
*/
CPU_LE( tbnz x19, #SCTLR_ELx_EE_SHIFT, 1f )
CPU_BE( tbz x19, #SCTLR_ELx_EE_SHIFT, 1f )

tst x19, #SCTLR_ELx_C /* 检查数据缓存 */
and x19, x19, #SCTLR_ELx_M /* 分离 M 位 */
csel x19, xzr, x19, eq /* 清零 x19 如果 C=0 */
ret

1:
/* 字节序不正确,切换并禁用 MMU */
eor x19, x19, #SCTLR_ELx_EE
bic x19, x19, #SCTLR_ELx_M
b.ne 2f
/* EL2 情况 */
pre_disable_mmu_workaround
msr sctlr_el2, x19
b 3f
2:
/* EL1 情况 */
pre_disable_mmu_workaround
msr sctlr_el1, x19
3:
isb
mov x19, xzr /* MMU 关闭 */
ret
SYM_CODE_END(record_mmu_state)

4.4.2 __cpu_setup

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
29
30
31
32
33
34
35
36
37
38
/* CPU 初始化 - 设置系统控制寄存器 */
SYM_FUNC_START(__cpu_setup)
/*
* 系统控制寄存器 (SCTLR_EL1) 配置
* 清除不需要的位,设置需要的位
*/
mrs x0, sctlr_el1
bic x0, x0, #SCTLR_ELx_EE /* 小端序 */
orr x0, x0, #SCTLR_EL1_SA0 /* SP 对齐检查 */
orr x0, x0, #SCTLR_EL1_UCI /* EL1 缓存访问 */

/*
* 翻译控制寄存器 (TCR_EL1) 配置
* 定义虚拟地址空间布局
*/
mov_q x0, TCR_T0SZ(VA_BITS) | \
TCR_IRGN_WBWA | \ /* 正常内存, 写回 */
TCR_ORGN_WBWA | \ /* 正常内存, 写回 */
TCR_SHARED_NON
msr tcr_el1, x0

/*
* 内存属性间接寄存器 (MAIR_EL1)
* 定义内存类型属性
*/
ldr x0, =MAIR_EL1_SET
msr mair_el1, x0

ret
SYM_FUNC_END(__cpu_setup)

/* MAIR 属性定义 */
#define MAIR_EL1_SET \
(MAIR_ATTR_IDX(MAIR_DEVICE_nGnRnE, DEVICE_nGnRnE) | \
MAIR_ATTR_IDX(MAIR_DEVICE_nGnRE, DEVICE_nGnRE) | \
MAIR_ATTR_IDX(MAIR_NORMAL_WT, NORMAL_WT) | \
MAIR_ATTR_IDX(MAIR_NORMAL_NC, NORMAL_NC) | \
MAIR_ATTR_IDX(MAIR_NORMAL, NORMAL))

4.4.3 init_kernel_el

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
29
30
31
32
33
34
35
36
37
/* 异常级别初始化 */
SYM_FUNC_START(init_kernel_el)
mrs x0, CurrentEL
cmp x0, #CurrentEL_EL2
b.eq el2_init

/* EL1 启动路径 */
mov x0, #BOOT_CPU_MODE_EL1
ret

el2_init:
/*
* EL2 启动路径
* 配置 HCR_EL2 并降到 EL1
*/
mov x0, #BOOT_CPU_MODE_EL2
bl __el2_setup

/* 退出到 EL1 */
eret
SYM_FUNC_END(init_kernel_el)

/* EL2 设置 */
SYM_FUNC_START(__el2_setup)
/*
* HYP 配置寄存器 (HCR_EL2)
* 禁用虚拟化功能,降到 EL1
*/
mov x0, #HCR_RW /* EL1 是 AArch64 */
msr hcr_el2, x0

isb

/* 切换到 EL1 */
mov x0, #BOOT_CPU_MODE_EL1
ret
SYM_FUNC_END(__el2_setup)

4.5 __primary_switch

4.5.1 启用 MMU 并切换

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/* 主切换代码 - 启用 MMU 并跳转到虚拟地址 */
SYM_FUNC_START_LOCAL(__primary_switch)
/*
* x0 = 物理地址偏移 (如果需要)
*/
adrp x1, init_idmap_pg_dir /* ID 页表 */
adrp x2, init_pg_dir /* 最终页表 */

/* 创建内核页表 */
bl __create_page_tables

/*
* 启用 MMU
* 设置 TTBR1_EL1 并设置 SCTLR_EL1.M
*/
bl __enable_mmu

/*
* 返回值:
* x0 = 物理地址和虚拟地址之间的偏移
*/
adrp x1, init_idmap_pg_end
sub x1, x1, x0 /* 转换为虚拟地址 */

/* 设置栈 */
adr_l sp, init_task + THREAD_SIZE
mov x29, xzr

/* 跳转到 C 代码 */
bl start_kernel

/* 不应该返回 */
.L__primary_switch:
b .L__primary_switch
SYM_FUNC_END(__primary_switch)

/* 启用 MMU */
SYM_FUNC_START_LOCAL(__enable_mmu)
/* mair/el1 已经在 __cpu_setup 中设置 */

/* 设置 TCR */
msr tcr_el1, x1

/* 设置 TTBR1_EL1 (内核页表) */
msr ttbr1_el1, x2

isb

/*
* 启用 MMU
* 设置 SCTLR_EL1.M = 1
*/
mrs x0, sctlr_el1
orr x0, x0, #SCTLR_ELx_M
msr sctlr_el1, x0

isb

/*
* 返回物理偏移
* 计算虚拟地址和物理地址的差异
*/
adrp x0, init_idmap_pg_dir
and x0, x0, #~((1 << 21) - 1) /* 页表基址 */

ret
SYM_FUNC_END(__enable_mmu)

4.6 设备树处理

4.6.1 setup_machine_fdt

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* arch/arm64/kernel/setup.c */

char * __init setup_machine_fdt(void *dt_virt)
{
const char *name;
struct machine_desc *mdesc;

/* 验证设备树 */
if (!early_init_dt_scan(dt_virt))
return NULL;

/* 获取兼容字符串 */
name = of_flat_dt_get_machine_name();

/* 查找匹配的机器描述 */
for_each_machine_desc(mdesc) {
if (of_flat_dt_match(dt_virt, mdesc->dt_compat))
return mdesc->name;
}

/* 使用默认机器描述 */
return "unknown";
}

/* 设备树扫描 */
bool __init early_init_dt_scan(void *params)
{
/* 检查魔数 */
if (fdt_check_header(params))
return false;

/* 初始化全局设备树指针 */
initial_boot_params = params;

/* 扫描 /chosen 节点 */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

/* 扫描 /memory 节点 */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
of_scan_flat_dt(early_init_dt_scan_memory, NULL);

return true;
}

4.6.2 unflatten_device_tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* drivers/of/fdt.c */

void __init unflatten_device_tree(void)
{
/*
* 将扁平设备树 (FDT) 转换为分层设备树
* 创建 device_node 结构
*/
__unflatten_device_tree(initial_boot_params, &of_root,
early_init_dt_alloc_memory_map, true);

/* 获取 /chosen 节点 */
of_chosen = of_find_node_by_path("/chosen");

/* 获取启动参数 */
if (of_chosen) {
of_property_read_string(of_chosen, "bootargs",
&boot_command_line);
}
}

4.7 ARM64 内存初始化

4.7.1 arm64_memblock_init

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
29
30
31
32
33
34
35
36
/* arch/arm64/kernel/setup.c */

void __init arm64_memblock_init(void)
{
const s64 linear_region_size = -(s64)PAGE_OFFSET;

/* 注册内核镜像区域 */
memblock_reserve(__pa(_text), _end - _text);

/* 注册设备树区域 */
if (initial_boot_params)
memblock_reserve(__pa(initial_boot_params),
fdt_totalsize(initial_boot_params));

/* 初始化 memblock */
early_init_fdt_scan_reserved_mem();

/*
* 处理内存区域
* 从设备树或 ACPI 获取内存映射
*/
arm64_memblock_init_mem();

/*
* 设置保留内存
* - 内核镜像
* - 设备树
* - initrd
*/
reserve_elfcorehdr();
reserve_crashkernel();

/* 调整内存布局 */
memblock_allow_resize();
memblock_dump_all();
}

4.7.2 paging_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* arch/arm64/mm/mmu.c */

void __init paging_init(void)
{
/*
* 创建内核页表
* - 直接映射物理内存
* - vmalloc 空间
* - vmemmap 空间
*/
map_kernel(pgdp);

/*
* 创建线性映射
* 物理内存 → 虚拟地址的直接映射
*/
map_mem(pgdp);

/*
* 刷新 TLB
*/
flush_tlb_all();
}

4.8 ARM64 启动总结

4.8.1 完整启动流程

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
29
30
31
32
33
34
35
36
37
38
┌─────────────────────────────────────────────────────────────┐
│ ARM64 启动流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 固件/引导程序 (UEFI/ATF/U-Boot) │
│ │ │
│ │ - 加载内核 Image 到内存 │
│ │ - 加载设备树 (DTB) │
│ │ - 设置 x0 = DTB 物理地址 │
│ │ - 跳转到 primary_entry │
│ │ │
│ ↓ │
│ primary_entry (head.S) │
│ │ │
│ ├─ record_mmu_state (记录 MMU 状态) │
│ ├─ preserve_boot_args (保存启动参数) │
│ ├─ __pi_create_init_idmap (创建身份映射) │
│ ├─ init_kernel_el (初始化异常级别) │
│ └─ __cpu_setup (CPU 初始化) │
│ │ │
│ ↓ │
│ __primary_switch │
│ │ │
│ ├─ __create_page_tables (创建页表) │
│ ├─ __enable_mmu (启用 MMU) │
│ │ │
│ ↓ │
│ start_kernel (C 代码) │
│ │ │
│ ├─ setup_arch() │
│ │ ├─ setup_machine_fdt() (处理设备树) │
│ │ ├─ arm64_memblock_init() (内存初始化) │
│ │ ├─ paging_init() (页表初始化) │
│ │ └─ ... │
│ │ │
│ └─ ... (后续通用初始化) │
│ │
└─────────────────────────────────────────────────────────────┘

4.8.2 ARM64 vs x86_64 启动对比

方面 ARM64 x86_64
入口点 primary_entry startup_32/startup_64
首先执行 汇编代码 实模式 C 代码
模式切换 EL3/EL2 → EL1 Real → Protected → Long
参数传递 x0 (DTB) boot_params 结构
页表初始化 C 代码中设置 汇编代码中设置
MMU 启用 __enable_mmu cr4_set_pg/cr3_write
设备发现 设备树 ACPI/e820
  • Title: Linux内核分析之基础知识-4
  • Author: 韩乔落
  • Created at : 2026-01-08 02:46:57
  • Updated at : 2026-01-19 13:40:51
  • Link: https://jelasin.github.io/2026/01/08/Linux内核分析之基础知识-4/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments