Bootloader-GRUB2

之前说过了grub的0.X版本, 而新版本是相当于重构了整个bootloader. 来学习一下.

grub2整体观

这些是和grub1的区别:

  • 配置文件的名称改变了。在grub中,配置文件为grub.conf或menu.lst(grub.conf的一个软链接),在grub2中改名为grub.cfg
  • grub2增添了许多语法,更接近于脚本语言了,例如支持变量、条件判断、循环
  • grub2中,设备名称从1开始,而在grub中是从0开始的
    • 举个例子, 当我们表示第一块硬盘的时候还是hd(0) 但是当说道分区的时候就变了, (hd0, msdos1)就表示第一块硬盘的第一个mbr分区, 而(hd0, gpt1)就表示第一块硬盘的第一个gpt分区.
  • grub2使用img文件,不再使用grub中的stage1、stage1.5和stage2。
  • 在已进入操作系统环境下,不再提供grub命令,也就是不能进入grub交互式界面,只有在开机时才能进入
  • 在grub2中取消了find命令.

引导方式

grub2有两种引导方式,但是第二种几乎不会用到,除非你需要引导grub2无法引导的操作系统.

这两种方式分别是:

  • 直接引导(direct): grub2直接通过默认的grub2 bootloader来引导写在默认配置文件中的操作系统
  • 链式引导(indirect): 使用默认的grub2 bootloader 来引导另一个bootloader而不是操作系统.

文件位置以及安装位置

当使用grub来管理启动菜单时,那么boot loader都是grub程序安装的。

传统的grub(legacy)将stage1转换后的内容安装到MBR(VBR或EBR)中的boot loader部分,将stage1_5转换后的内容安装在紧跟在MBR后的扇区中,将stage2转换后的内容安装在/boot分区中。

而grub2将boot.img转换后的内容安装到MBR(VBR或EBR)中的boot loader部分,将diskboot.img和kernel.img结合成为core.img(刚刚在上面说过img文件),同时还会嵌入一些模块或加载模块的代码到core.img中,然后将core.img转换后的内容安装到磁盘的指定位置处。

它们之间更具体的关系后面会有说.

根据分区表格式的不同, grub的安装位置(严格说是core.img的安装位置)也有所不同, 由于我的试验机器是MBR分区,所以就只说一下这个.

MBR允许四个主分区和额外的逻辑分区, 有两种方式安装GURB:

  1. 嵌入到MBR和第一个分区中间的空间,这部分就是大众所称的”boot track”,”MBR gap”或”embedding area”,它们大致需要31kB的空间;
  2. 将core.img安装到某个文件系统中,然后使用分区的第一个扇区(严格地说不是第一个扇区,而是第一个block)存储启动它的代码。

这两种方法有不同的问题。

使用嵌入的方式安装grub,就没有保留的空闲空间来保证安全性,例如有些专门的软件就是使用这段空间来实现许可限制的;另外分区的时候,虽然会在MBR和第一个分区中间留下空闲空间,但可能留下的空间会比这更小.

方法二安装grub到文件系统,但这样的grub是脆弱的 例如,文件系统的某些特性需要做尾部包装,甚至某些fsck检测,它们可能会移动这些block.

GRUB开发团队建议将GRUB嵌入到MBR和第一个分区之间,除非有特殊需求,但仍必须要保证第一个分区至少是从第31kB(第63个扇区)之后才开始创建的.

现在的磁盘设备,一般都会有分区边界对齐的性能优化提醒,所以第一个分区可能会自动从第1MB处开始创建的.

img文件

img文件是GRUB2的关键了, 也是他的核心. 我们来说一下这些文件的作用和GRUB legacy中的stage的对应关系.

grub2生成了好几个img文件,有些分布在/usr/lib/grub/i386-pc目录下,有些分布在/boot/grub2/i386-pc目录下,

img

事实上, 我们的core.img是动态生成的.而其他的img则存在在/usr/lib/grub/i386-pc/下. 在安装grub2的时候 boot.img会拷贝一份到/boot/grub2/i386-pc/目录下

imgs

上图描述了各个模块的关系和层级.

boot.img是grub启动的第一个img文件 我们先把他当成入口好了. 这个玩意存在于MBR中或者分区的boot sector里. 因为我们的boot sector大小就是512字节 所以我们的boot.img也正好是这个大小. boot.img的唯一作用就是读取core的第一扇区并跳转上去 接着移交控制权, 由于最大512所以boot.img是无法理解文件系统的. 因此这个core.img的路径是经过硬编码的, 确保boot.img能够找到core.img的位置.

core.img是给grub2-mkimage程序根据diskboot.img、kernel.img和一系列的模块动态创建的 core.img中嵌入了足够多的功能模块以保证grub能访问/boot/grub,并且可以加载相关的模块实现相关的功能,例如加载启动菜单、加载目标操作系统的信息等,由于grub2大量使用了动态功能模块,使得core.img体积变得足够小. core.img中包含了多个img文件的内容,包括diskboot.img/kernel.img等等.. 至于core.img的位置 我们在上面也说过啦.

根据启动环境的不同, core.img第一个扇区的内容是不同的.如果你的启动设备是磁盘, 那么第一个扇区就是diskboot.img, 如果是光盘,那么第一个扇区的内容就是cdboot.img, 作用和diskboot.img是一样的.

kernel.img文件包含了grub的基本运行时环境:设备框架, 文件句柄, 环境变量, 救援模式下的命令行解析器等等. 很少直接使用, 因为它们已经整个嵌入到了core.img中了. 注意, kernel.img是grub的kernel, 和操作系统的内核无关.

如果再多观察一下的话就会发现, kernel.img的大小是比core.img大的.这是因为生成的core中压缩了kernel.

除了这些***.img** 文件 各种*.mod其实就是各种功能模块, 部分模块已经嵌入到core.img中, 或者会被grub自动加载, 但有时也需要使用insmod命令手动加载.

还记得我们在说GRUB的时候, 简单的说了一下stage文件的作用. 现在我们把他们做一个简单的对应.

stage1在功能上就等于boot.img, stage1_5有点类似于core.img中的加载响应文件系统的代码, 但是core.img的功能显然比stage1 _5要多很多.

GRUB2的配置

我们已经知道了GRUB2中的磁盘代号的表示, 现在就来看一下grub.cfg这个重要的配置文件了. grub的官方并不期望用户直接修改这个文件, 而是通过修改其他的配置项接着在使用grub2-mkconfig来生成配置文件, 这个程序会参考/etc/default/grub作为模板, 这个文件中有很多宏.

1
2
3
4
5
6
7
8
[root@study ~]# cat /etc/default/grub
GRUB_TIMEOUT=5 # 指定预设的倒数秒数, 如果一定要用户来选择, 那么就写成-1即可
GRUB_DEFAULT=saved # 指定预设选单
GRUB_DISABLE_SUBMENU=true # 是否要隐藏次选单
GRUB_TERMINAL_OUTPUT="console" # 指定data的输出, 通常是终端控制台
GRUB_CMDLINE_LINUX="rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=auto rhgb quiet"
# 就是在 menuentry 括号内的 linux16 后面的核心参数
GRUB_DISABLE_RECOVERY="true" # 取消救援选单的制作

每次修改完这个模板文件, 为了使其生效, 我们执行:

1
2
3
4
5
6
7
8
9
10
[root@WWW ~]# vim /etc/default/grub 
[root@WWW ~]# grub2-mkconfig -o /boot/grub2/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.10.0-514.21.2.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-514.21.2.el7.x86_64.img
Found linux image: /boot/vmlinuz-3.10.0-514.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-514.el7.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-9a3f06751f26419c8e00e76f67d74f6b
Found initrd image: /boot/initramfs-0-rescue-9a3f06751f26419c8e00e76f67d74f6b.img
done

观察输出 这个程序能够主动的抓到对应的vmlinuz和initramfs. 这是怎么做到的呢?

这是因为grub2-mkconfig程序会去找/etc/grub.d/*里面的shell脚本, 所以这些脚本就显得尤为重要了我们来看一下(你会发现 这些脚本的名称在grub.cfg中就出现了):

1
2
3
4
5
6
7
8
9
10
11
12
[root@WWW grub.d]# ls -lh
total 72K
-rwxr-xr-x. 1 root root 8.5K Nov 22 2016 00_header
-rwxr-xr-x. 1 root root 992 Jun 16 2016 00_tuned
-rwxr-xr-x. 1 root root 232 Nov 22 2016 01_users
-rwxr-xr-x. 1 root root 11K Nov 22 2016 10_linux
-rwxr-xr-x. 1 root root 11K Nov 22 2016 20_linux_xen
-rwxr-xr-x. 1 root root 2.5K Nov 22 2016 20_ppc_terminfo
-rwxr-xr-x. 1 root root 11K Nov 22 2016 30_os-prober
-rwxr-xr-x. 1 root root 214 Nov 22 2016 40_custom
-rwxr-xr-x. 1 root root 216 Nov 22 2016 41_custom
-rw-r--r--. 1 root root 483 Nov 22 2016 README

首先是00_header, 我们在模板文件中所设定的大部分参数都体现在这个脚本中, 因为该脚本主要建立在初始的显示项目上, 比如需要的模块, 终端的格式, 倒计时, 是否隐藏菜单等等.

关于00_tuned, 其实也就只是设置了几个tune这个daemon的参数而已, 我们先忽略. 01 _users我没看懂 为什么是cat了一段shell?? (啊…我好像明白了, 这一段cat的内容会原封不动的跑到最终生成的配置文件里)

接着就是重要的10_linux了 这个脚本主要是为了制作菜单项, 它尝试分析/boot下的配置, 并且尝试找到对应的所需模块和参数.

后面的不想学了(摔!)

一般我们所需要用的就是40_custom了, 也就是手动加上菜单的时候才会动他. 至于怎么写, 从生成好的配置文件里模仿就好了..我不学了.

为菜单加上密码

grub2在用户管理上有一点像Linux, 有两种用户:

  • superusers 可以修改grub2下所有的项目的权限, 但是设定了这个参数之后 所有指令的修改将变成受限制的
  • users 可以设置多个, 就是简单的进入某个菜单, 搭配账号

我们来看一个grub.cfg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 第一个部分就是设定好管理员和用户的账号和密码

set superusers="root" # 设定系统管理员的账号
password root mimamima # 接下来就是设定账号和密码啦
password justin justinjustin # 沒有输入 superusers 的其他账号, 就判定成一般账号

menuentry "For Everyone!" --unrestricted {
set root=(hd0,1)
chainloader +1
}

menuentry "For 管理员 ONLY" --users "" {
set root=(hd0,2)
chainloader +1
}

menuentry "For 管理员 和 justin" --users justin {
set root=(hd0,3)
chainloader +1
}

但是问题是, 我们之前就说过了, 这个配置文件是不推荐进行修改的. 那么怎么让配置文件中出现这些内容呢?

在上文说的那些脚本文件中, 就出现了superusers这个关键词, 对了, 就是01_users这个文件, 我们如果要设置密码的话, 就对这个文件进行修改.

同grub-legacy一样, grub2也提供了密码工具, 叫做grub2-mkpasswd-pbkdf2 这是一个交互式的密码生成工具, 使用起来实在是太简便了.

1
2
3
4
[root@WWW grub.d]# grub2-mkpasswd-pbkdf2
Enter password:
Reenter password:
PBKDF2 hash of your password is grub.pbkdf2.sha512.10000.DEDC4DF8BD3FC85D670AA4D86C957432131B0824F81B47EAA2B1542CCD7E1AE32D136B606A2D5786FBC137C8632A417886EF48B82ED74E2E3DB386E22A890205.0DA123913F52203900B012E20FF57FA5DE0402814DAC5FF700ECC829139B2F8257F191147148BE97CC6EF87072DE32049CE70E1D43724D682EDC6972B8E67BE7

这个从grub2.pbkdf2开始的一大串字符串就是密码了.

这样, 我们直接修改01_users这个脚本, 具体的修改就像这样:

1
2
3
4
5
6
#!/bin/sh -e
cat << EOF
set superusers="justin"
password_pbkdf2 justin grub.pbkdf2.sha512.10000.27E9FC1D4955E2DB0542F7198D86A4103A525B20CC500577BD646EA48785D3EFB70CFA05BBF8B536E73AB4F144F4F9FC76040B745B1F3A0E2EE2F2DFEE5A4145.442DE0D08D00B21FE793DA96588D1144AF1597519921BC6125E35C13E345A8E738FF5D3A3EA90B94D023CA51148F5BEAF8C2A17C43081C5F371B33278D1BCDDE
EOF
# 也可以指定(多个)一般用户, 这里懒得弄了 hhhh 你打我啊

接着我们修改10_linux这个脚本, 在第一个CLASS的位置, 我们把原本在后面的–unrestricted的参数移除.

重启机器. 在开机的时候就会看到请求输入用户名和密码的界面了.