Linux GRUB2

boot loader 在计算机启动的时候决定如何运行(启动)安装于硬盘上的操作系统,GRand Unified Bootloader(GRUB)是 Linux 中最流行的 Bootloader 程序。目前主要有 2 个 GRUB 版本:

  • GRUB Legacy : 遗留版本 GRUB v1,在较旧的 Linux 发行版中使用
  • GRUB 2 : 当前最新版本,较新的 Linux 发行版中默认的 Bootloader. GRUB 2 的主要功能依旧是 查找并启动已安装在计算机上的操作系统 。同时 通过内嵌了一些工具和配置文件提供了更强大的功能以及灵活性

相比于 GRUB Legacy,GRUB 2 有以下优点:

  • 脚本支持 。支持脚本语言如 函数循环变量
  • 模块动态加载(Dynamic Module Loading)
  • 救援模式(Rescue Mode)
  • 自定义菜单(Custom Menus)
  • 主题(Thems)
  • 图形化的启动菜单
  • 直接从硬盘启动 LiveCD ISO
  • 全新的配置文件结构
  • Non-x86 架构支持,如 PowerPC
  • 全局 UUIDs 支持

GRUB 2 的配置文件是 /boot/grub/grub.cfg 或者 /boot/grub2/grub.cfg,关于其配置文件,要注意以下事项:

  • /boot/grub/grub.cfg 的内容是由工具 grub-mkconfig 或者 update-grub 根据 /etc/default/grub/etc/grub.d/ 中的内容自动生成,最好不要手动改动 /boot/grub/grub.cfg。要修改控制选项,可以修改 /etc/default/grub/etc/grub.d/*,然后使用 update-grub
  • /boot/grub/grub.cfg 的内容会因为 GRUB 2 的包升级(如内核升级导致新增内核或移除内核)而被覆盖,或者用户使用 update-grub 命令也会覆盖
  • 可用的启动内核列表(/boot/grub/grub.cfg 中的 menuentry)是由命令 update-grubupdate-grub2(一般是 grub 的软链接)自动生成
  • 用户可以在 OS 启动列表中自定义添加启动条目(menu entry),这个功能一般是通过修改 /etc/grub.d/40_custom 实现
  • 控制启动列表显示菜单的选项位于主配置文件 /etc/default/grub
  • 和 GRUB Legacy 不同,GRUB 2 分区(partitions)编号从 1 开始,而不是 0。但是 硬盘编号 依旧从 0 开始 。如系统上的第一个硬盘的第一个分区,GRUB Legacy 中是 hd(0, 0),GRUB 2 中是 hd(0, 1)
  • grub.cfg 中可以包含 Shell 脚本语法,如 函数,循环,变量等
  • 用于定位内核(Kernels)和 Initial RAM 位置的设备名称,最好是使用更加可靠的 标签(Labels) 或者是 UUIDs(Universally Unique Identifiers) ,而不是类似于 /dev/sda 的设备名称,这可以防止计算机系统新增硬盘后,/dev/sda 变成了 /dev/sdb 而导致系统启动时内核无法找到。
  • 配置文件更改后,只有执行了 update-grub 后才会最终生效

GRUB 2 配置流程

GRUB 2 的配置变更主要是通过修改主配置文件 /etc/default/grub 以及包含自定义脚本文件的目录 /etc/grub.d/,然后执行 update-grub 最终生效。update-grub 会从 /etc/default/grub/etc/grub.d/ 收集相关配置并将其更新到 /boot/grub/grub.cfg

启动菜单(Menu Display)的展示行为主要是通过 /etc/default/grub 进行控制

GRUB 2 常用控制选项说明

下表列出了 GRUB 2 Boot Menu 中常用的指令说明,更详细信息可以参考 info grub 或者 info grub2

Command Description Examples
linux file ... 从指定的文件中加载 Linux 内核镜像(Linux Kernel Image)
后面跟随的内容会被当作内核指令(Kernel Command)一字不差的传递给内核
linux /boot/vmlinuz-6.8.0-1016-aws root=PARTUUID=00c91e58-1b26-492f-a75f-c5159138dd1a ro console=tty1 console=ttyS0
initrd file [file ...] 仅用于 linux 指令之后。为 Linux Kernel Image 按顺序加载 initial RAM Disks,并在其中设置合适的参数 initrd /boot/microcode.cpio /boot/initrd.img-6.8.0-1016-aws

GRUB 2 相关命令

grub-install

检查 GRUB 版本 。以下示例中分别展示了 Ubuntu(使用命令 grub-install) 和 Centos (使用命令 grub2-install)上的 GRUB 2 版本

# cat /etc/os-release 
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.5 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy


# grub-install --version
grub-install (GRUB) 2.06-2ubuntu7.2

# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"

# grub2-install --version
grub2-install (GRUB) 2.02~beta2

grub-probe

grub-probe 主要用来检测 GRUB 2 支持的文件系统和设备信息,主要用于以下几个方面:

  • 检测指定路径所在的设备 。例如,使用 grub-probe /boot 可以显示与 /boot 路径相关的设备信息。
  • 识别文件系统类型 。帮助 GRUB 确定支持哪些文件系统。在执行引导配置时,GRUB 需要知道文件系统的类型,以便正确加载启动文件。
  • 调试和排查 。当 GRUB 出现引导问题时,grub-probe 可用于排查和验证 GRUB 是否正确识别了路径和设备。通过该命令,可以确认 GRUB 识别的设备与预期是否一致。

grub-probe 帮助信息请查看 man grub-probeinfo grub-probegrub-probe --help

检测指定的目录所在的设备

以下示例检测 /boot/root 所在的设备(硬盘),其输出表示 /boot//root/ 所在的文件系统在第一个 NVME 设备的第一个 Namespace

# grub-probe -t disk /boot/grub/
/dev/nvme0n1

# grub-probe -t disk /root/
/dev/nvme0n1

检测指定的目录所在的分区及其文件系统类型

以下示例检测 /boot/root 所在的分区和其文件系统类型

# grub-probe -t device /boot
/dev/nvme0n1p1

# grub-probe -t device /root
/dev/nvme0n1p1

# grub-probe -t fs /boot/grub/
ext2

# grub-probe -t fs /root
ext2

以上输出显示 /boot/root 都在硬盘(分区) /dev/nvme0n1p1 上。

重点需要关注其识别的文件系统 ,根据以上输出,它将 /boot/root 的文件系统类型检测为 ext2,使用 mount 命令检查 / 文件系统类型可知其为 ext4

# mount
/dev/nvme0n1p1 on / type ext4 (rw,relatime,discard,errors=remount-ro)

GRUB 通常在检测文件系统时,可能会将 ext4 识别为 ext2,因为 ext2ext3ext4 文件系统具有兼容性。GRUB 可能只读取文件系统的基本结构,因此即使是 ext4,它可能仍然识别为 ext2,因为它不需要特定于 ext4 的高级特性(例如日志记录)。在这种情况下,通常不会影响 GRUB 的正常引导。GRUB 在读取引导文件时,可以正确访问存储在 ext4 上的文件,即使它识别为 ext2。但如果引导失败或有文件系统不兼容性问题,可能需要进一步检查 GRUB 对 ext4 的支持情况

检测指定的目录所在的分区及其分区表类型

使用以下命令,可以检测给定的路径,其所在的分区,以及其分区表类型,是使用 GPT 分区表还是 MBR 分区表

# grub-probe -t drive /boot/grub/
(hostdisk//dev/nvme0n1,gpt1)

# grub-probe -t drive /home/
(hostdisk//dev/nvme1n1,msdos1)

检测指定的目录所在的分区的 Label 和 UUID

以下示例检测 /boot 所在文件系统对应的 Label 和 UUID,这在使用 Label 或 UUID 挂载文件系统时非常有用

# grub-probe -t fs_label /boot/grub/
cloudimg-rootfs

# grub-probe -t fs_uuid /boot/grub/
77eb4d39-dccf-478e-a49d-b2b32b53c1fd

常见问题处理

Kernel panic - not syncing: VFS: Unable to mount root fs on unknow-block(0,0)

环境信息

  • Centos 7 3.10.0-1160.11.1

AWS EC2 系统无法启动,通过 AWS 后台查看启动日志,输出信息如下图:

关键错误信息:

Kernel panic - not syncing: VFS: Unable to mount root fs on unknow-block(0,0)
CPU: 2 PID: 1 Comm: swapper/0 Not tainted 3.10.0-1160.11.1.el7.x86_64 #1

根据错误信息推测,可能是因为系统启动时无法加载内核或者初始内存镜像(initramfs)。为修复系统,将系统盘分离后挂载到其他正常的系统上,本示例挂载到 /data

/dev/nvme1n1p1 on /data type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

登陆正常的系统后, 执行 chroot 命令更改文件系统根目录到问题系统的文件系统中,这是关键步骤,否则以下所有操作都会以正常系统为目标进行操作,可能会导致正常系统异常

chroot /data/

执行命令 chroot /data/ 将文件系统根目录(/) 切换到 /data/ 后,后续执行的命令或目标文件都是以 /data//,比如执行 ls 不再是 /usr/bin/ls 而是 /data/usr/bin/ls,这样 后续操作的目标就变成了挂载的异常系统的文件系统

检查 grub2 配置文件:

/etc/grub2.cfg
#
# DO NOT EDIT THIS FILE
#
# It is automatically generated by grub2-mkconfig using templates
# from /etc/grub.d and settings from /etc/default/grub
#

### BEGIN /etc/grub.d/00_header ###
set pager=1

if [ -s $prefix/grubenv ]; then
load_env
fi
if [ "${next_entry}" ] ; then
set default="${next_entry}"
set next_entry=
save_env next_entry
set boot_once=true
else
set default="${saved_entry}"
fi

if [ x"${feature_menuentry_id}" = xy ]; then
menuentry_id_option="--id"
else
menuentry_id_option=""
fi

export menuentry_id_option

if [ "${prev_saved_entry}" ]; then
set saved_entry="${prev_saved_entry}"
save_env saved_entry
set prev_saved_entry=
save_env prev_saved_entry
set boot_once=true
fi

function savedefault {
if [ -z "${boot_once}" ]; then
saved_entry="${chosen}"
save_env saved_entry
fi
}

function load_video {
if [ x$feature_all_video_module = xy ]; then
insmod all_video
else
insmod efi_gop
insmod efi_uga
insmod ieee1275_fb
insmod vbe
insmod vga
insmod video_bochs
insmod video_cirrus
fi
}

serial --speed=115200
terminal_input serial console
terminal_output serial console
if [ x$feature_timeout_style = xy ] ; then
set timeout_style=menu
set timeout=1
# Fallback normal timeout code in case the timeout_style feature is
# unavailable.
else
set timeout=1
fi
### END /etc/grub.d/00_header ###

### BEGIN /etc/grub.d/00_tuned ###
set tuned_params=""
set tuned_initrd=""
### END /etc/grub.d/00_tuned ###

### BEGIN /etc/grub.d/01_users ###
if [ -f ${prefix}/user.cfg ]; then
source ${prefix}/user.cfg
if [ -n "${GRUB2_PASSWORD}" ]; then
set superusers="root"
export superusers
password_pbkdf2 root ${GRUB2_PASSWORD}
fi
fi
### END /etc/grub.d/01_users ###

### BEGIN /etc/grub.d/10_linux ###
menuentry 'CentOS Linux (3.10.0-1160.11.1.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-1062.12.1.el7.x86_64-advanced-388a99ed-9486-4a46-aeb6-06eaf6c47675' {
load_video
set gfxpayload=keep
insmod gzio
insmod part_msdos
insmod xfs
set root='hd0,msdos1'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint='hd0,msdos1' 388a99ed-9486-4a46-aeb6-06eaf6c47675
else
search --no-floppy --fs-uuid --set=root 388a99ed-9486-4a46-aeb6-06eaf6c47675
fi
linux16 /boot/vmlinuz-3.10.0-1160.11.1.el7.x86_64 root=UUID=388a99ed-9486-4a46-aeb6-06eaf6c47675 ro console=tty0 console=ttyS0,115200n8 crashkernel=auto console=ttyS0,115200 LANG=en_US.UTF-8
initrd16 /boot/initramfs-3.10.0-1160.11.1.el7.x86_64.img
}
menuentry 'CentOS Linux (3.10.0-1062.12.1.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-1062.12.1.el7.x86_64-advanced-388a99ed-9486-4a46-aeb6-06eaf6c47675' {
load_video
set gfxpayload=keep
insmod gzio
insmod part_msdos
insmod xfs
set root='hd0,msdos1'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint='hd0,msdos1' 388a99ed-9486-4a46-aeb6-06eaf6c47675
else
search --no-floppy --fs-uuid --set=root 388a99ed-9486-4a46-aeb6-06eaf6c47675
fi
linux16 /boot/vmlinuz-3.10.0-1062.12.1.el7.x86_64 root=UUID=388a99ed-9486-4a46-aeb6-06eaf6c47675 ro console=tty0 console=ttyS0,115200n8 crashkernel=auto console=ttyS0,115200 LANG=en_US.UTF-8
initrd16 /boot/initramfs-3.10.0-1062.12.1.el7.x86_64.img
}
menuentry 'CentOS Linux (0-rescue-3d5c05376530a2eb49e3e90576f83c5b) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-0-rescue-3d5c05376530a2eb49e3e90576f83c5b-advanced-388a99ed-9486-4a46-aeb6-06eaf6c47675' {
load_video
insmod gzio
insmod part_msdos
insmod xfs
set root='hd0,msdos1'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint='hd0,msdos1' 388a99ed-9486-4a46-aeb6-06eaf6c47675
else
search --no-floppy --fs-uuid --set=root 388a99ed-9486-4a46-aeb6-06eaf6c47675
fi
linux16 /boot/vmlinuz-0-rescue-3d5c05376530a2eb49e3e90576f83c5b root=UUID=388a99ed-9486-4a46-aeb6-06eaf6c47675 ro console=tty0 console=ttyS0,115200n8 crashkernel=auto console=ttyS0,115200
initrd16 /boot/initramfs-0-rescue-3d5c05376530a2eb49e3e90576f83c5b.img
}

### END /etc/grub.d/10_linux ###

### BEGIN /etc/grub.d/20_linux_xen ###
### END /etc/grub.d/20_linux_xen ###

### BEGIN /etc/grub.d/20_ppc_terminfo ###
### END /etc/grub.d/20_ppc_terminfo ###

### BEGIN /etc/grub.d/30_os-prober ###
### END /etc/grub.d/30_os-prober ###

### BEGIN /etc/grub.d/40_custom ###
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.
### END /etc/grub.d/40_custom ###

### BEGIN /etc/grub.d/41_custom ###
if [ -f ${config_directory}/custom.cfg ]; then
source ${config_directory}/custom.cfg
elif [ -z "${config_directory}" -a -f $prefix/custom.cfg ]; then
source $prefix/custom.cfg;
fi
### END /etc/grub.d/41_custom ###

从中可以看到,有 3 个 menuentry 可供选择,系统启动时默认选择第一个 CentOS Linux (3.10.0-1160.11.1.el7.x86_64) 7 (Core),加载的内核文件为 /boot/vmlinuz-3.10.0-1160.11.1.el7.x86_64,初始内存镜像(Initramfs)为 /boot/initramfs-3.10.0-1160.11.1.el7.x86_64.img,首先检查这 2 个文件是否存在

# ls /boot/vmlinuz-3.10.0-1160.11.1.el7.x86_64
/boot/vmlinuz-3.10.0-1160.11.1.el7.x86_6

# ls /boot/initramfs-3.10.0-1160.11.1.el7.x86_64.img
/usr/bin/ls.hide: cannot access /boot/initramfs-3.10.0-1160.11.1.el7.x86_64.img: No such file or directory

以上输出可以看到内核文件 /boot/vmlinuz-3.10.0-1160.11.1.el7.x86_6 存在,但是 初始内存镜像(Initramfs) /boot/initramfs-3.10.0-1160.11.1.el7.x86_64.img 不存在 ,由此基本可以确定此系统无法正常启动的原因为 初始内存镜像(Initramfs) /boot/initramfs-3.10.0-1160.11.1.el7.x86_64.img 不存在

检查第 2 个启动条目,其内核文件 /boot/vmlinuz-3.10.0-1062.12.1.el7.x86_64 和 初始内存镜像(Initramfs) /boot/initramfs-3.10.0-1062.12.1.el7.x86_64.img 皆存在

# ls /boot/vmlinuz-3.10.0-1062.12.1.el7.x86_64
/boot/vmlinuz-3.10.0-1062.12.1.el7.x86_64

# ls /boot/initramfs-3.10.0-1062.12.1.el7.x86_64.img
/boot/initramfs-3.10.0-1062.12.1.el7.x86_64.img

修改系统默认的启动项为第二个 ,重启系统后,启动正常

grub2-set-default 1