3、引导加载程序:GRUB2的配置与启动流程

系统上电后,BIOS/UEFI 把控制权交给了引导加载程序。在 Linux 世界里,GRUB2 是绝对的主角。说实话,我见过不少开发者把 GRUB2 当成一个黑盒子——装系统时自动配好,出问题时两眼一抹黑。我个人习惯是,必须搞懂它到底干了什么。

GRUB2 的全称是 GRand Unified Bootloader version 2。它负责三件事:加载内核、传递参数、交出控制权。听起来简单,但里面的门道不少。

核心要点:GRUB2 的启动流程分为三个阶段——boot.img → core.img → /boot/grub 下的配置和模块。每个阶段都有明确的职责边界。

3.1 GRUB2 的启动阶段拆解

GRUB2 不是一口气把所有代码都加载到内存的。它受限于 BIOS/UEFI 的初始环境,必须分步走。我把它拆成三个阶段来讲。

阶段一:boot.img(第一扇区)

BIOS/UEFI 读取磁盘的第一个扇区(MBR 或 GPT 保护分区),共 512 字节。这里面放的就是 boot.img。它小得可怜,唯一能干的事就是找到 core.img 的位置,然后跳转过去。

嗯,这里要注意:boot.img 本身不识别文件系统。它直接读取磁盘的绝对扇区号。所以 core.img 必须放在一个固定的、连续的磁盘区域里。这就是为什么你运行 grub-install 时,它会往 MBR 后面的扇区里写数据。

阶段二:core.img(核心映像)

core.img 是 GRUB2 真正的起点。它包含了文件系统驱动、磁盘驱动、以及一些基础模块。有了它,GRUB2 才能识别 /boot 分区上的文件系统,读取配置文件。

我记得有一次排查一个启动失败的问题,内核明明在 /boot 下,GRUB2 就是找不到。后来发现是 core.img 里缺少了对应文件系统的模块——当时用的是 Btrfs,而默认安装只带了 ext4 模块。踩过这个坑之后,我每次装系统都会检查 /boot/grub/ 下的模块列表。

阶段三:grub.cfg 与菜单

core.img 加载完成后,GRUB2 会去读取 /boot/grub/grub.cfg。这个文件是真正的配置中心。它定义了菜单项、内核路径、启动参数、以及各种钩子脚本。

你想想看,grub.cfg 其实不是手写的。它是通过 grub-mkconfig 命令自动生成的。但自动生成不代表你不能改——我经常在 /etc/default/grub 里加一些自定义参数,然后重新生成配置。

个人技巧:我习惯在 /etc/default/grub 里设置 GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" 之外,再加一个 GRUB_TIMEOUT=5 控制菜单等待时间。调试内核时,我会把 quiet 去掉,换成 verbose,这样启动日志会直接打印到屏幕上。

3.2 grub.cfg 配置文件深度解析

我们来看一个典型的 grub.cfg 片段。别被它的长度吓到,核心结构其实很清晰。

###
### BEGIN /etc/grub.d/00_header ###
set default="0"
set timeout=5
load_video
insmod gzio
insmod part_gpt
insmod ext2
set root='hd0,gpt2'
search --no-floppy --fs-uuid --set=root 1234-5678-90ab-cdef
### END /etc/grub.d/00_header ###

###
### BEGIN /etc/grub.d/10_linux ###
menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-1234-5678-90ab-cdef' {
    recordfail
    load_video
    gfxmode $linux_gfx_mode
    insmod gzio
    if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
    insmod part_gpt
    insmod ext2
    set root='hd0,gpt2'
    search --no-floppy --fs-uuid --set=root 1234-5678-90ab-cdef
    linux   /vmlinuz-5.15.0-91-generic root=UUID=1234-5678-90ab-cdef ro  quiet splash $vt_handoff
    initrd  /initrd.img-5.15.0-91-generic
}
### END /etc/grub.d/10_linux ###

这个文件由多个脚本片段拼接而成。每个 /etc/grub.d/ 下的脚本负责生成一部分内容。我挑几个关键点说说:

  • set root:指定根设备。注意,这里的 root 是 GRUB2 自己的根,不是 Linux 内核的根。它告诉 GRUB2 去哪里找内核和 initrd 文件。
  • search:通过 UUID 或标签查找分区。这是最可靠的方式,因为磁盘设备名(如 /dev/sda1)可能会变。
  • linux:指定内核文件路径和启动参数。root=UUID=... 是传给内核的参数,告诉内核根文件系统在哪。
  • initrd:指定初始 RAM 磁盘的路径。这个我们下一章会详细讲。

避坑指南:我曾经遇到过一个问题:更新内核后,grub.cfg 里出现了两个 menuentry,但默认启动的还是旧内核。原因是 /etc/default/grub 里的 GRUB_DEFAULT 设置成了 saved,而上次启动时选的是旧内核。解决方案是手动运行 grub-set-default 0 或者直接改成 GRUB_DEFAULT="Ubuntu" 按名称匹配。

3.3 GRUB2 的配置生成体系

grub.cfg 不是手写的,而是通过 grub-mkconfig 调用 /etc/grub.d/ 下的脚本生成的。这个设计很巧妙——每个脚本负责一个独立的功能模块。

脚本文件 功能 我常用的修改
00_header 设置全局变量、加载基础模块 调整 GRUB_TIMEOUT、GRUB_DEFAULT
10_linux 扫描 /boot 下的内核,生成菜单项 添加自定义内核参数
30_os-prober 探测其他操作系统(如 Windows) 我一般会禁用,避免误探测
40_custom 用户自定义菜单项 添加内存测试、救援模式等

为什么要有这个体系?说白了,是为了可维护性。你不需要直接编辑 grub.cfg,因为下次 update-grub 一跑,你的修改就没了。正确的做法是改 /etc/default/grub 或者 /etc/grub.d/ 下的脚本。

3.4 内核加载与参数传递

当你在 GRUB2 菜单里选中一个条目并按回车后,GRUB2 会执行 linux 和 initrd 命令。这两个命令干的事很关键:

  1. 加载内核:GRUB2 把 /boot/vmlinuz-* 文件读入内存。内核被加载到固定地址后,GRUB2 会解析内核的头部信息,确认格式正确。
  2. 设置启动参数:linux 命令后面的所有字符串都会被传递给内核。内核在启动时会解析这些参数,比如 root=、quiet、splash 等。
  3. 加载 initrd:initrd 命令把初始 RAM 磁盘加载到内存。内核启动后会把它挂载为临时的根文件系统。
  4. 跳转到内核入口:GRUB2 执行 boot 指令,把控制权交给内核。从此,GRUB2 的任务就结束了。

关键参数说明:

  • root=UUID=...:指定真正的根文件系统。内核在 initrd 阶段会挂载它。
  • ro:以只读方式挂载根文件系统。这是安全措施,防止启动过程中意外写入。
  • quiet:减少内核日志输出。调试时建议去掉。
  • splash:显示启动画面。如果显卡驱动有问题,这个参数会导致黑屏。

3.5 GRUB2 启动流程图

下面这张图把整个流程串起来了。你可以对照着看,每个阶段做了什么,数据流怎么走的。

GRUB2 启动流程 GRUB2 启动流程 boot.img MBR/GPT 第一扇区 core.img 文件系统驱动 + 模块 grub.cfg 菜单与配置 加载内核 (vmlinuz) 解析内核头部,设置参数 加载 initrd 初始 RAM 磁盘 boot 指令 → 跳转内核 控制权交给内核 关键说明 1. boot.img 不识别文件系统,只读绝对扇区 2. core.img 包含文件系统驱动,才能读 /boot 3. grub.cfg 由 /etc/grub.d/ 脚本自动生成 4. linux 命令传递内核参数,如 root=UUID=... 5. initrd 是临时根文件系统,用于挂载真正根 6. boot 指令执行后,GRUB2 使命完成 7. 内核接管后,开始真正的系统初始化

3.6 常见问题与排查思路

GRUB2 出问题的时候,系统连内核都加载不了。我总结了几种常见场景:

现象 可能原因 排查方法
GRUB2 菜单不出现,直接黑屏 GRUB_TIMEOUT 为 0,或显卡驱动问题 启动时按住 Shift 键强制显示菜单
选择菜单后报 "File not found" 内核或 initrd 路径错误,或分区 UUID 变了 进入 GRUB2 命令行,用 ls 和 cat 检查文件
启动后卡在 "Loading initial ramdisk" initrd 损坏或缺失 用 Live CD 启动,重新生成 initrd
GRUB2 安装失败,报 "grub-install: error" 磁盘分区表问题,或缺少 BIOS Boot Partition 检查 GPT 分区,确保有 bios_grub 标志

调试技巧:在 GRUB2 菜单界面按 c 键进入命令行模式。你可以手动执行 ls 列出所有分区,用 set root=(hd0,gpt2) 指定根设备,然后用 linux /vmlinuz-... root=/dev/sda2 手动启动内核。这是最直接的排查方式,比看日志快多了。

GRUB2 的配置和启动流程,说白了就是一套精心设计的接力赛。每个阶段只做自己该做的事,然后把接力棒交给下一棒。搞懂了这个流程,你就能在系统启动出问题时,快速定位是哪个环节掉了链子。