三.MBR和各种bootloader阶段
这小节将介绍各种BR(boot record)和各种boot loader,但只是简单介绍其基本作用。
MBR是主引导记录,位于磁盘的第一个扇区,和分区无关,和操作系统无关,bios一定会读取MBR中的记录。
在MBR中存储了bootloader/分区表/BRID。bootloader占用446个字节,用于引导加载;分区表占用64个字节,每个主分区或扩展分区占用16个字节,如果16个字节中的第一个字节为0x80,则表示该分区为激活的分区(活动分区),且只允许有一个激活的分区;最后2个字节是BRID(boot record ID),它固定为0x55AA,用于标识该存储设备的MBR是否是合理有效的MBR,如果bios读取MBR发现最后两个字节不是0x55AA,就会读取下一个启动设备。
boot loader
MBR中的bootloader只占用446字节,所以可存储的代码有限,能加载引导的东西也有限,所以在磁盘的不同位置上设计了多种boot loader。下面将说明各种情况。
在创建文件系统时,是否还记得有些分区的第一个block是boot sector?这个启动扇区中也放了boot loader,大小也很有限。如果是主分区上的boot sector,则该段boot loader所在扇区称为VBR(volumn boot record),如果是逻辑分区上的boot sector,则该段boot loader所在扇区称为EBR(Extended boot sector)。但很不幸,这两种方式的boot loader都很少被使用上了,因为它们很不方便,加上后面出现了启动管理器(LILO和GRUB),它们就被遗忘了。但即使如此,在分区中还是存在boot sector。
分区表
硬盘分区的好处之一就是可以在不同的分区中安装不同的操作系统,但boot loader必须要知道每个操作系统具体是在哪个分区。
分区表的长度只有64个字节,里面又分成四项,每项16个字节。所以,一个硬盘最多只能分四个主分区。
每个主分区表项的16个字节,都由6个部分组成:
(1).第1个字节:只能为0或者0x80。0x80表示该主分区是激活分区,0表示非激活分区。单磁盘只能有一个主分区是激活的。
(2).第2-4个字节:主分区第一个扇区的物理位置(柱面、磁头、扇区号等等)。
(3).第5个字节:主分区类型。
(4).第6-8个字节:主分区最后一个扇区的物理位置。
(5).第9-12字节:该主分区第一个扇区的逻辑地址。
(6).第13-16字节:主分区的扇区总数。
最后的四个字节”主分区的扇区总数”,决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过2的32次方。如果每个扇区为512个字节,就意味着单个分区最大不超过2TB。
采用VBR/EBR方式引导操作系统
暂且先不讨论grub如何管理启动操作系统的,以VBR和EBR引导操作系统为例。
当bios读取到MBR中的boot loader后,会继续读取分区表。分两种情况:
(1)如果查找分区表时发现某个主分区表的第一个字节是0x80,也就是激活的分区,那么说明操作系统装在了该主分区,然后执行已载入的MBR中的boot loader代码,加载该激活主分区的VBR中的boot loader,至此,控制权就交给了VBR的boot loader了;
(2)如果操作系统不是装在主分区,那么肯定是装在逻辑分区中,所以查找完主分区表后会继续查找扩展分区表,直到找到EBR所在的分区,然后MBR中的boot loader将控制权交给该EBR的boot loader。
也就是说,如果一块硬盘上装了多个操作系统,那么boot loader会分布在多个地方,可能是VBR,也可能是EBR,但MBR是一定有的,这是被bios给”绑定”了的。在装LINUX操作系统时,其中有一个步骤就是询问你MBR装在哪里的,但这个MBR并非一定真的是MBR,可能是MBR,也可能是VBR,还可能是EBR,并且想要单磁盘多系统共存,则MBR一定不能被覆盖(此处不考虑grub)。
如下图,是我测试单磁盘装3个操作系统时的分区结构。其中/dev/sda{1,2,3}是第一个CentOS 6系统,/dev/sda{5,6,7}是第二个CentOS 7系统,/dev/sda{8,9,10}是第三个CentOS 6系统,每一个操作系统的分区序号从前向后都是/boot分区、根分区、swap分区。
再看下图,是装第三个操作系统时的询问boot loader安装位置的步骤。
装第一个操作系统时,boot loader可以装在/dev/sda上,也可以选择装在/dev/sda1上,这时装的是MBR和VBR,任选一个都会将另一个也装上,从第二个操作系统开始,装的是EBR而非MBR,且应该指定boot loader位置(如/dev/sda5和/dev/sda8),否则默认选项是装在/dev/sda上,但这会覆盖原有的MBR。
另外,在指定boot loader安装路径的下方,还有一个方框是操作系统列表,这就是操作系统菜单,其中可以指定默认的操作系统,这里的默认指的是MBR默认跳转到哪个VBR或EBR上。
所以,MBR/VBR和EBR之间的跳转关系如下图。
使用这种方式的菜单管理操作系统启动,无需什么stage1,stage1.5和stage2的概念,只要跳转到了分区上的VBR或EBR,那么直接就可以加载引导该分区上的操作系统。
但是,这种管理操作系统启动的菜单已经没有意义了,现在都是使用grub来管理,所以装第二个操作系统或第n个操作系统时不手动指定boot loader安装位置,覆盖掉MBR也无所谓,想要实现单磁盘多系统共存所需要做的,仅仅只是修改grub的配置文件而已。
使用grub管理引导菜单时,VBR/EBR就毫无用处了,具体的见下文。
四.grub阶段
使用grub管理启动,则MBR中的boot loader是由grub程序安装的,此外还会安装其他的boot loader。CentOS 6使用的是传统的grub,而CentOS 7使用的是grub2。
如果使用的是传统的grub,则安装的boot loader为stage1、stage1_5和stage2,如果使用的是grub2,则安装的是boot.img和core.img。传统grub和grub2的区别还是挺大的,所以下面分开解释,如果对于grub有不理解之处,见我的另一篇文章grub2详解。
使用grub2时的启动过程
grub2程序安装grub后,会在/boot/grub2/i386-pc/目录下生成boot.img和core.img文件,另外还有一些模块文件,其中包括文件系统类的模块。
[root@xuexi ~]# find /boot/grub2/i386-pc/
-name '*.img'
-o -name "*fs.mod"
-o -name "*ext[0-9].mod"
/boot/grub2/i386-pc/affs.mod
/boot/grub2/i386-pc/afs.mod
/boot/grub2/i386-pc/bfs.mod
/boot/grub2/i386-pc/btrfs.mod
/boot/grub2/i386-pc/cbfs.mod
/boot/grub2/i386-pc/ext2.mod # ext2、ext3和ext4都使用该模块
/boot/grub2/i386-pc/hfs.mod
/boot/grub2/i386-pc/jfs.mod
/boot/grub2/i386-pc/ntfs.mod
/boot/grub2/i386-pc/procfs.mod
/boot/grub2/i386-pc/reiserfs.mod
/boot/grub2/i386-pc/romfs.mod
/boot/grub2/i386-pc/sfs.mod
/boot/grub2/i386-pc/xfs.mod
/boot/grub2/i386-pc/zfs.mod
/boot/grub2/i386-pc/core.img
/boot/grub2/i386-pc/boot.img
其中boot.img就是安装在MBR中的boot loader。当然,它们的内容是不一样的,安装boot loader时,grub2-install会将boot.img转换为合适的代码写入MBR中的boot loader部分。
core.img是第二段Boot loader段,grub2-install会将core.img转换为合适的代码写入到紧跟在MBR后面的空间,这段空间是MBR之后、第一个分区之前的空闲空间,被称为MBR gap,这段空间最小31KB,但一般都会是1MB左右。
实际上,core.img是多个img文件的结合体。它们的关系如下图:
这张图解释了开机过程中grub2阶段的所有过程,boot.img段的boot loader只有一个作用,就是跳转到core.img对应的boot loader的第一个扇区,对于从硬盘启动的系统来说,该扇区是diskboot.img的内容,diskboot.img的作用是加载core.img中剩余的内容。
由于diskboot.img所在的位置是以硬编码的方式写入到boot.img中的,所以boot.img总能找到core.img中diskboot.img的位置并跳转到它身上,随后控制权交给diskboot.img。随后diskboot.img加载压缩后的kernel.img(注意,是grub的kernel不是操作系统的kernel)以初始化grub运行时的各种环境,控制权交给kernel.img。
但直到目前为止,core.img都还不识别/boot所在分区的文件系统,所以kernel.img初始化grub环境的过程就包括了加载模块,严格地说不是加载,因为在安装grub时,文件系统类的模块已经嵌入到了core.img中,例如ext类的文件系统模块ext2.mod。
加载了模块后,kernel.img就能识别/boot分区的文件系统,也就能找到grub的配置文件/boot/grub2/grub.cfg,有了grub.cfg就能显示启动菜单,我们就能自由的选择要启动的操作系统。
当选择某个菜单项后,kernel.img会根据grub.cfg中的配置加载对应的操作系统内核(/boot目录下vmlinuz开头的文件),并向操作系统内核传递启动时参数,包括根文件系统所在的分区,init ramdisk(即initrd或initramfs)的路径。例如下面是某个菜单项的配置:
代码语言:javascript复制menuentry 'CentOS 6' --unrestricted { search --no-floppy --fs-uuid --set=root f5d8939c-4a04-4f47-a1bc-1b8cbabc4d32 linux16 /vmlinuz-2.6.32-504.el6.x86_64 root=UUID=edb1bf15-9590-4195-aa11-6dac45c7f6f3 ro quiet initrd16 /initramfs-2.6.32-504.el6.x86_64.img}
加载完操作系统内核后grub2就将控制权交给操作系统内核。
总结下,从MBR开始后的过程是这样的: 1.执行MBR中的boot loader(即boot.img)跳转到diskboot.img。
2.执行diskboot.img,加载core.img剩余的部分,并跳转到kernel.img。
3.kernel.img读取/boot/grub2/grub2.cfg,并显示启动管理菜单。
4.选中某菜单后,kernel.img加载该菜单项配置的操作系统内核/boot/vmlinux-XXX,并传递内核启动参数,包括根文件系统所在分区和init ramdisk的路径。
5.控制权交给操作系统内核。
使用传统grub时的启动过程
传统grub对应的boot loader是stage1和stage2,从stage1跳转到stage2大多数情况下还会用到stage1_5对应的boot loader。
与grub2相比,stage1和boot.img的作用是类似的,都在MBR中。当该段boot loader执行后,它的目的是跳转到stage1_5的第一个扇区上,然后由该扇区的代码加载剩余的内容,并跳转到stage2的第一个扇区上。
stage1_5存在的理由是因为stage2功能较多,导致其文件体积较大(一般至少都有100多K),所以并没有像core.img一样嵌入到磁盘上,而是简单地将其放在了boot分区上,但stage1并不识别boot分区的文件系统类型,所以借助中间的辅助boot loader即stage1_5来跳转。
stage1_5的目的之一是识别文件系统,但文件系统的类型有很多,所以对应的stage1_5也有很多种。
代码语言:javascript复制[root@xuexi ~]# ls -C /boot/grub/*stage1_5*/boot/grub/e2fs_stage1_5 /boot/grub/jfs_stage1_5 /boot/grub/vstafs_stage1_5/boot/grub/fat_stage1_5 /boot/grub/minix_stage1_5 /boot/grub/xfs_stage1_5/boot/grub/ffs_stage1_5 /boot/grub/reiserfs_stage1_5/boot/grub/iso9660_stage1_5 /boot/grub/ufs2_stage1_5
虽然有很多种stage1_5,但每个boot分区也只能对应一种stage1_5。这个stage1_5对应的boot loader一般会被嵌入到MBR后、第一个分区前的中间那段空间(即MBR gap)。
当执行了stage1_5对应的boot loader后,stage1_5就能识别出boot所在的分区,并找到stage2文件的第一个扇区,然后跳转过去。
当控制权交给了stage2,stage2就能加载grub的配置文件/boot/grub/grub.conf并显示菜单并初始化grub的运行时环境,当选中操作系统后,stage2将和kernel.img一样加载操作系统内核,传递内核启动参数,并将控制权交给操作系统内核。
所以,stage1、stage1_5和stage2之间的关系如下图:
虽然绝大多数都提供了stage1_5,但它不是必须的,它的作用仅仅只是识别boot分区的文件系统类型,对于一个会编程的人来说,可以将固定boot分区的文件系统识别代码嵌入到stage1中,这样stage1自身就能识别boot分区,就不需要stage1_5了。
看看安装grub时,grub到底做了些什么工作。
grub> setup (hd0)
-
Checking
if
"/boot/grub/stage1" exists... yes
-
Checking
if
"/boot/grub/stage2" exists... yes
-
Checking
if
"/boot/grub/e2fs_stage1_5" exists... yes
-
Running
"embed /boot/grub/e2fs_stage1_5 (hd0)"...
15 sectors are embedded.
succeeded
-
Running
"install /boot/grub/stage1 (hd0) (hd0)1 15 p (hd0,0)/boot/grub/stage2 /boot/grub/menu.lst"... succeeded
Done.
首先检测各stage文件是否存在于/boot/grub目录下,随后嵌入stage1_5到磁盘上,该文件系统类型的stage1_5占用了15个扇区,最后安装stage1,并告知stage1 stage1_5的位置是第1到第15个扇区,之所以先嵌入stage1_5再嵌入stage1就是为了让stage1知道stage1_5的位置,最后还告知了stage1 stage2和配置文件menu.lst(它是grub.conf的软链接)的路径。