[Linux]Grub和initrd对文件系统驱动的支持
前言
本文源于这样一个问题:内核镜像存储在硬盘下特定文件系统中,bootloader必须先加载文件系统驱动,才能读取内核文件。类似的,内核要读取根文件系统中的文件,也需要先加载文件系统驱动。如果文件系统驱动存储在硬盘中,启动时没有驱动无法读硬盘,将导致整个启动过程的失败。那么Grub和内核如何解决这个问题的呢?
启动过程中需要读硬盘的时刻:
1. BIOS读硬盘中MBR,将控制权交给MBR中Grub Stage1
2. Stage 1读硬盘中的Stage 1.5,Stage 1.5读Stage 2;或者Stage 1 直接读取 Stage 2
3. Stage 2读Kernel
4. Kernel挂载根文件系统,读文件
分析
BIOS硬盘寻址
BIOS主要支持传统的CHS寻址模式 (528M),和28bit LBA模式(137G),以及48bit LBA模式(144,000TB)。
CHS普通模式是最早的 IDE 方式,在硬盘访问时,BIOS 和 IDE 控制器对参数不做任何转换。该模式支持的最大柱面数为 1024,最大磁头数为 16,最大扇区数为 63,每扇区字节数为 512,因此支持最大硬盘的容量为:512x63x16x1024=528MB。
LBA(Logical Block Addressing)逻辑块寻址模式。在 LBA 模式下,设置的柱面、磁头、扇区等参数并不是实际硬盘的物理参数。在访问硬盘时,由 IDE 控制器把由柱面、磁头、扇区等参数确定的逻辑地址转换为实际硬盘的物理地址。目前所有容量超过137G的IDE/ATA硬盘,使用的都是48位LBA寻址方式。
Grub Stage 文件机制
由于MBR大小有512字节,且分区表占用64字节,MBR签名(0xAA55)占两字节,可供Grub使用的仅446字节。因此Grub采用了Stage 机制,分步加载整个系统。其中,各文件功能如下:
Stage 1:MBR前 446字节存放的是 stage1,其功能仅仅是将硬盘0头0道2扇区读入内存。0头0道2扇区内容是源代码中的 /stage2/start.S,编译后512字节,它是stage2或者stage1_5的入口。此时Stage1并不能识别文件系统的,它是利用BIOS INIT13中断,将硬盘0头0道2扇区内容载入内存的。定位0头0道2扇区有两种寻址方式:LBA、CHS。
Start.S: start.S的主要功能是将 stage2 或 stage1_5 从硬盘载入内存。注意这里的Stage2 或者Stage1_5不是/boot/grub目录下的文件,这个时候 grub 还没有能力识别任何文件系统。分以下两种情况:
(1)假如 start.S 读取的是stage1_5,它存放在硬盘0头0道3扇区向后的位置,stage1_5作为stage1和stage2中间的桥梁,stage1_5有识别文件系统的能力,此后,grub 才有能力去访问 /boot 分区 /boot/grub目录下的 stage2 文件,将stage2载入内存并执行。
(2)假如 start.S 读取的是 stage2,同样,这个 stage2 也不是 /boot 分区 /boot/grub 目录下的 stage2,这个时候start.S读取的是存放在 /boot 分区 Boot Sector的stage2。这种情况下就有一个限制:因为 start.S通过BIOS中断方式直接对硬盘寻址(而非通过访问具体的文件系统),其寻址范围受BIOS寻址范围限制。如下图:
很明显,假如是情形(2),我们将/boot/grub目录下的内容清空,依然能成功启动grub;假如是情形(1),将/boot/grub目录下stage2 删除后,则系统启动过程中grub会启动失败。
Stage 2:利用Stage 1.5识别文件系统的功能,读取内核镜像,加载内核。
可见,Grub主要是通过GRUB 1.5来获得识别文件系统的能力,否则只能通过BIOS中断方式直接对硬盘寻址。
Initrd 机制
内核被成功加载后,接下来就需要挂载文件系统,读取配置文件进入接下来的启动过程了。这里我们面临的问题是,文件系统种类繁多,如果将其驱动统统编入内核,将导致内核体积过于庞大。但是,如果将他们编译成模块存储在根文件系统中,又会导致内核读文件失败。为了在维持内核体积的条件下提供对不同文件系统的兼容性,Linux 2.6支持initrd机制。Initrd全称是Init RAM Disk,它是一个临时的根文件系统,为不同硬件架构生成,启动时为内核加载设备驱动,支持使内核加载真正的根文件系统。
Linux Kernel需要适应多种不同的硬件架构,但是将所有的硬件驱动编入Kernel又是不实际的,而且Kernel也不可能每新出一种硬件结构,就将该硬件的设备驱动写入内核。实际上Linux Kernel仅是包含了基本的硬件驱动,在系统安装过程中会检测系统硬件信息,根据安装信息和系统硬件信息将一部分设备驱动写入 initrd 。这样在以后启动系统时,一部分设备驱动就放在 initrd 中来加载。
在2.6内核中,支持两种格式的 initrd,一种是2.4内核的文件系统镜像 image-initrd,一种是 cpio 格式。以 cpio 格式为例,内核判断 initrd 为 cpio 的文件格式后,会将 initrd 中的内容释放到 rootfs 中。 启动过程中,系统在访问真正的根文件系统 / 时,会先访问 initrd 文件系统。将 initrd中的内容打开来看,会发现有bin、devetc、lib、procsys、sysroot、 init等文件(包含目录)。其中包含了一些设备的驱模拟块,比如 scsi ata 等设备驱动模块,同时还有几个基本的可执行程序 insmod, modprobe, lvm,nash。主要目的是加载一些存储介质的驱动模块,初始化LVM,把 / 根文件系统以只读方式挂载。
initrd中的内容释放到rootfs中后,Kernel会执行其中的init文件,这里的init是一个脚本,由nash解释器执行。这个时候内核的控制权移交给init文件处理,我们查看init文件的内容,主要也是加载各种存储介质相关的设备驱动。
驱动加载后,会创建一个根设备,然后将根文件系统 / 以只读的方式挂载。这步结束后,执行switchroot,转换到真正的根 /上面去,同时运行/sbin/init程序,开启系统的 1 号进程,此后,系统启动的控制权移交给 init 进程。关于switchroot,这是在nash中定义的程序。
initrd处理流程图
结论
Grub Stage 1.5为Grub提供文件系统驱动。Initrd为kernel提供未编译进内核的设备驱动。在没有上述机制的情况下,要读取文件系统中的文件要求程序原生支持该文件系统。