4、内核入口点分析:Linux内核在ARM64上的入口函数(stext/__primary_switch)、启动CPU与辅助CPU的分离路径
ARM64架构下,Linux内核的入口点设计体现了多核处理器启动的典型模式:主启动CPU(Primary CPU)负责全局初始化,而辅助CPU(Secondary CPU)则等待被唤醒并完成各自的初始化。本章将深入剖析内核入口函数 stext 与 __primary_switch 的执行逻辑,并详细阐述启动CPU与辅助CPU的分离路径。
4.1 内核入口点:stext
stext 是ARM64 Linux内核的第一个C语言入口点,位于 arch/arm64/kernel/head.S 中。它负责从汇编启动阶段过渡到C语言环境,并完成早期初始化。其核心任务包括:
- 设置页表:为内核空间建立恒等映射(identity mapping)和线性映射。
- 启用MMU:使能内存管理单元,进入虚拟地址空间。
- 跳转到C代码:调用
start_kernel函数。
以下是 stext 的简化执行流程:
stext 执行时,CPU仍处于物理地址空间,所有地址操作必须使用物理地址。页表建立后,MMU使能,CPU才切换到虚拟地址空间。
/*
* arch/arm64/kernel/head.S - stext 入口
*/
SYM_CODE_START(stext)
/* 1. 保存启动信息(设备树地址等) */
mov x20, x0 // DTB地址保存在x20
/* 2. 设置页表(恒等映射 + 线性映射) */
bl __create_page_tables
/* 3. 启用MMU,跳转到虚拟地址空间 */
bl __enable_mmu
/* 4. 跳转到C语言入口 */
b start_kernel
SYM_CODE_END(stext)
4.2 主启动CPU路径:__primary_switch
__primary_switch 是主CPU在启用MMU后执行的关键切换函数。它负责完成从汇编到C语言的最终过渡,并设置主CPU的启动上下文。
其核心步骤包括:
- 设置栈指针:为主CPU分配并初始化内核栈。
- 跳转到C代码:调用
start_kernel,进入内核初始化主流程。 - 标记CPU在线:在
start_kernel中,主CPU会将自己标记为“在线”,并开始初始化子系统。
__primary_switch 仅在主CPU上执行一次。辅助CPU不会执行此函数,而是通过 secondary_startup 路径启动。
4.3 辅助CPU启动路径:secondary_startup
辅助CPU的启动路径与主CPU不同。它们通常由主CPU通过CPU热插拔框架或PSCI(Power State Coordination Interface)唤醒。辅助CPU的入口点是 secondary_startup。
辅助CPU的启动流程如下:
| 步骤 | 描述 | 关键函数/操作 |
|---|---|---|
| 1 | 等待唤醒 | 辅助CPU在低功耗状态等待主CPU通过PSCI唤醒。 |
| 2 | 进入内核入口 | 辅助CPU从 secondary_entry 开始执行。 |
| 3 | 设置页表 | 复用主CPU已建立的页表,无需重新创建。 |
| 4 | 启用MMU | 使能MMU,进入虚拟地址空间。 |
| 5 | 初始化本地资源 | 调用 secondary_start_kernel,初始化中断、调度等。 |
| 6 | 标记在线 | 将辅助CPU标记为“在线”,加入调度域。 |
4.4 启动CPU与辅助CPU的分离路径
ARM64内核通过CPU ID和启动标志来区分主CPU和辅助CPU。主CPU的ID通常为0,但也可以通过设备树或ACPI指定。辅助CPU的启动路径完全独立,直到它们被唤醒并加入系统。
分离路径的核心差异如下:
- 入口点不同:主CPU从
stext开始,辅助CPU从secondary_startup开始。 - 页表创建:主CPU负责创建页表,辅助CPU直接复用。
- 初始化范围:主CPU初始化全局资源(内存、中断控制器等),辅助CPU仅初始化本地资源(栈、per-CPU变量等)。
- 同步机制:主CPU通过
cpu_up和smp_cpus_done等待所有辅助CPU完成初始化。
CPU hotplug 状态。
4.5 启动流程SVG图:主CPU与辅助CPU分离路径
下图展示了ARM64 Linux内核启动过程中,主CPU与辅助CPU的分离路径:
4.6 关键代码片段:主CPU与辅助CPU入口
以下代码展示了主CPU与辅助CPU入口点的差异:
/*
* 主CPU入口:stext
*/
SYM_CODE_START(stext)
mov x20, x0 // 保存DTB地址
bl __create_page_tables
bl __enable_mmu
b start_kernel
SYM_CODE_END(stext)
/*
* 辅助CPU入口:secondary_startup
*/
SYM_CODE_START(secondary_startup)
mov x20, x0 // 保存CPU ID
bl __enable_mmu // 复用页表,仅启用MMU
b secondary_start_kernel
SYM_CODE_END(secondary_startup)
__create_page_tables 创建页表,而辅助CPU直接调用 __enable_mmu 复用已有页表。这是两者启动路径的核心区别。
4.7 总结
ARM64 Linux内核的入口点设计通过 stext 和 secondary_startup 实现了主CPU与辅助CPU的分离路径。主CPU负责全局初始化,辅助CPU则专注于本地资源初始化。这种设计确保了多核系统的高效启动和可扩展性。