跳过正文
  1. 笔记/
  2. 系统底层/

Linux 启动流程:从固件、分区与 GRUB 到内核

·5931 字·12 分钟· loading · loading · · ·
ICE345
作者
ICE345
CS Student | System | Linux | OCaml
目录

这篇文章把几个经常被放在一起讨论、但实际上属于不同层次的概念串起来:固件、启动模式、分区方案、启动管理器、引导加载程序、ELF、Linux 内核和用户空间

先给出一条总路线。这里使用 Blowfish 的 timeline,因为启动过程是有明确先后顺序的阶段链:

  1. 硬件复位与平台固件

    1

    BIOS / UEFI

    CPU 从复位入口开始执行,固件初始化处理器、内存、总线和必要的启动设备。
  2. 固件选择启动项

    2

    Boot Manager

    Legacy BIOS 通常从磁盘首扇区开始执行;UEFI 通常根据启动项从 ESP 读取 EFI 程序。
  3. GRUB 或其他启动程序接管

    3

    Bootloader

    启动程序读取配置,显示菜单,并准备要启动的操作系统或内核。
  4. Linux 内核开始运行

    4

    Kernel + initramfs

    GRUB 把 Linux 内核、initramfs 和内核命令行交给内核启动协议,然后转移控制权。
  5. 内核初始化系统

    5

    Memory / Drivers / Root FS

    Linux 初始化内存、进程、驱动和文件系统,并通过 initramfs 找到和挂载真正的根文件系统。
  6. PID 1 与用户空间

    6

    systemd 或其他 init

    PID 1 启动服务、登录界面、桌面和普通应用程序,系统进入正常运行状态。

这条路线的关键是:**启动模式和分区方案是两条不同的轴;ELF 是文件格式;GRUB 是启动程序;内核是被启动的操作系统核心。**把它们混成同一个概念,就很容易产生“UEFI 一定等于 GPT”“.efi 一定是 ELF”“内核就是一个普通 ELF 程序”等误解。

1. 先区分四个概念
#

1.1 启动模式:Legacy BIOS 与 UEFI
#

启动模式描述的是:计算机通电后,主板固件用什么方式寻找并运行启动程序

  • Legacy BIOS 是传统 PC 的启动接口。它通常读取磁盘的第一个扇区,把其中的代码放到约定的内存位置并开始执行。
  • UEFI 是较新的固件接口。它可以访问 FAT 文件系统,读取 EFI 分区中的启动文件,并通过启动项选择要运行的 EFI 应用程序。

1.2 分区方案:MBR 与 GPT
#

分区方案描述的是:磁盘如何记录分区的布局

  • MBR 分区表使用传统的分区表结构,分区数量和磁盘地址范围有限。
  • GPT 使用 GUID 标识分区,并在磁盘头尾保存分区表信息和校验信息,适合现代磁盘。

MBR/GPT 本身不是启动模式,也不是启动加载程序。常见组合如下:

启动模式分区方案说明
Legacy BIOSMBR传统组合,BIOS 读取磁盘首扇区中的启动代码
UEFIGPT现代 PC 最常见组合,UEFI 从 ESP 读取 .efi 文件
Legacy BIOSGPT可以工作,但 GRUB 通常需要 BIOS Boot Partition
UEFIMBR某些平台和介质可以工作,但不是现代 Linux 安装的首选组合

因此,“UEFI + GPT”和“BIOS + MBR”是常见的搭配,不是绝对的一一对应关系。

1.3 启动管理器与引导加载程序
#

  • **启动管理器(boot manager)**负责选择启动哪个系统、哪个内核或哪个启动项。
  • **引导加载程序(bootloader)**负责把被选择的操作系统内核或下一级启动程序加载到内存,并把控制权交出去。

这两个角色经常由同一个程序承担。GRUB 就是典型例子:它可以显示菜单,因此充当启动管理器;它也可以加载 Linux 内核和 initramfs,因此充当引导加载程序。

UEFI 固件本身也具有启动管理功能:它可以根据 NVRAM 中的 BootOrderBoot#### 启动项选择一个 EFI 文件。它选择并运行的文件可能是 GRUB,也可能是 Windows Boot Manager、systemd-boot 或其他 EFI 应用程序。

2. ELF 到底是什么
#

ELF 是 Executable and Linkable Format 的缩写,即“可执行与可链接格式”。在 Linux/Unix 工具链中,ELF 可以表示:

  • 可执行文件;
  • 共享库,例如 libc.so
  • 可重定位目标文件,例如编译器生成的 .o
  • core dump;
  • 通常情况下的 Linux 内核模块 .ko

但 ELF 不是“所有 Linux 二进制文件的格式”。例如:

  • UEFI 的 .efi 启动程序通常是 PE/COFF 格式,不是 ELF;
  • Linux 的 vmlinuz 通常是带有 Linux 启动协议的压缩内核镜像,不应简单地等同于普通 ELF 可执行文件;
  • 固件、设备微码和各种裸机镜像可能使用完全不同的格式。

2.1 ELF 文件的整体结构
#

一个 ELF 文件通常包含:

ELF Header
  ├── Program Header Table(程序头表,可选但对可执行文件很重要)
  ├── Section Header Table(节头表,可选,链接和分析时常用)
  └── 各种代码、数据、字符串和元数据

ELF Header 中会记录一些基本信息,例如:

  • 32 位还是 64 位;
  • 目标机器架构,例如 x86-64、AArch64;
  • 文件类型,例如可执行文件、共享对象或可重定位文件;
  • 程序入口地址 e_entry
  • 程序头表和节头表的位置。

e_entry 是一个入口地址,但它不等于“源代码第一行”,也不保证是文件中最先出现的机器指令。对于动态链接程序,入口还会经过运行时启动代码和动态加载器。

2.2 Section 与 Segment 不是一回事
#

这是理解 ELF 时最容易混淆的一点。

**Section(节)**主要服务于编译、链接和调试,例如:

  • .text:机器指令;
  • .rodata:只读数据和字符串常量;
  • .data:已经初始化的可写数据;
  • .bss:未初始化或初始化为零的数据,通常不需要在文件中保存完整内容;
  • .symtab:符号表,可能在发布版中被删除;
  • .debug_*:调试信息,通常不会保留在精简的发布版程序中。

**Segment(段)**主要服务于装载。操作系统装载 ELF 时,重点读取 Program Header Table 中的 PT_LOAD 等条目,把适合的文件区域映射到进程的虚拟地址空间,并设置读、写、执行权限。

所以更准确的说法是:

链接器把多个 Section 组织进若干 Segment;运行时加载器主要按照 Segment 装载程序,而不是逐个把 .text.data 当作独立文件块复制进内存。

可以使用下面的命令观察二者:

readelf -h ./program       # ELF 总体信息
readelf -S ./program       # Section 列表
readelf -l ./program       # Program Header 和 Segment
readelf -d ./program       # 动态链接信息
file ./program             # 粗略识别文件类型

2.3 ELF 程序是如何被 Linux 启动的
#

以动态链接的普通程序为例:

shell 执行 ./program
execve() 请求内核替换当前进程映像
内核识别 ELF Header 和 Program Header
内核映射可加载的 PT_LOAD 段
根据 PT_INTERP 找到动态加载器(如果存在)
动态加载器加载共享库并完成重定位
进入程序入口和 C 运行时启动代码
调用 main()

这里的“映射”不一定意味着把整个文件一次性复制到 RAM。内核可以使用文件映射、按需调页和页缓存,让程序在真正访问页面时再读取数据。

2.4 .S 汇编文件与 ELF 的关系
#

.S 是汇编源文件的一种常见后缀。大写 .S 通常表示它会先经过 C 预处理器;小写 .s 通常直接交给汇编器。它们都只是源代码,不是 ELF 文件。

典型编译链是:

boot.S / head.S
      ↓ 预处理器 + 汇编器
foo.o(通常是 ELF 可重定位目标文件)
      ↓ 链接器和链接脚本
可执行文件、内核镜像或共享库

汇编源代码中的:

.section .text
.global _start
_start:
    nop

.section .data
value:
    .long 0x1234

是在告诉汇编器如何生成输入 Section。之后链接器还会通过链接脚本决定这些 Section 在输出文件中的布局、虚拟地址和 Segment 归属。.S 中的 .text 与最终 ELF 中的 .text 有关系,但二者不是“同一个文件结构的文字版和二进制版”。

3. UEFI 模式下的启动流程
#

下面以现代 x86-64 Linux、UEFI + GPT 为例。不同架构可能使用不同的内核启动协议,但整体层次相似。

3.1 CPU 复位,固件开始运行
#

通电或复位后,CPU 从架构规定的复位入口开始执行主板固件。固件完成基本硬件初始化,并执行 POST,例如检查内存、处理器、显示设备和存储设备。

UEFI 不是一个完整的桌面操作系统。它是一个在操作系统之前运行的固件环境,提供设备访问、文件系统访问、启动服务和运行 EFI 程序所需的接口。

3.2 UEFI 启动管理器选择启动项
#

UEFI 通常根据固件变量中的启动项选择程序。启动项可能指向:

\EFI\Microsoft\Boot\bootmgfw.efi
\EFI\ubuntu\grubx64.efi
\EFI\Linux\linuxx64.efi

如果启动项丢失,某些场景下 UEFI 还会尝试可移动介质的默认路径,例如 x86-64 上常见的:

\EFI\BOOT\BOOTX64.EFI

3.3 UEFI 读取 ESP 中的 EFI 程序
#

ESP 是 EFI System Partition 的缩写。它通常使用 FAT 文件系统,用于存放 EFI 启动程序及其相关文件。ESP 是一个分区,不等于整个 /boot,也不等于 GPT 本身。

一个重要纠正是:

.efi 文件通常是 UEFI 使用的 PE/COFF 镜像,而不是 ELF。

UEFI 规范定义了 PE32/PE32+ 形式的 EFI 镜像。GRUB 的 grubx64.efi 因此应当理解为一个 UEFI 可执行程序;它不是“加了 EFI 后缀的 ELF”。

3.4 GRUB 接管控制权
#

UEFI 运行 grubx64.efi 后,GRUB 可能继续读取:

  • grub.cfg
  • GRUB 模块;
  • 字体、主题和本地化文件;
  • /boot 中的 Linux 内核和 initramfs。

如果安装了多个系统或内核,GRUB 可以显示菜单。用户选择某个菜单项后,GRUB 通常会准备:

Linux 内核镜像
initramfs
内核命令行(root=、quiet 以及其他硬件参数等)

这里的 initramfs 很重要。它是一个在真正根文件系统挂载前使用的临时根文件系统,常用于加载存储驱动、解锁 LUKS、组合 RAID/LVM、识别设备以及找到真正的根分区。

4. Legacy BIOS 模式下的启动流程
#

下面以 BIOS + MBR 为例。

4.1 BIOS 读取磁盘第一个扇区
#

BIOS 完成 POST 和设备选择后,会读取启动磁盘的第一个扇区。经典 BIOS 启动约定通常是:

  • 将扇区读到物理地址 0x7C00 附近;
  • 检查末尾的启动签名 0x55AA
  • 把 CPU 控制权交给该扇区中的启动代码。

4.2 MBR 的 512 字节结构
#

传统 MBR 启动扇区通常可概括为:

偏移 0      - 445:启动代码区域,常说的 446 字节
偏移 446    - 509:4 个 16 字节分区表项,共 64 字节
偏移 510    - 511:启动签名 0x55AA,共 2 字节

这里要注意三件事:

  1. MBR 既可以指整个磁盘首扇区,也可以在上下文中指其中的分区表或启动代码。
  2. 分区表只是描述磁盘布局,不会自己加载操作系统。
  3. “MBR 中没有启动代码”只意味着无法通过传统 BIOS 从这个磁盘继续启动,不代表 UEFI 模式下所有启动方式都失效。

4.3 GRUB 在 BIOS 模式中的嵌入
#

传统教学材料经常把 GRUB 描述成 Stage 1、Stage 1.5 和 Stage 2:

boot.img       → 磁盘启动扇区中的最小代码
core.img       → 嵌入区域中的核心镜像
模块和配置     → 文件系统中的 /boot/grub 等目录

“Stage 1.5”有助于理解历史流程,但它不是所有 GRUB 版本和所有启动方式都存在的固定文件。现代 GRUB 2 文档更常使用 boot.imgcore.img、模块和配置文件这些术语。

在 MBR 磁盘上,GRUB 的核心镜像通常尝试嵌入第一个分区之前的空间。这个空间经常被称为 MBR gap 或 embedding area,但它不是一个正式的文件系统分区,也不保证永远安全可用。现代分区工具通常会把第一个分区从约 1 MiB 处开始,以便对齐和给引导程序留下空间;GRUB 官方也建议预留足够的嵌入空间。

5. GPT、ESP 与 BIOS Boot Partition
#

5.1 GPT 的基本布局
#

GPT 磁盘通常包含:

LBA 0:Protective MBR(保护性 MBR)
LBA 1:主 GPT Header
后续:主 GPT 分区条目数组
中间:实际分区区域
磁盘末尾:备份分区条目数组和备份 GPT Header

保护性 MBR 的主要目的,是避免只理解 MBR 的旧工具把 GPT 磁盘误认为“没有分区的空磁盘”并覆盖它。它不等于一套完整的 BIOS 启动代码。

GPT 的主表和备份表带有校验信息。磁盘头部的 GPT Header 会记录分区条目数组的位置、数量和大小;备份 GPT Header 位于磁盘末尾。

5.2 GPT + UEFI
#

这是现代 PC 上最常见的组合:

UEFI
GPT 磁盘上的 ESP
ESP 中的 grubx64.efi 或其他 .efi 程序
内核、initramfs、用户空间

GPT 不直接“存放 GRUB”。更准确地说:GPT 提供分区布局,ESP 是其中一个使用 FAT 文件系统的分区,EFI 启动程序作为文件保存在 ESP 中。

5.3 GPT + BIOS
#

GRUB 和 Linux 也可以在 GPT 磁盘上使用传统 BIOS 模式启动。由于 GPT 布局不应依赖一个随意的 MBR 后间隙,通常会创建一个特殊的 BIOS Boot Partition

  • 不需要文件系统;
  • 不用于存放普通用户文件;
  • 分区类型需要标记为 BIOS boot;
  • 用于嵌入 GRUB 的核心镜像。

这个分区不是 ESP,也不是 /boot。它只服务于 BIOS 模式下的 GRUB 嵌入。

6. Linux 内核到底在哪里接手
#

当 GRUB 准备好内核、initramfs 和命令行后,它会根据具体架构的启动协议跳转到内核入口。

以 x86 Linux 为例,/boot/vmlinuz-* 通常是经过压缩、带有启动协议结构的内核镜像;Linux 构建目录中的 vmlinux 通常是更接近链接结果的 ELF 文件。二者不能简单地互换:

vmlinux       → 构建出的、常见为 ELF 的内核文件
vmlinuz       → 安装到 /boot、可由启动程序使用的压缩/启动镜像
initramfs     → 启动早期的临时根文件系统

内核启动后,大致会完成以下工作:

  1. 建立内存管理和异常处理机制;
  2. 初始化调度器、时间系统和中断;
  3. 初始化内核子系统和设备模型;
  4. 初始化存储控制器、文件系统和网络等驱动;
  5. 解包或使用 initramfs;
  6. 找到并挂载真正的根文件系统;
  7. 通过 switch_root 或类似机制切换离开 initramfs;
  8. 启动 PID 1。

Linux 内核并不是“把所有硬件都直接交给固件管理”。固件和内核分工如下:

  • 固件在操作系统之前初始化平台,并提供启动环境;
  • 内核驱动负责运行时以操作系统的方式使用硬件;
  • 某些设备还需要额外的设备固件,驱动会从系统中的固件目录加载它并传给设备;
  • Linux 的设备固件目录常见为 /lib/firmware,在使用 usr-merge 的发行版中也可能位于 /usr/lib/firmware

6.1 用户空间启动
#

PID 1 可以是 systemd、OpenRC、BusyBox init 或其他 init 实现。以 systemd 为例,它会继续:

  • 启动基础服务;
  • 挂载其他文件系统;
  • 初始化网络;
  • 启动日志、时间同步和设备管理服务;
  • 启动显示管理器或文本登录服务。

到这里才进入我们通常理解的“登录界面和桌面”。因此,“启动完成”不是内核刚跳转时,而是用户空间逐步建立之后。

7. 启动链中的角色关系
#

可以用下面的关系理解:

UEFI 固件的 Boot Manager
        │ 选择一个 EFI 应用程序
grubx64.efi / systemd-boot / Windows Boot Manager
        │ 选择并加载内核或另一个启动程序
Linux 内核 + initramfs
        │ 初始化操作系统
PID 1 和用户空间

对于 BIOS:

BIOS
  │ 读取并执行磁盘首扇区
MBR 中的 boot.img
  │ 加载 core.img
GRUB 核心、模块和配置
  │ 加载内核与 initramfs
Linux 内核

不要把这些角色强行理解为永远固定的“固件 → boot manager → bootloader → 内核”四个独立程序。实际情况可能是:

  • UEFI 固件先选择 GRUB,而 GRUB 同时承担启动管理和内核加载;
  • UEFI 固件直接选择 Linux 内核加载器,根本没有 GRUB;
  • GRUB 通过 chainloading 把控制权交给 Windows Boot Manager;
  • 一个启动管理器可以选择另一个启动程序,而不是直接加载内核。

8. 在正在运行的 Linux 中检查启动状态
#

8.1 判断当前是 UEFI 还是 Legacy BIOS
#

if [ -d /sys/firmware/efi ]; then
    echo UEFI
else
    echo Legacy-BIOS-or-unknown
fi

这个判断应该在已经运行的 Linux 中执行。/sys/firmware/efi 通常表示内核是从 UEFI 路径启动的。

8.2 查看分区方案和文件系统
#

lsblk -o NAME,SIZE,TYPE,FSTYPE,PARTTYPE,PARTLABEL,MOUNTPOINTS
sudo fdisk -l
sudo gdisk -l /dev/nvme0n1

你可以重点观察:

  • 是否存在 vfat 的 ESP;
  • 是否存在 BIOS Boot Partition;
  • 根文件系统、/boot 和 ESP 分别位于什么分区;
  • 磁盘是 GPT 还是 DOS/MBR 分区表。

8.3 查看 UEFI 启动项
#

sudo efibootmgr -v

8.4 查看内核、initramfs 和命令行
#

ls -lh /boot/vmlinuz* /boot/initramfs* 2>/dev/null
cat /proc/cmdline
ps -p 1 -o pid,comm,args

8.5 观察 ELF
#

readelf -h /bin/ls
readelf -l /bin/ls
readelf -S /bin/ls

这三个命令分别帮助你观察 ELF Header、Program Header/Segment 和 Section。

9. 几个需要纠正的常见说法
#

容易误解的说法更准确的说法
UEFI 就是一个小型操作系统UEFI 是固件接口和运行环境,不等同于完整操作系统
UEFI 一定使用 GPTUEFI 常和 GPT 搭配,但启动模式和分区方案是不同概念
GPT 没有 MBRGPT 通常包含用于兼容和保护的 Protective MBR,但它不等于传统 MBR 启动方案
GPT 不能用 BIOS 启动GPT 可以配合 BIOS 启动,GRUB 通常需要 BIOS Boot Partition
.efi 是 ELF 加上后缀UEFI EFI 镜像通常是 PE/COFF 格式
ELF 的 .text.data 就是加载器直接逐个复制的内存段ELF Section 和 Segment 不同,运行时主要按照 Program Header 中的 Segment 装载
Linux 内核就是普通 ELF 程序vmlinux 常见为 ELF;vmlinuz 是可由启动协议使用的内核镜像,启动方式更特殊
Stage 1.5 永远是 GRUB 的一个文件这是对传统 GRUB 启动过程的教学化称呼,现代 GRUB 2 更适合用 boot.imgcore.img 和模块描述
MBR 中没有代码就所有系统都无法启动只有依赖传统 BIOS 从该磁盘启动的路径会失败,UEFI 路径可以使用 ESP
固件和驱动程序是一回事固件通常运行在设备或平台中;驱动是内核用来控制设备的软件接口

参考资料
#

相关文章


 MBR 分区方案 GRUB 

评论