2. Linux 引导流程:从 BIOS/UEFI 到内核启动的完整流程

Linux 系统的启动是一个分阶段、高度结构化的过程,从硬件固件初始化开始,经过引导加载程序,最终将控制权移交给内核。理解这一完整流程是分析 ACPI 电源状态初始化故障的前提。

2.1 固件阶段:BIOS 与 UEFI

系统上电后,CPU 首先执行固件代码。当前主流固件接口分为传统 BIOS 和现代 UEFI,二者在引导方式上存在本质差异。

特性 传统 BIOS UEFI
运行模式 16 位实模式 32/64 位保护模式或长模式
分区表 MBR(主引导记录) GPT(GUID 分区表)
引导设备扫描 扫描第一个扇区(512 字节) 读取 EFI 系统分区(ESP)中的 .efi 文件
ACPI 支持 RSDP 通常位于 EBDA 或 0xE0000-0xFFFFF RSDP 通过 UEFI 配置表直接传递
安全启动 不支持 支持(Secure Boot)
关键点:UEFI 固件在启动服务阶段会维护 ACPI 表(RSDT/XSDT)的指针,内核可以直接通过 UEFI 配置表获取,无需像 BIOS 那样在内存中扫描 RSDP 签名 "RSD PTR"。

2.2 引导加载程序阶段:GRUB 的作用

GRUB(GRand Unified Bootloader)是 Linux 生态中最常用的引导加载程序。它在固件与内核之间扮演“桥梁”角色,负责加载内核映像、初始 RAM 磁盘(initrd)以及传递引导参数。

GRUB 的核心工作流程

  1. 加载自身核心映像:从引导设备读取 core.img(BIOS)或 grubx64.efi(UEFI)。
  2. 解析配置文件:读取 /boot/grub/grub.cfg,显示菜单选项。
  3. 加载内核与 initrd:根据菜单项将 vmlinuz 和 initramfs 映像读入内存。
  4. 设置引导参数:构造 linux 命令行(如 acpi=off、pci=noacpi)。
  5. 启动内核:通过 boot 命令将控制权交给内核入口点。
调试提示:在 GRUB 菜单按 e 键可以编辑引导参数。临时添加 acpi=debug 可以开启 ACPI 早期调试输出,有助于定位电源状态初始化阶段的故障。
GRUB 引导加载流程 固件完成 POST GRUB core.img 解析 grub.cfg 加载内核 加载 initrd 设置引导参数 boot 启动 说明:GRUB 在加载内核前会收集 ACPI 表指针(UEFI 下通过配置表,BIOS 下通过内存扫描), 并将 RSDP 地址通过引导协议传递给内核。

2.3 内核解压与早期初始化

当 GRUB 执行 boot 命令后,控制权转移到内核的入口点。对于 bzImage 格式的内核,入口位于 arch/x86/boot/header.S 中的 start_of_setup。

内核解压阶段(实模式)

  • setup.bin:运行在实模式,执行硬件探测、内存大小检测、视频模式设置。
  • 切换到保护模式:通过 ljmp 指令跳转到 32 位代码段。
  • 解压内核:调用 decompress_kernel(),将压缩的内核映像(vmlinux.bin.gz)解压到高地址内存。

内核早期初始化(保护模式/长模式)

解压完成后,内核进入 start_kernel() 函数,这是体系结构无关的 C 语言入口。关键初始化步骤包括:

顺序 函数/模块 说明
1 sched_init() 初始化进程调度器
2 build_all_zonelists() 建立内存管理区列表
3 page_alloc_init() 初始化页分配器
4 trap_init() 设置中断描述符表(IDT)
5 acpi_boot_table_init() ACPI 早期初始化:定位 RSDP 并验证签名
6 acpi_early_init() 解析 RSDT/XSDT,注册表地址
7 acpi_subsystem_init() 初始化 ACPI 子系统,加载 DSDT
故障高发区:ACPI 早期初始化阶段(步骤 5-7)是电源状态初始化故障的集中爆发点。常见问题包括:RSDP 指针损坏、DSDT 表校验和错误、AML 解释器解析失败。如果此阶段失败,内核通常会回退到 acpi=off 模式,导致电源管理功能完全丧失。
内核解压与早期初始化流程 实模式:setup.bin 切换到保护模式 decompress_kernel() start_kernel() — 体系结构无关的 C 入口 sched_init() trap_init() acpi_boot_table_init() acpi_early_init() acpi_subsystem_init() → 加载 DSDT 注意:acpi_boot_table_init() 必须在早期内存管理就绪后立即执行,以确保 ACPI 表不被后续初始化覆盖。 如果此阶段失败,内核将无法获取电源状态转换所需的 _PTC/_CST 等控制方法。

2.4 引导流程中的 ACPI 表传递机制

ACPI 表从固件到内核的传递路径决定了电源状态信息的完整性。内核通过以下方式定位 ACPI 根系统描述指针(RSDP):

  • UEFI 系统:通过 UEFI 配置表(Configuration Table)中的 ACPI_TABLE_GUID 直接获取 RSDP 地址。
  • BIOS 系统:在扩展 BIOS 数据区(EBDA)或系统内存 0xE0000-0xFFFFF 范围内搜索 "RSD PTR" 签名。
  • 引导加载程序传递:GRUB 可以通过 linux 命令的 acpi_rsdp= 参数手动指定 RSDP 地址。
故障排查线索:如果内核启动日志中出现 ACPI: Unable to locate RSDP 或 ACPI: Core revision 20200925 后无后续 ACPI 输出,说明 ACPI 表传递失败。此时应检查:
1. 固件设置中是否禁用了 ACPI。
2. 引导参数中是否误加了 acpi=off 或 acpi=noirq。
3. UEFI 固件版本是否需要更新以修复 ACPI 表损坏问题。

2.5 引导阶段 ACPI 电源状态初始化关键点

在 acpi_subsystem_init() 完成后,内核已经具备读取和解析 ACPI 表的能力。电源状态初始化(C-states、P-states、T-states)依赖于以下 ACPI 表的正确加载:

ACPI 表 电源状态相关 初始化时机
DSDT 定义 _CST、_PCT、_PPC 等控制方法 acpi_subsystem_init()
SSDT 处理器和设备特定的电源状态覆盖 acpi_load_tables()
FACP 提供 SMI 命令端口、PM1a/PM1b 寄存器基址 acpi_tb_parse_fadt()
MADT 描述处理器拓扑,用于 C-state 协调 acpi_parse_madt()