5.1.2 安装GRUB
通常,在安装操作系统的最后,操作系统安装程序将会为用户安装GRUB。当然,有时我们也会手动安装GRUB。但是都是通过GRUB提供的工具,执行的命令如下:
事实上,在这个安装命令的背后,GRUB的安装过程分为两个阶段:第一阶段是创建core.img,GRUB为此提供的工具是grub-mkimage;第二阶段是安装boot.img及core.img到硬盘,GRUB提供的工具是grub-setup。为了方便,GRUB将这两个过程封装到脚本grub-install中。
在创建core.img时,grub-mkimage需要获取需要加入core.img的模块,GRUB也提供了相应的工具grub-probe。grub-install利用这个工具根据内核映像所在的介质,自动探测所需要的模块,并将它们传给grub-mkimage。以笔者机器为例,使用grub-probe探测磁盘分区方式和文件系统的的方法如下:
1.创建映像
grub-install首先调用grub-mkimage创建core.img,我们结合其源代码来讨论core.img的创建过程。
根据上面的代码可见,创建core.img的主要过程如下:
1)generate_image读取kernel.img到内存,见代码第7~9行。
2)除了kernel.img外,还要将一些模块合并到core.img中。传递给函数generate_image的参数mods是一个数组,其中记录的是每个要合并到core.img中的模块。但是这些模块可能还依赖其他模块,所以代码第4~5行是检查这些模块的依赖模块,并将这些模块记录到链表path_list中。然后,第11~16行的代码将这些模块全部加载到kernel.img的后面。
3)至此,kernel.img和各个模块组成的core.img组装完成。为了在62个扇区中容纳下core.img,所以代码第18~19行压缩core.img。对于基于IA32架构的PC,GRUB使用的默认压缩方法是LZMA。
4)既然压缩了core.img,那么就得有人来负责解压缩。如同内核采用的方法,GRUB也在压缩的core.img前面附加了一段未经压缩的指令,见代码第21~37行。如果core.img使用的是LZMA压缩方法,则generate_image读取lzma_decompress.img,将其附加到core.img的前面。
5)如果core.img是为基于IA32的PC创建的,那么在core.img的最前面还要附加一个diskboot.img,代码第43~66就是准备diskboot.img。因为diskboot.img负责将core.img中除diskboot.img的部分读入内存,因此,diskboot.img需要知道core.img所在的扇区信息。因为这里已经确定了core.img占用的大小,所以,generate_image将core.img占用的扇区数写入了diskboot.img最后的blocklist中,这就是代码第53~59行的目的。准备好diskboot.img后,generate_image将其写入了在磁盘上保存core.img的文件,见代码第61~62行,其中out就是对应的文件core.img。
6)在将diskboot.img写入磁盘文件core.img后,generate_image将内存中的core.img的其他部分,包括lzma_decompress.img、kernel.img以及需要合并到core.img中的模块,也写入磁盘上的文件core.img中,紧接在diskboot.img之后。至此,core.img映像创建完毕。
2.安装映像
创建完core.img映像后,grub-install将调用grub-setup将core.img(包括boot.img)安装到硬盘。下面我们结合grub-setup的代码来讨论这一过程。
根据上述代码可见,安装GRUB的主要过程如下:
1)grub-setup首先读取boot.img和core.img到内存,见代码第4~6行。
2)boot.img的安装位置是固定的,即MBR,但是grub-setup需要确定core.img安装的扇区。对于不同的情况,确定的方法是不同的。代码第8~10行是针对多个磁盘组成的逻辑盘的情况。否则依次尝试使用具体分区方案以及文件系统提供的embed函数,见代码第11~16行。代表MBR分区方案的对象是msdos,其中提供了获取安装GRUB所在的扇区函数pc_partition_map_embed,该函数将计算core.img安装的扇区,并将结果保存到数组sectors中。变量nsec记录的是core.img占用扇区数。
3)在确定了core.img的安装扇区后,显然要将diskboot.img中的blocklist填充上,代码第18~20行就是在做这件事。
4)一旦确定了core.img安装的扇区,grub-setup还要修订boot.img。虽然对嵌入式安装而言,diskboot.img就安装在第2个扇区,但是GRUB不能进行这样的假设,因为还有可能使用非嵌入的安装方式。因此,grub-setup需要设置boot.img中diskboot.img所在的扇区,见代码第22行。其中所谓的first_sector就是core.img的第1个扇区,即diskboot.img占据的硬盘扇区。
5)映像准备好后,如果采用嵌入式安装,那么需要将core.img嵌入MBR后面的空闲扇区,即数组sectors中记录的扇区,见代码第24~27行。对于嵌入式安装,因为是嵌入在一块连续的扇区,所以diskboot.img中只需要记录一个blocklist,如图5-3所示。
图 5-3 嵌入式安装GRUB示意图
6)最后,grub-setup将boot.img写入MBR,见代码第29~30行。
为简单起见,上面代码中略去了非嵌入式安装的情况。在非嵌入式安装情况下,GRUB不需要将保存在文件系统中的core.img写入到MBR后面空闲的扇区中,而只需要将文件系统中的core.img所在的扇区写入disboot.img的blocklist,将diskboot.img所在的扇区写入boot.img即可。因为core.img很有可能不是连续存储在硬盘上的,所以diskboot.img中需要记录多个blocklist,这就是diskboot.img后面预留了多个blocklist空间的原因,如图5-4所示。
图 5-4 非嵌入式安装GRUB示意图