BootLoader之Grub2

在开机过程中 boot loader 是载入内核的重要工具,如果没有 boot loader ,那么 kernel 就无法被系统加载。

目前新版的 CentOS 7.x 已经将沿用多年的 grub 换成了 grub2,并且 grub2 版本在设定与安装上跟先前的 grub 有一定的差异。

boot loader 的两个 stage

开机流程中,在 BIOS 读完信息后,接下来会到第一个开机装置的 MBR 读取 boot loader 。这个 boot loader 可以具有菜单选择功能、直接加载内核文件以及控制权移交的功能等, 系统必须要有 loader 才能加载该操作系统的内核。

另外,MBR 是整个硬盘的第一个 sector 内的一个区块,整个大小为 446 bytes。即使是 GPT 也没有很大的扇区来储存 loader 的数据。 仅仅是程序代码和设定数据不可能只占据这么小的容量。

为了解决这个问题,所以 Linux 将 boot loader 的程序代码执行与设定值加载分成两个阶段 (stage) 来执行:

Stage 1:执行 boot loader 主程序:

第一阶段:执行 boot loader 的主程序,这个主程序必须要被安装在开机区,也就是 MBR 或者是 boot sector 。而因为 MBR 容量太小,所以 MBR 或 boot sector 通常仅安装 boot loader 的最小主程序, 并没有安装 loader 的相关配置文件;

Stage 2:主程序加载配置文件:

第二阶段:通过 boot loader 加载所有配置文件与相关的环境参数文件 (包括文件系统定义与主要配置文件 grub.cfg),一般情况下,配置文件都在 /boot 目录下。

与 grub2 有关的文件都存放在了 /boot/grub2 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[root@m5 ~]# ls /boot/grub2/ --format=single-column
device.map # <== grub2 的装置对应文件
fonts # <== 开机过程中的画面会使用到的字型数据
grub.cfg # <== grub2 的主配置文件
grubenv # <== 一些环境区块的符号
i386-pc # <== 针对一般 x86 PC 所需要的 grub2 的相关模块
locale # <== 字符集相关的数据
themes # <== 开机主题画面数据
[root@m5 locale]# ls --format=single-column /boot/grub2/i386-pc/
acpi.mod # <== 电源管理有关的模块
ata.mod # <== 磁盘有关的模块
chain.mod # <== 进行 loader 控制权转交的相关模块
command.lst # <== 一些命令相关的列表
efiemu.mod # <== 与 uefi BIOS 相关的模块
ext2.mod # <== EXT 文件系统家族相关模块
fat.mod # <== FAT 文件系统模块
file.mod
gcry_dsa.mod # <== 常见的加密模块
gcry_md4.mod
gcry_md5.mod
gcry_rsa.mod
gcry_sha1.mod
gcry_sha256.mod
gcry_sha512.mod
gcry_tiger.mod
iso9660.mod # <== 光盘文件系统模块
lvm.mod # <== LVM 文件系统模块
mdraid09.mod # <== 软件磁盘阵列模块
minix.mod # <== MINIX 相关文件系统模块
msdospart.mod # <== 一般 MBR 分区表
part_gpt.mod # <== GPT 分区表
part_msdos.mod # <== MBR 分区表
scsi.mod # <== SCSI 相关模块
usb_keyboard.mod # <== 底下两个为 USB 相关模块
usb.mod
usbms.mod
usbserial_common.mod
usbserial_ftdi.mod
usbserial_pl2303.mod
usbserial_usbdebug.mod
vga.mod # <== VGA 显示适配器相关模块
xfs.mod # <== XFS 文件系统模块

上面只列出了部分模块说明,loader 读取了这些文件系统定义数据后,就能够认识文件系统并读取在该文件系统内的内核文件了。

grub2 的配置文件 /boot/grub2/grub.cfg

grub2 的优点包括:

  • 识别与支持较多的文件系统,并且可以使用 grub2 的主程序直接在文件系统中搜索内核文件;

  • 开机的时候,可以自定义与修改开机设定条目,类似 bash 的命令模式;

  • 可以动态搜索配置文件,而不需要在修改配置文件后重新安装 grub2 。即:只要修改完 /boot/grub2/grub.cfg 中的设定,下次开机就会生效了。

这也是 Stage 1, Stage 2 分别安装在 MBR (主程序) 与文件系统当中 (配置文件) 的原因

磁盘与分区在 grub2 中的代号

安装在 MBR 的 grub2 主程序,最重要的任务之一是从磁盘当中加载内核文件, 以让内核能够顺利驱动整个系统的硬件。所以 grub2 必须要识别硬盘,而 grub2 对硬盘的代号设置与传统的 Linux 磁盘代号可完全不同的,它对硬盘的识别使用的是如下的代号:

1
2
3
(hd0,1)            # 一般的默认语法,由 grub2 自动判断分区格式
(hd0,msdos1) # 此磁盘的分区为传统的 MBR 模式
(hd0,gpt1) # 此磁盘的分区为 GPT 模式

要注意以下几点:

  • 硬盘代号以小括号 () 包起来;

  • 硬盘以 hd 表示,后面会接一组数字;

  • 以 搜索顺序 作为硬盘的编号;

  • 第一个搜索到的硬盘为 0 号,第二个为 1 号,以此类推;

  • 每块硬盘的第一个 partition 代号为 1 ,以此类推。

因此,第一块 搜索到的硬盘 代号为:(hd0),而这块硬盘的第一号分区为 (hd0,1)。另外,为了区分不同的分区格式,磁盘后面的分区号码可以使用类似 msdos1 与 gpt1 的方式来调整。 一定要注意:磁盘的号码是由 0 开始编号,分区的号码则与 Linux 一 样,是由 1 号开始编号。

由于 BIOS 可以调整磁盘的开机顺序,因此上述的磁盘对应的 (hdN) 中的 N 是可能会变动的。因此,整个硬盘代号为:

磁盘搜索顺序 在 Grub2 中的代号
第一块(MBR) (hd0) (hd0,msdos1) (hd0,msdos2) (hd0,msdos3) ……
第二块(GPT) (hd1) (hd1,gpt1) (hd1,gpt2) (hd1,gpt3) ……
第三块 (hd2) (hd2,1) (hd2,2) (hd2,3) ……

第一块硬盘的 MBR 安装处的硬盘代号是 (hd0), 第一块硬盘的第一个分区的 boot sector 代号就是 (hd0,msdos1),那么第一块硬盘的第一个逻辑分区的 boot sector 代号就是 (hd0,msdos5)

假设系统仅有一块 SATA 硬盘,那么该硬盘的第一个逻辑分区槽在 Linux 与 grub2 当中的档名与代号应该是 (hd0,msdos5)(hd0,5)

/boot/grub2/grub.cfg 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
[root@m5 ~]# cat /boot/grub2/grub.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
#

# ----------------------------------------------------------------------
# 开始是 /etc/grub.d/00_header 这个脚本执行的结果展示,主要与基础设定与环境有关
# ----------------------------------------------------------------------
### BEGIN /etc/grub.d/00_header ###
set pager=1

if [ -s $prefix/grubenv ]; then
load_env
fi

# ----------------------------------------------------------------------
# ...... 中间省略 ......
# ----------------------------------------------------------------------

# Fallback normal timeout code in case the timeout_style feature is
# unavailable.
else
set timeout=5
fi
### END /etc/grub.d/00_header ###

# ----------------------------------------------------------------------
# 开始执行 /etc/grub.d/10_linux,主要针对实际的 Linux 内核文件的开机环境
# ----------------------------------------------------------------------

### BEGIN /etc/grub.d/10_linux ###
menuentry 'CentOS Linux (3.10.0-957.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-957.el7.x86_64-advanced-7f64a74e-d812-4042-b4ff-14b7e4bc36ec' {
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-bios=hd0,msdos1 --hint-efi=hd0,msdos1 --hint-baremetal=ahci0,msdos1 --hint='hd0,msdos1' b9a77702-c231-4e5c-82d6-6b5111e783f9
else
search --no-floppy --fs-uuid --set=root b9a77702-c231-4e5c-82d6-6b5111e783f9
fi
linux16 /vmlinuz-3.10.0-957.el7.x86_64 root=UUID=7f64a74e-d812-4042-b4ff-14b7e4bc36ec ro crashkernel=auto biosdevname=0 net.ifnames=0 rhgb quiet LANG=en_US.UTF-8
initrd16 /initramfs-3.10.0-957.el7.x86_64.img
}

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

# ----------------------------------------------------------------------
# ...... 中间省略 ......
# ----------------------------------------------------------------------

### 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 ###

在文件最开始的部分,大多是环境设定与默认值设定等,更重要的是默认由哪个选项开机 (set default) 以及预设的秒数 (set timeout), 然后是每一个选择菜单的设定,也就是在 menuentry 这个设定值之后的配置。

在 menuentry 之后会有几个配置的规范,包括 --class, --unrestricted --id 等等的指定配置,之后通过 {} 将这个选择菜单会用到的数据包起来,在选择这个菜单之后就会进行括号内的动作。 如果真的选择了这个菜单,那么 grub2 首先会加载模块:

1
2
3
4
load_video
insmod gzio
insmod part_msdos
insmod xfs

之后就是三个比较重要的配置:

1
set root='hd0,msdos1'

使用 root 来指定 grub2 配置文件所在的硬盘位置和分区。比如,在安装系统的时候分区出 //boot 两个分区,而 grub2 是在 /boot/grub2 这个位置上, 这个位置的磁盘文件名为 /dev/vda2 ,因此完整的 grub2 磁盘名称就是 (hd0,2) 。如果系统用的是 GTP 的磁盘分区格式, 全名就是 (hd0,gpt2)

1
linux16 /vmlinuz-3.10.0-957.el7.x86_64 root=UUID=7f64a74e-d812-4042-b4ff-14b7e4bc36ec ro crashkernel=auto biosdevname=0 net.ifnames=0 rhgb quiet LANG=en_US.UTF-8

这个配置是 Linux 内核文件以及内核在执行时所下达的参数。这里的内核文件位置和上面的 root 有关:

  • 如果没有 /boot 分区,仅有 / 分区,文件路径会这样变化: /boot/vmlinuz-xxx => (/)/boot/vmlinuz-xxx => (hd0,msdos1)/boot/vmlinuz-xxx

  • 如果 /boot 是独立分区,则文件路径的变化会是这样:/boot/vmlinuz-xxx => (/boot)/vmlinuz-xxx => (hd0,msdos1)/vmlinuz-xxx

因此,在 linux16 后面接的文件路径要和上面的 root 搭配在一起,才是完整的绝对路径文件名。而 linux16 /vmlinuz-xxx root=/file/name 中的 root 指的是在 linux 文件系统中,根目录的位置。在开机流程中,内核会主动去挂载根目录,并且从根目录中读取配置 文件, 再进一步开始开机流程。 所以,内核文件后面一定要配置根目录的位置。

/etc/fstab 里面可以知道根目录的挂载可以是设备文件名、 UUID 与 LABEL 名称,因此这个 root 后面也是可以带入类似 root=UUID=1111.2222.33... 的模式。

1
initrd16 /initramfs-3.10.0-957.el7.x86_64.img

这一行配置的是 initramfs 所在的位置,与 linux16 vmlinuz-xxx 相同,这条配置也需要搭配 set root=xxx 才会得到正确的位置。

grub2 配置文件维护 /etc/default/grub 与 /etc/grub.d

grub2 的主配置文件 grub.cfg 因为该文件的内容太过复杂,数据量非常庞大,grub2 官方说明不建议手动修改。而应该要通过 /etc/default/grub 这个环境配置文件与 /etc/grub.d/ 目录内的相关配置文件来处理。

/etc/default/grub 环境配置文件

配置文件大致内容如下:

1
2
3
4
5
6
7
8
9
GRUB_TIMEOUT=5                    # 预设倒计时:经过多少秒之后进入开机
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
# 发行版本
GRUB_DEFAULT=saved # 预设开机菜单:指定预设由哪一个菜单来开机
GRUB_DISABLE_SUBMENU=true # 是否隐藏次选菜单,通常隐藏起来比较妥当
GRUB_TERMINAL_OUTPUT="console" # 指定数据输出的终端格式,默认是通过文字终端
GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet"
# 在 menuentry 括号内的 linux16 项目后续的内核参数
GRUB_DISABLE_RECOVERY="true" # 是否取消救援模式菜单
1
GRUB_TIMEOUT=5

这个选项用来配置倒计时的秒数,如果不想等待则输入 0 ,如果一定要使用者选择,则填 -1 即可。

1
GRUB_TIMEOUT_STYLE=menu

这个选项可选择的设定值有 menu, countdown, hidden 等。如果没有设定,预设是 menu 。 主要用于设置是否显示选择菜单。如果不想要让使用者看到选择菜单,则可以设定为 countdown。使用 countdown 会在屏幕上显示剩余的等待秒数, 而 hidden 则什么都不显示。

1
GRUB_TERMINAL_OUTPUT="console"

这个选项用来配置输出的画面应该使用哪一个终端来显示,主要的设定值有console, serial, gfxterm, vga_text 等。 除非有特别的需求,否则一般使用 console 即可。

1
GRUB_DEFAULT=saved

这个选项用来指定使用哪一个选择菜单 (menuentry) 来作为默认开机条目。能使用的设定值包括有 saved, 数字, title 名, ID名 等。 假设当前系统有三个 menuentry 的条目大约像这样:

1
2
3
4
menuentry 'CentOS Linux (4.13.2-1.el7.elrepo.x86_64) 7 (Core)' --class centos --class gnu-linux
menuentry 'CentOS Linux (3.10.0-693.21.1.el7.x86_64) 7 (Core)' --class centos --class gnu-linux
menuentry 'CentOS Linux (3.10.0-693.el7.x86_64) 7 (Core)' --class centos --class gnu-linux
menuentry 'CentOS Linux (0-rescue-9ee2ecae51814f0993302f3bf77c7b7c) 7 (Core)' --class centos --class gnu-linux

几个常见的设定值是这样的:

1
2
3
4
5
6
GRUB_DEFAULT=1
# 使用第二个 menuentry 开机,这里的数字编号是以 0 开始的
GRUB_DEFAULT='CentOS Linux (4.13.2-1.el7.elrepo.x86_64) 7 (Core)'
# 使用第一个 menuentry 开机,因为配置的是 title 名,它会自动检测到
GRUB_DEFAULT=saved
# 代表在使用 grub2-set-default 命令时设定哪一个 menuentry 为默认值,通常预设值为0
1
GRUB_CMDLINE_LINUX="crashkernel=auto rhgb quiet"

如果想要使内核在启动的时候入额外的参数,就可以使用这个选项来配置。比如对于云主机来说,并不关心网卡的物理位置,只想将网卡命名方式改为 eth0eth1 的格式,可以这样处理:

1
GRUB_CMDLINE_LINUX="...... net.ifnames=0 biosdevname=0"

这个环境配置文件编写完成之后,必须要使用 grub2-mkconfig 来重建 /boot/grub2/grub.cfg 才有作用。

1
grub2-mkconfig -o /boot/grub2/grub.cfg

选单建立的脚本 /etc/grub.d/*

在 grub2-mkconfig 执行时,会分析 /etc/grub.d/ 下的文件,然后执行各个脚本,最后生成 grub.cfg。该目录下会有这些文件存在:

  • 00_header:主要在建立初始的显示项目,包括需要加载的模块分析、屏幕终端的格式、倒数秒数、选单是否需要隐藏等,大部分在 /etc/default/grub 里所设定的变量,基本都会在这个脚本当中被利用来重建 grub.cfg 。

  • 10_linux:分析 /boot 下的文件,尝试找到正确的 linux 内核以及读取这个内核需要的文件系统模块与参数等,这一系列操作都在这个脚本运行时进行,并设定到 grub.cfg 当中。 因为这个脚本会将所有在 /boot 下的每一 个内核文件都对应到一个选单,因此核心文件数量越多,开机选单项目就越多。 如果不想要旧的核心出现在选单上,可以通过移除旧核心来处理。

  • 30_os-prober:这个脚本默认会到系统上找其他的 partition 里面可能含有的操作系统,然后将该操作系统做成选单。 如果不想要让其他的操作系统被侦测到并拿来开机,可以在 /etc/default/grub 里加上 GRUB_DISABLE_OS_PROBER=true 取消这个脚本的运行。

  • 40_custom:如果有其他需要自定义加上去选单条目,或者是其他需求,建议在这里补充。

有钱任性,请我吃包辣条
0%