BootLoader(引导程序)是系统启动之后第一个运行的程序。它的主要作用是加载操作系统并转入操作系统的入口,接下来的系统将由操作系统接管。随着技术发展,计算机系统的部署越来越复杂。从硬件角度,计算机引入了许多新的技术用以提供系统的性能,如SAS接口磁盘、NVMe接口磁盘、APIC等等,从软件角度,系统可能会安装在不同的文件系统上,甚至需要支持RAID、逻辑卷等底层的数据组织架构。复杂的环境对BootLoad有的设计带来了很大的挑战。BootLoad需要设计能够应对各种硬件的驱动程序,而且需要能够识别越来越复杂的数据存储结构。引导程序逐渐成为了一个在操作系统之前的操作系统。
Grub是一个可以被广泛应用的引导程序。他即能支持多种多样的硬件平台,也能够与包括Linux、windows在内的多种操作系统进行对接,它甚至可以能够引导安装在软RAID上的操作系统1。当采用Grub进行引导时,你即可以用他的图形界面选择启动项,也可以通过它提供的命令行在启动之前对启动项的各项进行编辑。而且,对于我们经常进行内核开发的场景来说,Grub提供的命令操作是非常有用的。
2002年始,Grub的维护者 Yoshinori K. Okuji开始重写Grub的相关程序,在架构上对其进行了重构,引入了模块化、基于Shell的配置文件生成机制、类脚本的编程的配置文件格式等新的设计思想,彻底的解决了Grub原有设计上的一些限制。业界将重构版的Grub称做Grub2,以区分原有的Grub。自2007年始,Grub2逐渐被各大发行版所接受,代替原有的引导程序。
与原有的grub相比,有很多新的不同点。在其用户手册中,主要总结了如下几点:
* 采用grub.cfg作为配置文件名字。采用新的配置文件语法
* grub.cfg可以通过grub-mkconfig进行自动生成。围绕grub-mkconfig,Grub2提供许多自动生成脚本,包括linux启动项自动搜索、适于Xen的配置项及内存检测配置项等。你可以在/etc/grub.d
中找到他们。
* 分区编号开始从1开始
* 配置文件支持脚本化编写,支持变量、条件、循环
* 提供环境块机制用以保存少量的启动过程信息(操作系统加载之前的)
* 能够支持多盘引导,可以通过磁盘标签、磁盘的UUID确定磁盘
* 支持更多的系统,包括PC BIOS、PC EFI等等
* 提供图形化的终端
* 支持更多的文件系统
* 支持LVM、RAID等机制
* 重新组织引导镜像,不再使用Stage 1, Stage 1.5, and Stage 2作为引导阶段
* 支持动态模块加载,可以根据需要加载不同的驱动
如果有兴趣或者必要,可以对上述的区别进行详累的分析。
假设我们编译一个新内核,以期在现有的系统进行引导,需要做的动作如下:
/boot
目录/usr/lib/modules
目录前两步由内核的编译程序可以帮你完成。后面一步是grub提供的程序完成的。这一步会重新检索/boot
目录,将识别到可引导的操作系统引导项加入到grub.cfg中。
第三步我们可以手动操作。从配置文件中复制一项以munuentry开头的命令项,将内核及initcramfs替换成新的项就可以。之所以可以这做手动操作,是因为,Grub启动引导时只与grub.cfg这一个配置文件有关。与/etc/grub.d
中的所有配置都是无关的。
前面已经提到,grub.cfg是脚本化的,语法类似于Shell的语法。但是要注意,它不是一个Shell脚本,我们不能使用在本Shell所能使用的指令去编写这个脚本(比如,做一些字符处理什么的)。脚本里面的命令均是Grub的内置命令,其执行机理与操作系统Shell无一点关系。在这个脚本执行时,我们的操作系统内核还没有引导起来。下面,我们摘一些段落进行分析。
字符串是组成脚本的基本语义单元。字符串在脚本中可以表示函数名、变量等。
保留字是一类特殊的字符串,在脚本中具有特别的意义,不能表示函数名、变量等。grub.cfg定义的一些保留字如下:
! [[ ]] { }
case do done elif else esac fi for function
if in menuentry select then time until while
和Shell脚本一样,grub.cfg用”$”表示一个变量。变量可放在大括号中,如 ${prefix}
,其主要目的是在与字符联接时能被完整的识别为一个变量。如表达式if [ -e ${prefix}/gfxblacklist.txt ]; then
中${prefix}
可以正确被识别为一个变量。一般的变量的名称是以字母开头的,后面可以有0个或者多个字母。数字作为变量名表示函数中的参数。如$1
表示传递给函数的第一个变量。变量$?
表示近一次调用函数的状态值。这个含义也与Shell中的含义相同。
其它预定义特殊的变量。总结如下表:
变量名称 | 含义 |
---|---|
$1 |
传到函数中的第一个变量,以此类推 |
$? |
近一次函数执行的状态值,可参考Shell中的状态值的含义 |
$@ |
所有的参数,每个参数用”“引起来 |
$* |
没加引号的所有参数 |
$# |
参数的个数 |
一行简单的命令就是一个表达式。他可以是一个单独的命令执行。第一个单词指定了要执行的命令。后面的字符串表示其执行所需要的参数。命令执行的返回值存放在状态值里面,在接下来最近语句中可以使用$?
获取。可以在命令执行前加上”!”,使其最后的值取反。
Grub配置很多引导过程所需要的命令。这些命令在脚本中的使用方式和惯例与Shell脚本非常相似。可以说,Grub配置文件本身就是一个小的程序解释器。
官方的手册将其所支持的命令分根据应用范围和作用,分了四类:
1. 与菜单相关的命令
Grub支持menuentry
和submenu
两个与启动项相关的命令。
menuentry
表示生成一个GRUB的选择项,一旦用户选择这个选项后,将会执行一系列指定的命令。
submenu
生成一个包括子菜单的菜单项。用户选定后,会弹出子菜单,子菜单的中的选项由menuentry
指定。
2. 通用命令
通用命令是可以放在配置文件任何位置命令。
3. 可以在命令行及菜单内部使用的命令
这类命令即可以在命令行也可以在菜单项内部执行。此类命令可以分成两类理解。一类是模访Shell用于判断和字符串处理的基本命。如[
、cat
、cmp
等。一类是Grub所使用的命令集。如acpi
、linux
、boot
等。
Grub提供了help
命令可查阅各个命令的具体使用。
4. 与网络相关指令
此类命令用于远程启动时对网络环境配置。如设置IP、设置DHCP等。
形式为:
function name { command; . . . }
表示定义了一个函数名为name
的函数;函数体中包含着这个函数所要执行的命令。其格式如简单命令所述。通常的写法是每一行一个命令,如果在同一行中,两个命令之间需要用分号隔开。函数定义不会改变$?
中的值。即在函数定义块之前和之后,$?
的值一定是相等。函数调用时,其状态值由最后一个执行的命令所决定。
以下为一个函数的示例:
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
}
可参考shell
中的写法,如下表:
表达式 | 含义 |
---|---|
for name in word . . .; do list; done |
对word中的每个字符串进行遍历 |
if list; then list; [elif list; then list;] . . . [else list;] fi |
条件判断 |
while cond; do list; done until cond; do list; done |
循环 |
menuentry title [‘--class=class’ . . .] [‘--users=users’] [‘--unrestricted’] [‘--hotkey=key’] [‘--id=id’] { command; . . . } |
定义一个启动项 |
为了简化引导程序的实现,Grub没有实现对文件写的逻辑。因此,在操作系统运行之前,我们不能像管理配置文件那样对一些需要保存的信息进行保存的。在一些场景下,我们需要保存一些用户在Grub操作中执行的一些状态信息。例如,保存本次用户选择的启动项,以便下次启动时以此为启动项。为此,Grub引入了环境块(environment block)来解决这个问题。在启动过程中,grub.cfg调用load_env命令将其载入到内存中,在运行过程中,使用save_env命令保存修改的信息。
在ubuntu系统上,环境块存储于/boot/grub/grubenv中。我们可以使用grub-editenv编辑这个文件。ubuntu系统默认情况下,对没有保存任保变量。例用上述命令增加一个变量:
[root@ubuntu grub]# grub-editenv /boot/grub/grubenv set foo=1
查看验证:
[root@ubuntu grub]# grub-editenv /boot/grub/grubenv list
foo=1
取消这个已经设置的变量:
[root@ubuntu grub]# grub-editenv /boot/grub/grubenv unset foo
[root@ubuntu grub]# grub-editenv /boot/grub/grubenv list
grub-mkconfig利用此信息定义记录上次启动信息以便下次利用。当将设置选项2 设GRUB_SAVEDEFAULT设置为true后,再次生成一次配置文件,启动时,就能够将启动时选择的启动项记录到环境块的saved_entry。为使下次执行仍使用上次启动的项,还需要将GRUB_SAVEDEFAULT设置成saved。
如果grub环境变量 GRUB_SAVEDEFAULT
设置为true时,表示:每当启动时,对启动项进行的调整,这个启动项将会保存起来。
Grub2运行时配置文件可以通过grub-mkconfig3进行生成。
[root@ubuntu grub]# grub-mkconfig -o /tmp/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.16.0-30-generic
Found initrd image: /boot/initrd.img-3.16.0-30-generic
Found memtest86+ image: /boot/memtest86+.elf
Found memtest86+ image: /boot/memtest86+.bin
done
该命令被调用时会检索/etc/grub.d目录下配置文件,按照文件排序进行调用执行,将配置文件的输出到标准输出的信息重定向到grub.cfg中。在该目录下的所有可执行的配置文件都会被调用,为确保调用顺序易理解,系统将该目录下的文件进行编号。编号的规则是:
* 所有的需要被生成命令识别的配置文件都以*‘编号’+‘-’+文件名的形式定义
* 编号00_*: 留给00_header,使其优先于所有的配置文件而被执行
* 编号10_*: 本系统的相关启动项生成脚本
* 编号20_*: 第三方应用的生成脚本
通过上述编码之后,每个配置文件名称前面的编号基本上决定了其解析顺序。这个顺与执行ls -l
所获得的文件的顺序是一致的。如:unbuntu系统中的配置文件列出如下:
[root@ubuntu grub.d]# ls -l
total 76
-rwxr-xr-x 1 root root 9791 Jul 16 22:59 00_header
-rwxr-xr-x 1 root root 6058 May 8 2014 05_debian_theme
-rwxr-xr-x 1 root root 11608 May 15 2014 10_linux
-rwxr-xr-x 1 root root 10412 May 15 2014 20_linux_xen
-rwxr-xr-x 1 root root 1992 Mar 12 2014 20_memtest86+
-rwxr-xr-x 1 root root 11692 May 15 2014 30_os-prober
-rwxr-xr-x 1 root root 1418 Jul 16 22:59 30_uefi-firmware
-rwxr-xr-x 1 root root 214 May 15 2014 40_custom
-rwxr-xr-x 1 root root 216 May 15 2014 41_custom
-rw-r--r-- 1 root root 483 May 15 2014 README
如果需要对Grub进行个性化定制,我们需要调整/etc/grub.d目录中的配置。