CPU 启动过程详解
CPU 启动过程(Boot Process)是指从硬件上电到操作系统内核主函数(如kmain()或)执行之间发生的全部事件序列。这个过程涉及硬件逻辑、固件(Firmware)、引导加载程序(Bootloader)和操作系统内核四个层次的协作。本文以x86-64 架构为主线展开,并在最后一章对比 ARM 架构的关键差异。CPU 模式:UEFI 固件本身在复位后仍在实模式启动,但非常迅速地切换到保护模
CPU 启动过程详解
目录
- 概述
- 上电与复位(Power-On & Reset)
- Reset Vector — CPU 的第一条指令
- 实模式(Real Mode)初始化
- BIOS 阶段
- UEFI 阶段
- 引导加载程序(Bootloader)
- 从实模式到保护模式
- 从保护模式到长模式(64 位)
- 操作系统内核入口
- 多核(SMP)启动
- 常见架构差异对比(x86 vs ARM)
- 启动时序图
- 参考资料
1. 概述
CPU 启动过程(Boot Process)是指从硬件上电到操作系统内核主函数(如 kmain() 或 start_kernel())执行之间发生的全部事件序列。这个过程涉及硬件逻辑、固件(Firmware)、引导加载程序(Bootloader)和操作系统内核四个层次的协作。
本文以 x86-64 架构为主线展开,并在最后一章对比 ARM 架构的关键差异。
2. 上电与复位(Power-On & Reset)
2.1 硬件事件序列
电源就绪 → 芯片组发出 RESET# 信号 → CPU 复位 → 采样配置引脚 → 执行第一条指令
- 电源稳定:当按下电源按钮后,电源供应器(PSU)在极短的时间内(通常为 100-500 ms)完成电压稳定,并向主板发出
POWER_GOOD信号。 - RESET# 信号:芯片组(PCH / Southbridge)检测到
POWER_GOOD后,向 CPU 的RESET#引脚发送一个有效低电平信号,持续至少 1 ms,然后释放。 - CPU 内部复位:RESET# 释放后,CPU 内部逻辑执行硬件复位:
- 所有寄存器恢复为预定义的复位值(见下表)。
- 内部缓存(L1/L2/L3)被无效化。
- 分支预测器(Branch Target Buffer)、TLB(Translation Lookaside Buffer)被清空。
- 微码(Microcode)加载引擎准备就绪。
- 配置引脚采样: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可达。
- Base =
- 跳转指令执行后,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 切换条件
从保护模式切换到长模式需要满足以下条件:
- CPU 支持:CPUID 的
EDX[29](MSR)为 1 表示支持 - 物理地址扩展(PAE):CR4.PAE = 1
- 页表结构:必须使用 IA-32e 分页(4 级或 5 级页表),不能使用传统 2 级页表
- EFER.LME = 1:设置 IA32_EFER MSR 的长模式启用位
- 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 等架构在启动细节上有所差异,但整体概念(硬件复位 → 固件初始化 → 引导加载程序 → 操作系统内核)是一致的。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)