2. 启动流程总览:从BIOS/UEFI到内核启动的完整路径图
现代x86_64服务器从按下电源键到SMP多核系统完全就绪,经历了一条高度分层的启动链。理解这条路径是剖析SMP启动的前提——因为多核唤醒的时机、触发方式、数据传递都依赖于前序阶段的准备。
2.1 启动路径全景:固件 → Bootloader → 内核
整个启动过程可划分为三个宏观阶段,每个阶段承担不同的职责,并逐步将系统控制权从固件移交到操作系统内核。
| 阶段 | 执行主体 | 核心输出 | SMP相关性 |
|---|---|---|---|
| 固件阶段 | BIOS / UEFI | 硬件拓扑表、ACPI RSDP、APIC初始化 | 枚举所有CPU核心,为BSP/AP分配APIC ID |
| Bootloader阶段 | GRUB 2 | 内核镜像加载到内存、setup_header解析 | 仅BSP运行,AP仍处于复位向量等待状态 |
| 内核初始化阶段 | Linux内核 | start_kernel → smp_init | BSP完成全局初始化后,通过IPI唤醒AP |
关键认知:在Bootloader阶段,系统中只有一个CPU核心(BSP)在执行代码。其余核心(AP)在硬件复位后处于等待INIT-SIPI-SIPI序列的暂停状态。SMP的真正启动发生在内核初始化后期。
2.2 Bootloader(GRUB)加载内核镜像
GRUB 2是Linux生态中最主流的Bootloader。它负责从磁盘分区(或网络)读取内核镜像,将其加载到物理内存的指定位置,并准备启动参数。
2.2.1 加载流程步骤
- 阶段1.5 / 阶段2加载:GRUB从MBR或EFI系统分区加载核心映像,识别文件系统(ext4/xfs/btrfs)。
- 读取配置文件:解析
/boot/grub/grub.cfg,显示菜单或使用默认条目。 - 加载内核与initrd:将vmlinuz和initramfs镜像读入物理内存。内核镜像被放置在
0x100000(1MB)以上的区域。 - 设置启动参数:填充
struct boot_params,包括命令行、内存布局、APIC信息等。 - 跳转到内核入口:GRUB执行
ljmp到内核的startup_32(32位入口)或startup_64(64位入口)。
内存布局要点:内核镜像头部包含
setup_header(位于偏移0x1F1处),GRUB通过该结构体获取内核版本、加载标志、堆栈指针等关键参数。如果 setup_header 中的 loadflags 字段设置了 LOADED_HIGH 位,内核将被加载到高端内存(>16MB)。
2.3 SMP启动的宏观阶段划分
SMP(对称多处理)启动不是一蹴而就的,它被精心设计为三个宏观阶段,确保在唤醒其他核心之前,BSP已经完成了全局数据结构和中断控制器的初始化。
| 阶段 | 名称 | 执行函数/位置 | 核心动作 |
|---|---|---|---|
| 阶段A | BSP单核初始化 | start_kernel() → setup_arch() |
解析ACPI/MADT表,构建cpu_possible_mask,初始化本地APIC |
| 阶段B | AP核准备就绪 | smp_init() → smp_boot_cpus() |
BSP向每个AP发送INIT-SIPI-SIPI序列,AP执行trampoline代码进入保护模式 |
| 阶段C | 全核协同运行 | cpu_startup_entry() |
所有核心进入idle循环,调度器接管,负载均衡开始工作 |
常见误区:很多人认为SMP启动是从
smp_init() 开始的。实际上,BSP在 setup_arch() 中就已经通过解析ACPI表确定了系统中存在多少个CPU核心,并预留了资源。如果ACPI表损坏或缺失,SMP启动将直接失败,系统退化为单核模式。
2.4 关键数据结构:启动参数传递
从GRUB到内核,以及BSP到AP之间的数据传递依赖几个关键结构体。理解它们有助于后续章节深入分析代码。
- boot_params:位于实模式内核头部,GRUB填充。包含
hdr(setup_header)、e820_map(内存布局)、edid_info等。 - zeropage:内核在进入保护模式前将
boot_params复制到ZERO_PAGE(0x1000),后续初始化全程依赖此结构。 - acpi_table_header / MADT:固件通过ACPI表向内核传递CPU拓扑。MADT(Multiple APIC Description Table)包含每个CPU核心的Local APIC ID、Flags(是否启用)等信息。
- smp_ops:内核抽象出的SMP操作方法集,包含
.smp_boot_cpus、.smp_cpus_done等回调,允许不同架构(x86/arm64)定制SMP启动行为。
调试切入点:在启动早期,可以通过
early_printk 或 debug_early 参数打印 boot_params.hdr 中的 code32_start 字段,验证GRUB是否正确设置了内核入口点。如果该值为0,说明Bootloader阶段加载失败。