CPU 启动过程详解

目录

  1. 概述
  2. 上电与复位(Power-On & Reset)
  3. Reset Vector — CPU 的第一条指令
  4. 实模式(Real Mode)初始化
  5. BIOS 阶段
  6. UEFI 阶段
  7. 引导加载程序(Bootloader)
  8. 从实模式到保护模式
  9. 从保护模式到长模式(64 位)
  10. 操作系统内核入口
  11. 多核(SMP)启动
  12. 常见架构差异对比(x86 vs ARM)
  13. 启动时序图
  14. 参考资料

1. 概述

CPU 启动过程(Boot Process)是指从硬件上电到操作系统内核主函数(如 kmain()start_kernel())执行之间发生的全部事件序列。这个过程涉及硬件逻辑、固件(Firmware)、引导加载程序(Bootloader)和操作系统内核四个层次的协作。

本文以 x86-64 架构为主线展开,并在最后一章对比 ARM 架构的关键差异。


2. 上电与复位(Power-On & Reset)

2.1 硬件事件序列

电源就绪 → 芯片组发出 RESET# 信号 → CPU 复位 → 采样配置引脚 → 执行第一条指令
  1. 电源稳定:当按下电源按钮后,电源供应器(PSU)在极短的时间内(通常为 100-500 ms)完成电压稳定,并向主板发出 POWER_GOOD 信号。
  2. RESET# 信号:芯片组(PCH / Southbridge)检测到 POWER_GOOD 后,向 CPU 的 RESET# 引脚发送一个有效低电平信号,持续至少 1 ms,然后释放。
  3. CPU 内部复位:RESET# 释放后,CPU 内部逻辑执行硬件复位:
    • 所有寄存器恢复为预定义的复位值(见下表)。
    • 内部缓存(L1/L2/L3)被无效化。
    • 分支预测器(Branch Target Buffer)、TLB(Translation Lookaside Buffer)被清空。
    • 微码(Microcode)加载引擎准备就绪。
  4. 配置引脚采样:CPU 在复位释放后的上升沿采样一批配置引脚(如 BSEL 用于总线频率,SKTOEN 用于封装类型),这些引脚的电平组合决定了 CPU 的初始操作模式。

2.2 关键寄存器的复位值

寄存器 复位值(x86) 说明
CS.Base 0xFFFF0000 代码段基址
CS.Selector 0xF000 代码段选择子
EIP 0x0000FFF0 指令指针
CS:EIP 0xFFFFFFF0 第一条指令地址(Reset Vector)
CR0 0x60000010 PE=0(实模式),ET=1(协处理器类型),NE=1
CR4 0x00000000 所有扩展功能关闭
EFLAGS 0x00000002 位 1 固定为 1,其余为 0
RFLAGS 0x00000002 同上(64 位)
GDTR.Base 0x00000000 GDT 基址未初始化
IDTR.Base 0x00000000 IDT 基址未初始化
LDTR 0x0000 LDT 选择子为 0
TR 0x0000 任务寄存器为 0
EFER 0x00000000 LME=0(长模式未启用)
MTRRs 平台相关 内存类型范围寄存器由 BIOS 配置

2.3 复位后关键寄存器的状态图

CS.Base = 0xFFFF0000
   │
   ├── 实模式寻址公式: PhysicalAddr = (CS.Base << 4) + EIP
   │      注意: 在 Reset 瞬间,CS.Base 的高 12 位被置 1,覆盖实模式规则
   │
   └── 必须用特殊规则:
       PhysicalAddr = (CS.Base | 0xFFFF0000) + EIP
                    = (0xFFFF0000) + 0x0000FFF0
                    = 0xFFFFFFF0    ← Reset Vector

3. Reset Vector — CPU 的第一条指令

3.1 地址映射

CPU 复位后执行的第一条指令位于地址 0xFFFFFFF0,即 Reset Vector(复位向量)。这个地址落在系统固件(BIOS 或 UEFI)映射的 ROM 区域末尾前 16 字节处。

物理地址空间布局(复位瞬间,简化的 x86 布局):

0x00000000 ┌──────────────────────┐
           │       DRAM           │
           │                      │
           │                      │
0xE0000000 ├──────────────────────┤ ◄── 芯片组存储器地址映射
           │    PCI/MMIO 空间     │
           │                      │
0xFFE00000 ├──────────────────────┤
           │  BIOS/UEFI ROM (2MB) │
0xFFFFFFF0 ├──────────────────────┤ ◄── Reset Vector
0xFFFFFFF0 └──────────────────────┘
           [jmp far f000:e05b]    ◄── 这里是一条跳转指令

3.2 Reset Vector 处的内容

Reset Vector 通常不是真正的启动代码起点,而是一条跳转指令(典型的是 jmp far),因为 16 字节的空间不足以存放完整的初始化代码。

; 典型 Reset Vector 处的代码(在 BIOS/UEFI ROM 中)
FFFFFFF0: EA 5B E0 00 F0      ; jmp far ptr 0xF000:0xE05B
; 或者
FFFFFFF0: E9 xx xx xx         ; jmp near (相对跳转)
; 或者 UEFI 平台上的 64 位代码
FFFFFFF0: 66 EA 5B E0 00 F0   ; jmp far ptr 0xF000:0xE05B (operand-size override)

执行这条跳转后,CPU 进入 0xF000:0xE05B,即物理地址 0xFE05B,这是 BIOS/UEFI 启动代码的真实入口。

3.3 Reset Vector 的特殊之处

  • CS 的隐藏部分(Hidden Descriptor Cache):在 Reset 时,CS 的段描述符缓存被加载为特殊值:
    • Base = 0xFFFF0000
    • Limit = 0xFFFFFFFF(因为 Reset 时 Limit 被设为最大值 4GB)
    • 这使得地址 0xFFFFFFF0 可达。
  • 跳转指令执行后,CS.Base 被重新加载为 0x000F0000,CPU 恢复到标准的实模式寻址。

4. 实模式(Real Mode)初始化

4.1 进入实模式

Reset 后 CPU 默认处于实模式(Real Mode),这是 x86 最基础的运行模式:

  • 寻址能力:20 位地址总线 → 最大 1 MB 地址空间(0x00000 ~ 0xFFFFF
  • 段寻址PhysicalAddr = SegmentRegister × 16 + Offset
  • 权限:无保护机制,所有代码运行在最高权限(Ring 0)
  • 默认操作数大小:16 位

4.2 BIOS 初始化(POST)

固件在 Reset Vector 跳转后执行 POST(Power-On Self Test):

CPU 复位
  │
  ├── 1. CPUID 检查
  │     CPU 执行 CPUID 指令检测自身型号/特性
  │     加载微码补丁(Microcode Update)
  │     检查必要特性是否存在(如 FPU、PAE、MSR、APIC)
  │
  ├── 2. 基本芯片组初始化
  │     配置 MCH(Memory Controller Hub)
  │     初始化内存控制器
  │     设置内存时序(SPD 读取)
  │
  ├── 3. 内存检测与初始化(DRAM Init)
  │     SPD(Serial Presence Detect)读取
  │     DDR 时序训练
  │     ECC 检测(如果支持)
  │     内存测试(快速模式)
  │
  ├── 4. 低级硬件初始化
  │     设置 MTRR(Memory Type Range Registers)
  │     配置 Local APIC
  │     初始化缓存(Cache As RAM,CAR)
  │
  ├── 5. 中断向量表(IVT)准备
  │     在物理地址 0x0000:0x0000 处建立 IVT
  │     BIOS 填充中断向量(如 int 13h、int 10h、int 15h)
  │
  └── 6. PCI 总线枚举
        检测 PCI/PCIe 设备
        分配总线号、IRQ、MMIO 地址

4.3 实模式内存布局(初始阶段)

0x000000 ┌──────────────────────┐
         │   中断向量表 (IVT)    │  1024 字节 (256 × 4 字节)
         │   (1 KB)             │
0x000400 ├──────────────────────┤
         │   BIOS 数据区 (BDA)   │  256 字节
0x000500 ├──────────────────────┤
         │   自由内存            │
         │   (实模式可用)        │
0x07C00  ├──────────────────────┤ ◄── MBR/VBR 加载地址
         │   引导扇区 (512B)     │
0x07E00  ├──────────────────────┤
         │   自由内存            │
0xA0000  ├══════════════════════┤ ◄── ISA/VGA 内存区域开始
         │   VGA 帧缓冲区        │  (128 KB)
0xC0000  ├──────────────────────┤
         │   Option ROMs         │  (VGA BIOS, SCSI BIOS 等)
         │   (可选的 128 KB)     │
0xE0000  ├──────────────────────┤
         │   BIOS 数据区         │  (64 KB)
0xEF000  ├──────────────────────┤
         │   BIOS 扩展           │
0xF0000  ├──────────────────────┤
         │   BIOS ROM            │  (64 KB) F000:0000 ~ F000:FFFF
0x100000 └──────────────────────┘ ◄── 1 MB 边界(HMA)

5. BIOS 阶段

5.1 传统 BIOS 流程

POST 完成
  │
  ├── 检查 CMOS 中的引导顺序(Boot Order)
  │
  ├── 尝试加载第一个可引导设备的第一个扇区(512 字节)
  │     读取到地址 0x0000:0x7C00
  │
  ├── 验证引导签名(0x55AA 在扇区偏移 510 处)
  │     ┌─ 有效 → 跳转到 0x0000:0x7C00(执行 MBR)
  │     └─ 无效 → 尝试下一个设备
  │              全部失败 → "No bootable device" 错误
  │
  └── ROM shadowing(可选)
      将 BIOS ROM 内容复制到 RAM 中以提高访问速度

5.2 BIOS 中断服务

BIOS 通过软件中断提供底层硬件访问服务:

中断号 功能 典型用途
int 10h 视频服务 设置显示模式、写字符、读/写像素
int 13h 磁盘服务 读/写扇区、获取磁盘参数
int 15h 系统服务 内存大小查询(E820 映射)
int 16h 键盘服务 读取按键、检测按键状态
int 1Ah 时间服务 RTC 读取/设置、时钟滴答计数

重要:在 UEFI 引导模式下,传统 BIOS 中断通常不可用。

5.3 传统 BIOS 的局限性

  • 只能寻址 2.2 TB 以上的磁盘(MBR 分区限制)
  • 16 位实模式代码,效率低下
  • 无法充分利用现代硬件特性
  • 已被 UEFI 取代

6. UEFI 阶段

6.1 UEFI 概述

UEFI(Unified Extensible Firmware Interface)取代了传统 BIOS,提供了更现代、功能更丰富的启动环境:

  • CPU 模式:UEFI 固件本身在复位后仍在实模式启动,但非常迅速地切换到保护模式甚至长模式(64 位)
  • 启动服务(Boot Services):在操作系统接管前可用
  • 运行时服务(Runtime Services):操作系统启动后仍可通过约定接口调用
  • GPT 分区表:支持 2 TB 以上磁盘
  • 安全启动(Secure Boot):验证引导加载程序签名

6.2 UEFI 启动流程

POST + 硬件初始化
  │
  ├── SEC (Security Phase) —— 安全阶段
  │     处理系统重置
  │     设置临时内存(Cache-as-RAM,CAR)
  │     验证固件的安全性和完整性
  │     找到并跳转到 PEI Phase
  │
  ├── PEI (Pre-EFI Initialization) —— 预初始化阶段
  │     初始化内存控制器
  │     发现和初始化系统内存
  │     创建 HOB(Hand-Off Block)传递给 DXE
  │     找到 PEI 核心和 PEIM(PEI Modules)
  │
  ├── DXE (Driver Execution Environment) —— 驱动执行环境
  │     完整的内存子系统可用
  │     加载和执行 UEFI 驱动程序
  │     初始化控制台(Serial, Graphics)
  │     生成 UEFI 系统表(System Table)
  │     枚举 PCI 总线
  │     初始化 ACPI、SMBIOS 表
  │
  ├── BDS (Boot Device Selection) —— 引导设备选择
  │     显示固件菜单(如按 F2/Del)
  │     读取 BootOrder / BootManager 策略
  │     遍历引导选项
  │
  └── TSL (Transient System Load) —— 短暂系统加载
       启动操作系统引导加载程序
       (如 Windows Boot Manager、GRUB、systemd-boot)
       加载器通过 UEFI Boot Services 分配内存和加载文件

最后 UEFI 调用 ExitBootServices(),释放固件所有权给操作系统

6.3 UEFI 系统表

UEFI 启动时向操作系统引导加载程序传递一套关键的数据结构:

typedef struct {
    EFI_TABLE_HEADER         Hdr;           // 表头(签名 = "IBI SYST")
    UINT16                   FirmwareVendor; // 固件厂商字符串
    UINT32                   FirmwareRevision;
    EFI_HANDLE               ConsoleInHandle;
    EFI_SIMPLE_TEXT_INPUT_PROTOCOL  *ConIn; // 输入协议
    EFI_HANDLE               ConsoleOutHandle;
    EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; // 输出协议
    EFI_HANDLE               StandardErrorHandle;
    EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
    EFI_RUNTIME_SERVICES     *RuntimeServices;  // 运行时服务表
    EFI_BOOT_SERVICES        *BootServices;     // 启动服务表
    UINTN                    NumberOfTableEntries;
    EFI_CONFIGURATION_TABLE  *ConfigurationTable; // 配置表(含 ACPI、SMBIOS 指针)
} EFI_SYSTEM_TABLE;

6.4 安全启动(Secure Boot)

UEFI 固件
  │
  ├── 内置可信根证书(KEK、db、dbx)
  │
  ├── 验证引导加载程序签名
  │     ┌─ 通过 → 执行
  │     └─ 失败 → 禁止启动(Shim 或 MOK 可绕过)
  │
  └── 引导加载程序继续验证内核签名

7. 引导加载程序(Bootloader)

7.1 传统 BIOS 引导:MBR → VBR → Bootloader

第一阶段: MBR (主引导记录)
  从引导设备的第一个扇区(LBA 0)加载到 0x7C00
  大小: 512 字节(含 446 字节代码 + 64 字节分区表 + 2 字节签名 0x55AA)
  功能: 加载第二阶段的起始扇区

第二阶段: VBR (卷引导记录) / 引导加载程序第一阶段
  由 MBR 加载的活动分区的第一个扇区
  大小: 通常 512 字节 ~ 32 KB
  功能: 加载文件系统驱动并定位引导加载程序主体

第三阶段: 引导加载程序主体
  如 GRUB 的 core.img、Windows 的 bootmgr
  从文件系统加载完整的引导加载程序
  切换到保护模式或长模式
MBR 结构
偏移量     大小      内容
─────────────────────────────────
0x0000    446 字节   引导代码(第一阶段加载器)
0x01BE     16 字节   分区表条目 1
0x01CE     16 字节   分区表条目 2
0x01DE     16 字节   分区表条目 3
0x01EE     16 字节   分区表条目 4
0x01FE      2 字节   引导签名 (0x55AA)

7.2 UEFI 引导

UEFI 直接加载一个EFI 应用程序(即引导加载程序),无需 MBR/VBR 两级加载:

UEFI 固件
  │
  ├── 从 ESP (EFI System Partition, FAT32 格式) 读取 .efi 文件
  │
  ├── 路径:
  │    EFI\BOOT\BOOTX64.EFI    (x86-64 默认/回退)
  │    EFI\Microsoft\Boot\bootmgfw.efi  (Windows)
  │    EFI\GRUB\grubx64.efi    (GRUB)
  │    EFI\systemd\systemd-bootx64.efi  (systemd-boot)
  │
  ├── 加载器可直接使用 UEFI 协议:
  │    - 文件系统访问 (EFI_SIMPLE_FILE_SYSTEM_PROTOCOL)
  │    - 内存分配 (AllocatePages, AllocatePool)
  │    - 图形输出 (EFI_GRAPHICS_OUTPUT_PROTOCOL)
  │    - 网络引导 (PXE, HTTP Boot)
  │
  └── 引导加载程序加载操作系统内核后调用 ExitBootServices()

7.3 GRUB(Grand Unified Bootloader)流程示例

GRUB stage 1 (boot.img)
  嵌入在 MBR 或 GPT 的 boot code 区域 (512 字节)
  │
  ├── 加载 stage 1.5 (diskboot.img) —— 512 字节
  │   从 MBR 之后的扇区加载 core.img
  │
  └── core.img —— 包含文件系统驱动
       │
       ├── 识别 /boot/grub 文件系统
       │
       ├── 加载 GRUB 模块 (normal.mod, linux.mod, chain.mod 等)
       │
       ├── 显示 GRUB 菜单
       │
       └── 根据配置引导:
            ├── 直接引导 Linux:   kexec /vmlinuz → 进入保护模式/长模式
            ├── 链式引导 Windows: chainloader +1
            └── 多引导 (Multiboot): 加载符合 Multiboot 规范的内核

8. 从实模式到保护模式

8.1 为什么需要切换模式

特性 实模式 保护模式
地址位宽 20 位 32 位
最大寻址 1 MB 4 GB
寻址方式 段:偏移 (段 × 16 + 偏移) 分段 + 分页(可选)
权限级别 无(Ring 0 等效) 4 级 Ring(0/1/2/3)
保护机制 段级保护、页级保护
中断处理 IVT(中断向量表) IDT(中断描述符表)

引导加载程序内核加载完毕后,必须切换模式以获得完整寻址能力和保护机制。

8.2 切换步骤

; 伪代码 —— 从实模式切换到保护模式

; 1. 禁用中断(IDT 在保护模式下是 GDT 格式,未就绪前不可中断)
cli

; 2. 建立 GDT(Global Descriptor Table)
;    包含至少: 空描述符, 代码段描述符, 数据段描述符
;    代码段: DPL=0, Type=Execute/Read, S=1, D/B=1, G=1
;    数据段: DPL=0, Type=Read/Write, S=1, D/B=1, G=1

; 3. 加载 GDTR
lgdt [gdt_ptr]

; 4. 设置 CR0 的保护模式位 (PE = Protection Enable)
mov eax, cr0
or  eax, 0x00000001   ; CR0.PE = 1
mov cr0, eax

; 5. 远跳转刷新指令流水线
jmp 0x08:protected_mode_entry

; 6. 重新加载段寄存器
protected_mode_entry:
    mov ax, 0x10       ; 选择数据段描述符
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

; 7. 设置栈指针
    mov esp, 0x7C00

; 8. 启用分页(可选,但推荐)
;    建立页目录和页表
;    设置 CR3 = 页目录基址
;    设置 CR0.PG = 1

; 9. 设置 IDT
;    填充中断/异常处理入口

; 10. 开中断
sti

8.3 GDT 结构

// GDT 描述符结构
struct gdt_entry {
    uint16_t limit_low;      // 段界限低 16 位
    uint16_t base_low;       // 基址低 16 位
    uint8_t  base_middle;    // 基址中间 8 位
    uint8_t  access;         // 访问权限字节
        // bit 7: P (Present)
        // bits 6-5: DPL (Descriptor Privilege Level)
        // bit 4: S (System=0 / Code/Data=1)
        // bits 3-0: Type (代码: 执行/读, 数据: 读/写)
    uint8_t  granularity;    // 粒度 + limit_high
        // bit 7: G (Granularity: 0=1B, 1=4KB)
        // bit 6: D/B (Default operation size: 0=16bit, 1=32bit)
        // bit 5: L (Long mode, for 64-bit code segment)
        // bits 3-0: limit_high (段界限高 4 位)
    uint8_t  base_high;      // 基址高 8 位
} __attribute__((packed));

// 典型保护模式 GDT
static gdt_entry gdt[] = {
    { .limit_low=0,     .base_low=0,     .access=0x00, .granularity=0x00 },  // 空描述符
    { .limit_low=0xFFFF, .base_low=0,   .access=0x9A, .granularity=0xCF },  // 代码段: Ring 0, 32bit, 4GB
    { .limit_low=0xFFFF, .base_low=0,   .access=0x92, .granularity=0xCF },  // 数据段: Ring 0, 32bit, 4GB
    { .limit_low=0xFFFF, .base_low=0,   .access=0xFA, .granularity=0xCF },  // 代码段: Ring 3, 32bit, 4GB
    { .limit_low=0xFFFF, .base_low=0,   .access=0xF2, .granularity=0xCF },  // 数据段: Ring 3, 32bit, 4GB
};

9. 从保护模式到长模式(64 位)

9.1 x86-64 长模式

x86-64 长模式(Long Mode)是 64 位操作系统的目标模式,分为两个子模式:

子模式 描述 地址位宽 操作数默认大小
64 位模式 完整 64 位执行 64 位(实际 48/57 位) 32 位(可用 REX 前缀切换)
兼容模式 运行 32 位/16 位程序 32 位 32/16 位

9.2 切换条件

从保护模式切换到长模式需要满足以下条件:

  1. CPU 支持:CPUID 的 EDX[29](MSR)为 1 表示支持
  2. 物理地址扩展(PAE):CR4.PAE = 1
  3. 页表结构:必须使用 IA-32e 分页(4 级或 5 级页表),不能使用传统 2 级页表
  4. EFER.LME = 1:设置 IA32_EFER MSR 的长模式启用位
  5. CR0.PG = 1:启用分页,CPU 自动进入长模式

9.3 切换步骤

; x86-64 长模式切换(从保护模式)

; 1. 检查 CPU 是否支持长模式
    mov eax, 0x80000001
    cpuid
    test edx, (1 << 29)    ; bit 29 = LM (Long Mode)
    jz   no_long_mode

; 2. 禁用分页和分页相关功能
    mov eax, cr0
    and eax, ~(1 << 31)    ; CR0.PG = 0
    mov cr0, eax
    ; 现在 CPU 处于保护模式(未分页)

; 3. 启用 PAE(物理地址扩展)
    mov eax, cr4
    or  eax, (1 << 5)      ; CR4.PAE = 1
    ; 如果需要 5 级分页(57 位虚拟地址):
    ; or  eax, (1 << 12)   ; CR4.LA57 = 1
    mov cr4, eax

; 4. 建立 4 级页表(IA-32e paging)
;    PML4 → PDP → PD → PT → 2MB/4KB 页面
;
;    内存映射典型设置:
;    低 1:1 映射(恒等映射): 虚拟地址 = 物理地址
;    高地址映射: 虚拟地址 = KERNEL_BASE + 物理地址(可选)

; 5. 加载 PML4 基址到 CR3
    mov eax, pml4_table
    mov cr3, eax

; 6. 设置 EFER.LME = 1(长模式启用)
    mov ecx, 0xC0000080    ; IA32_EFER MSR
    rdmsr
    or  eax, (1 << 8)      ; EFER.LME = 1
    wrmsr

; 7. 启用分页(此时 CPU 进入长模式)
    mov eax, cr0
    or  eax, (1 << 31)     ; CR0.PG = 1
    mov cr0, eax
    ; 至此,CPU 已处于长模式的兼容模式。
    ; 还必须加载 64 位代码段来进入 64 位模式。

; 8. 加载 64 位 GDT
;    需要一个带有 L=1 的代码段描述符和一个 64 位数据段描述符
    lgdt [gdt64_ptr]

; 9. 远跳转进入 64 位模式
    jmp 0x08:x64_entry     ; 0x08 选择 GDT 中 L=1 的代码段

[BITS 64]
x64_entry:
    ; 现在 CPU 在 64 位长模式下执行

    ; 10. 重新加载段寄存器(64 位下段的作用大幅弱化)
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; 11. 设置 64 位栈指针
    mov rsp, stack_top

    ; 12. 可选: 设置 GS.base 用于 CPU 本地数据(per-CPU area)
    ;    wrmsr 0xC0000101 (GS base)

    ; 13. 跳转到内核入口
    jmp kmain

9.4 长模式 GDT

// x86-64 长模式 GDT
static gdt_entry gdt64[] = {
    [0] = {0},                                  // 空描述符
    [1] = {                                     // 64 位代码段
        .limit_low  = 0,
        .base_low   = 0,
        .access     = 0x9A,                     // P=1, DPL=0, S=1, Type=Execute/Read
        .granularity = 0x20,                    // L=1 (Long mode), G=0
        .base_high  = 0,
    },
    [2] = {                                     // 数据段
        .limit_low  = 0,
        .base_low   = 0,
        .access     = 0x92,                     // P=1, DPL=0, S=1, Type=Read/Write
        .granularity = 0x00,
        .base_high  = 0,
    },
    // 用户模式段(可选)
    [3] = { .access = 0xFA, .granularity = 0x20 }, // 用户代码段: DPL=3, L=1
    [4] = { .access = 0xF2 },                      // 用户数据段: DPL=3
};

10. 操作系统内核入口

10.1 从引导加载程序到内核

引导加载程序将内核加载到内存后,通过约定的接口将控制权移交给内核。不同操作系统采用不同的约定:

Linux (Multiboot / Boot Protocol)
GRUB → header.S → startup_32 → startup_64 → x86_64_start_kernel → start_kernel
  │       │           │            │                │                  │
  │       │           │            │                │                  └── 通用内核初始化
  │       │           │            │                └── 设置 BSS、IDT、GDT
  │       │           │            └── 切换/确认 64 位长模式
  │       │           └── 进入 32 位保护模式(如果尚未在保护模式)
  │       └── 解析内核头部(boot_params)
  └── 设置实模式参数(E820 内存映射、视频模式等)

Linux 内核头部细节

内核镜像(vmlinuz / bzImage)开头有一个实模式头部结构:

struct boot_params {
    struct setup_header hdr;   // 偏移 0x01F1 开始
    // hdr 包含:
    //   - setup_sects: setup 代码的扇区数
    //   - syssize: 内核大小
    //   - ramdisk_image/ramsize: initrd 地址和大小
    //   - vid_mode: 视频模式
    //   - boot_flag: 0xAA55
    //   - code32_start: 32 位入口地址
    //   - kernel_alignment: 内核对齐要求
    //   - `relocatable_kernel`: 标记内核是否可重定位
    //   - `pref_address`: 首选的加载地址
    ...
};

10.2 Linux 内核启动(start_kernel

// init/main.c
asmlinkage __visible void __init start_kernel(void)
{
    char *command_line;
    char *after_dashes;

    // 1. 设置 CPU 特定数据
    setup_arch(&command_line);       // 架构特定初始化
    //    ├── setup_memory_map()
    //    ├── parse_early_param()
    //    ├── trap_init()          —— 设置 IDT
    //    ├── mm_init()            —— 内存管理初始化
    //    └── paging_init()        —— 最终页表初始化

    // 2. 控制台 / 日志
    setup_log_buf(0);
    vprintk_emit(0, LOGLEVEL_DEFAULT,
                 NULL, 0, "%s", linux_banner);  // 打印 Linux 横幅

    // 3. 内存管理
    mm_init();                       // slab 分配器、页分配器初始化

    // 4. 调度器初始化
    sched_init();

    // 5. 中断子系统
    early_irq_init();
    init_IRQ();
    tick_init();

    // 6. 定时器
    init_timers();
    hrtimers_init();

    // 7. 软中断 / tasklets
    softirq_init();

    // 8. 内核线程 / 进程
    rest_init();                     // 创建 init 进程 (PID 1)
    //    ├── kernel_init()
    //    │    ├── do_basic_setup()
    //    │    │    ├── driver_init()    —— 驱动程序初始化
    //    │    │    └── do_initcalls()   —— 所有模块初始化(initcall 级别)
    //    │    ├── prepare_namespace()   —— 挂载根文件系统
    //    │    └── run_init_process("/sbin/init")  —— 执行 init
    //    │        或其他路径: /etc/init, /bin/init, /bin/sh
    //    │
    //    └── cpu_startup_entry() —— CPU 空闲循环(idle 循环)
}

10.3 内核入口的关键步骤

步骤 函数 说明
架构初始化 setup_arch() 解析内核命令行、初始化内存映射、设置页表
陷阱/TSS 初始化 trap_init() 设置 IDT 表(中断/异常处理程序)
内存初始化 mm_init() 启动 buddy allocator、slab allocator
调度器初始化 sched_init() 初始化进程调度器数据结构
中断初始化 init_IRQ() 初始化 8259A PIC 或 APIC
定时器初始化 init_timers() 初始化内核定时器子系统
软件中断 softirq_init() 初始化 softirq/tasklet 机制
控制台 console_init() 初始化内核日志控制台
init 线程 rest_init() 创建 PID 1(init 进程)并进入空闲循环

11. 多核(SMP)启动

11.1 BSP 与 AP

现代 x86 系统中,启动期间只有一个核心执行初始化代码,称为 BSP(Bootstrap Processor),其余核心称为 AP(Application Processor)。

角色 说明
BSP 上电后自动开始执行 Reset Vector → POST → Bootloader → 内核
AP 上电后处于休眠状态(等待 STARTUP IPI)
BSP 唤醒 AP 内核初始化完成后,BSP 通过 IPI 唤醒各 AP

11.2 AP 启动(SMP 初始化)

BSP 初始化完成
  │
  ├── 1. 构建 AP 启动代码(trampoline)
  │     放在物理地址低 1 MB 内(AP 尚未启用分页时也能访问)
  │     包含实模式 → 保护模式 → 长模式的完整切换代码
  │
  ├── 2. 发送 INIT IPI
  │     让 AP 执行 INIT (重置)
  │
  ├── 3. 等待 10 ms
  │
  ├── 4. 发送 STARTUP IPI(SIPI)
  │     发往 AP 的 Local APIC ID
  │     参数 = 启动代码的页号(如 0x9 → AP 从 0x9000 执行)
  │
  ├── 5. AP 执行 trampoline 代码
  │     ┌─ 初始化自身 GDT
  │     ├─ 切换到保护模式
  │     ├─ 启用 PAE 并加载共享页表
  │     ├─ 切换到长模式(64 位)
  │     └─ 设置 per-CPU 数据区域 (GS.base)
  │
  ├── 6. AP 在 trampoline 末尾设置自己的栈
  │     跳转到内核函数 smp_callin()
  │
  ├── 7. smp_callin():
  │     ├─ 注册 AP 到全局 CPU 掩码 (cpu_online_mask)
  │     ├─ 初始化 per-CPU 变量
  │     ├─ 启用本地中断
  │     └─ 进入空闲循环 (cpu_startup_entry)
  │
  └── BSP 确认所有 AP 已注册后继续通用初始化

11.3 APIC 与 IPI 机制

Local APIC (每个 CPU 一个)
  │
  ├── LAPIC ID: 每个核心的硬件 ID(x2APIC 模式下可达 32 位)
  │
  └── IPI (Inter-Processor Interrupt) 命令寄存器:
      ┌─────────────────────────────────┐
      │ ICR (Interrupt Command Register) │
      ├─────────────────────────────────┤
      │ Destination Field │ DEST_SHORT  │
      │ Delivery Mode     │ INIT/STARTUP│
      │ Trigger Mode      │ Edge        │
      │ Level             │ De-assert   │
      │ Vector            │ 0x00 (INIT) │
      └─────────────────────────────────┘

  SIPI 格式:
      ┌─────────────────────────────────┐
      │ Delivery Mode = STARTUP (110)   │
      │ Vector = 启动代码地址 >> 12      │
      └─────────────────────────────────┘
      例: Vector = 0x9  →  AP 从物理 0x9000 开始执行

11.4 Linux SMP 启动代码示例

// arch/x86/kernel/smpboot.c

static int do_boot_cpu(int apicid, int cpu, struct task_struct *idle)
{
    unsigned long start_ip = trampoline_address();

    // 1. 将 AP 启动代码复制到低地址
    memcpy(__va(start_ip), trampoline_data, trampoline_data_end - trampoline_data);

    // 2. 设置 AP 启动参数
    *((volatile u32 *)ap_start_code) = start_ip;
    *((volatile u32 *)ap_gdt)        = __pa(gdt_page);
    *((volatile u32 *)ap_栈指针)      = idle->thread.sp;

    // 3. 发送 INIT IPI
    apic->send_IPI(apicid, INIT);

    udelay(10000);   // 等待 10 ms

    // 4. 发送 STARTUP IPI (最多 2 次)
    for (i = 0; i < 2; i++) {
        apic->send_IPI(apicid, STARTUP | (start_ip >> 12));
        udelay(300);
    }

    // 5. 等待 AP 报告启动成功或超时
    if (!cpu_online(cpu)) {
        pr_err("CPU %d failed to boot\n", cpu);
        return -EIO;
    }

    return 0;
}

12. 常见架构差异对比(x86 vs ARM)

12.1 x86-64 与 ARM64 启动对比

阶段 x86-64 ARM64 (AArch64)
复位地址 0xFFFFFFF0(Reset Vector) 由硬件配置的 RVBAR 地址(SOC 相关)
初始模式 实模式(16 位) AArch64 EL3(最高特权级)
固件 BIOS / UEFI ATF (ARM Trusted Firmware) / U-Boot
引导加载程序 GRUB, systemd-boot, Windows Boot Manager U-Boot, GRUB, TFTP 引导
设备树 ACPI(新型)/ MP 表、E820(传统) 设备树(Device Tree, DTB)
页表基址寄存器 CR3 TTBR0_EL1 / TTBR1_EL1
异常级别 Ring 0-3 EL0(用户)/ EL1(内核)/ EL2(虚拟机)/ EL3(安全监控)
SMP 启动 SIPI(STARTUP IPI) PSCI(Power State Coordination Interface)
引导参数传递 boot_params(legacy)/ UEFI 系统表 DTB(fdt)或 ACPI 表

12.2 ARM64 PSCI 启动流程

BSP(主核心):
  │
  ├── 运行 ATF (BL1 → BL2 → BL31)
  │
  ├── BL33(非安全世界引导加载程序,如 U-Boot)
  │
  └── Linux 内核调用 PSCI 唤醒其他核心:

      struct psci_operations {
          int (*cpu_on)(unsigned long target_cpu,
                        unsigned long entry_point);
      };

      // 调用 SMC/HVC 陷入 EL2/EL3
      // PSCI CPU_ON 目标核心进入 entry_point 地址

AP(从核心):
  │
  ├── 上电后进入 ATF (BL31) 的 PSCI 处理程序
  │
  ├── 等待 PSCI CPU_ON 请求
  │
  ├── 收到请求后:
  │     └── 设置 EL1 异常向量
  │         设置页表(TTBR0_EL1)
  │         设置栈指针(SP_EL1)
  │         eret 到 Linux 内核的 secondary_entry
  │
  └── 在 Linux 中调用 secondary_start_kernel()

12.3 RISC-V 启动特点(简要)

阶段 说明
复位地址 SOC 固定(通常在高地址 ROM)
机器模式 启动时处于 M-Mode(最高权限)
引导流程 ZSBL → SBI (Supervisor Binary Interface) → 引导加载程序 → OS
设备树 通常使用 FDT(Flattened Device Tree)

13. 启动时序图

以下是从上电到操作系统就绪的完整时序图:

时间 ─────────────────────────────────────────────────────────────────►

电源按钮按下
   │
   ▼
PSU 电压稳定 ────── POWER_GOOD ──────┐
                                      │
                                      ▼
                              芯片组发出 RESET#
                                      │
                                      ▼
CPU 内部复位 ── 寄存器复位 ── Cache/TLB 清空 ── 微码准备
                                      │
                                      ▼
                          采样配置引脚 (BSEL, SKTOEN...)
                                      │
                                      ▼
                          Reset Vector (0xFFFFFFF0)
                                      │
                                      ▼
                          BIOS/UEFI 固件启动
                                      │
                          ┌───────────┴───────────┐
                          ▼                       ▼
                    POST 检查              SEC/PEI 阶段
                    内存检测               CAR + 内存初始化
                    PCI 枚举               DXE 驱动加载
                    Option ROM             BDS 引导选择
                          │                       │
                          ▼                       ▼
                  传统 BIOS 扇区读取        加载 .efi 文件
                  加载 MBR → VBR           UEFI 协议调用
                          │                       │
                          └───────┬───────────────┘
                                  │
                                  ▼
                        引导加载程序 (GRUB, systemd-boot, bootmgfw.efi)
                                  │
                          ┌───────┴───────┐
                          │ 实模式:       │
                          │  内核/initrd  │
                          │  加载到内存   │
                          │  收集 E820 表 │
                          │               │
                          │ 保护模式切换  │
                          │  PAE 启用     │
                          │  4 级页表建立 │
                          │  长模式切换   │
                          └───────┬───────┘
                                  │
                                  ▼
                        操作系统内核入口
                                  │
                          ┌───────┴───────────────┐
                          │ setup_arch()          │
                          │   ├─ 页表初始化       │
                          │   └─ 内存映射建立     │
                          │ trap_init()           │
                          │ mm_init()             │
                          │ sched_init()          │
                          │ init_IRQ()            │
                          │ rest_init()           │
                          │   ├─ kernel_init()    │
                          │   │  ├─ initcalls     │
                          │   │  ├─ 挂载根文件系统│
                          │   │  └─ exec(/sbin/init)
                          │   └─ cpu_startup_entry│
                          │                      │
                          │ [BSP 唤醒 AP 核心]   │
                          │                      │
                          ▼                      ▼
                   用户空间 init (PID 1)    SMP 所有核心就绪
                          │
                          ▼
                   系统就绪 (Shell / GUI)

时间 ─────────────────────────────────────────────────────────────────►
      [0ms]   [~50ms]  [~200ms] [~500ms]  [~1-3s]   [~5-30s]
      上电    固件启动  引导加载  OS 内核   init +   完全就绪
                             程序      初始化     服务启动

14. 参考资料

  • Intel 64 and IA-32 Architectures Software Developer’s Manual (Vol. 3A: System Programming Guide, Chapter 8 — Processor Management and Initialization)
  • AMD64 Architecture Programmer’s Manual (Volume 2: System Programming, Chapter 7 — System Initialization)
  • UEFI Specification (Chapter 2: Boot Manager, Chapter 4: Firmware Phases)
  • Linux Kernel Source: arch/x86/boot/, arch/x86/kernel/head_64.S, arch/x86/kernel/smpboot.c
  • Multiboot 2 Specification (GRUB / GNU)
  • ARM Architecture Reference Manual ARMv8 (Chapter G4: AArch64 System Level Controls)
  • PSCI (Power State Coordination Interface) Specification
  • coreboot / EDK2 / TianoCore 开源固件实现

本文以 x86-64 架构为主线,涵盖了从 CPU 上电复位到操作系统内核入口、再到多核 SMP 唤醒的完整启动流程。ARM64 和 RISC-V 等架构在启动细节上有所差异,但整体概念(硬件复位 → 固件初始化 → 引导加载程序 → 操作系统内核)是一致的。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐