星期四, 二月 01, 2007

FC6启动过程分析——从power on 到switch root

让我们从启动开始,看看FC6都做了些什么。

众所周知,和所有别的发行版本一样,FC6是由grub引导的,grub通常被安装在主引导扇区即MBR-Master Boot Record,也就是说,如果你在主板的bios中设置了从硬盘启动,那么主板自检以后所执行的第一部分代码就是grub,grub将在其安装时候指定的位置寻找 menu.lst这个文件,并且根据这个文件的配置,加载相应的内核,启动linux。这里值得我们注意的是,由于grub的这种机制,即使是格式化你觉得已经完全不再使用的硬盘分区,也可能造成灾难性的后果,假设我们把grub安装在了 mbr,并且将配置文件放置在hda2,hda1安装了一套windows操作系统,通过grub实现多重引导,但是现在我们想放弃hda2的linux 系统,或者想把它换成另外一套linux发行版,我们可能会选择格式化hda2,虽然grub被安装在mbr,但是hda2的被格式化仍然会破坏其配置文件所在的目录,grub将无法正常启动,你也就无法正常引导位于hda1的windows系统了,因为grub会提示错误,不给你选择系统的机会。这种情况在实际的双系统使用过程中,可能经常会遇到。 遇到这种问题,常见的修复方法是使用软盘启动windows,使用fdisk /mbr命令使用windows系统提供的mbr覆盖gurb的mbr代码,或者使用其他方式启动linux(软盘,U盘或者光盘),重新安装grub。虽然天不会塌下来,但是相信也会让你很不爽了,所以要小心。

内核是所有linux的核心,grub在成功的读取了配置文件以后,将会找到 kernel所在的位置,加载内核并且把电脑的控制权交给kernel程序,在FC对应的grub的menu.lst文件中,我们通常可以看到类似这样的语句:root (hd0,2) 这句话告诉我们,从现在开始,根路径(这里的根路径并不是系统所安装的根目录路径,而是指启动进程的根文件系统所挂载的路径,一般挂载在/boot/)将被设置为第一个硬盘的第3个分区,然后是kernel /boot/vmlinuz-2.6.18-1.2798.fc6 ro root=LABEL=/ rhgb quiet,这句话告诉我们,从根分区的boot目录的vmlinuz-2.6.18-1.2798.fc6这个文件中读取内核,执行内核的时候使用 "ro root=LABEL=/ rhgb quiet"这样的参数,内核的执行参数可以控制内核的行为,比如ro参数告诉内核,以只读方式挂载根分区,而quiet则告诉内核,启动的时候不要打印任何信息。这些参数不光影响内核的执行,大多数的发行版也使用这些参数控制启动完毕以后后续的动作。这些参数可以在任何时候从/proc/cmdline这个文件中获得。

现在,grub找到了内核(hd0,2) /boot/vmlinuz-2.6.18-1.2798.fc6,它将整个电脑的控制权交给了这个程序,内核开始进行各种初始化的动作,你可以将 quiet参数去掉,以便看看内核都做了哪些事情,也可以在系统启动成功以后,使用dmesg这个命令查看内核启动的时候,都打印了哪些东西,总的来说,内核做的都是一些和硬件打交道的事情,比如初始化内存,检测并初始化硬件等,在内核启动的最后,它将寻找init程序并将电脑的控制权交给这个程序。

有越来越多的新硬件需要linux的支持,如果把所有的硬件检测工作都放在内核中完成,内核会变得无比巨大,这不光是没有效率的,事实上也是不可能和不允许的,因此,如果你清楚的知道你的电脑都拥有哪些硬件,并且在未来不会添加新的硬件,你可以只将那些你需要的硬件编译到内核中去,然后直接启动你的 linux系统(事实上,早期的Gentoo系统要求每个安装者在安装的时候编译自己的内核),但是对于FC6这样的发行版来说,为了让全球大多数的PC 都可以顺利使用它,它使用模块的方式编译了尽可能多的硬件支持,并且在启动的时候在grub的配置文件中指定了initrd参数。

initrd 参数指定一个小的文件系统,这个文件系统虽然很小,但是比起内核来可以大很多,如果指定了initrd参数,内核在进行完自己的任务之后,将会运行 initrd这个小文件系统中的init程序,由这个程序完成进一步的系统初始化动作,加载更多的硬件支持以便找到真正的根文件系统。在menu.lst 文件中,这是通过initrd /boot/initrd-2.6.18-1.2798.fc6.img这一行来完成的,扩展名img通常预示着这是一个小的系统镜像文件。

使用file命令,我们可以看到/boot/initrd-2.6.18-1.2798.fc6.img是一个使用gzip压缩过的文件,解压缩以后再使用 file命令,看到这是一个cpio文件,再解压缩这个文件,我们就可以看到initrd文件系统了,这个系统中的文件不多,在根目录中包含一个init 文件,这就是内核初始化完毕以后要运行的文件,这是一个脚本文件,它使用nash解释执行,nash是专门为initrd定制的脚本解释器,它的功能小而专业,内建了很多initrd很需要的命令,我们在FC6启动的时候看到的"Red Hat nash version xxx starting "这句话,就是这个时候打印出来的。

我们来具体看看FC6的initrd做了哪些事情,首先为了让包含在initrd镜像中的那些程序顺利执行,它需要完备当前的文件系统,包括挂载proc和sys文件系统(这些是内核支持的系统目录,需要将其挂载到用户区),创建/dev目录,并且在 /dev目录中创建系统初始化所需的那些设备,最典型的设备比如console,有了这个设备,echo命令才能把信息显示到终端上,这个阶段FC6的 initrd中的init程序创建的设备达到数十个之多。然后启动hotplug支持热插拔,这里的hotplug是nash内建支持的命令之一,然后使用内建的mkblkdevs命令根据/sys/block目录下的文件信息创建/dev目录中对应的设备文件,然后加载usb和ext3相关的模块,在这个过程中可能又有新的设备被发现,因此需要使用mkblkdevs命令再次更新设备目录,在准备好了/dev设备目录以后,init程序开始调用内建的 mkrootdev命令来创建/dev/root这个设备作为后续操作的根分区,这个命令的大致逻辑是:如果内核命令行中指定了root参数,则使用其指定的那个参数作为root设备,如果指定的为LABEL,则检查所有的块设备并且寻找卷标为指定值的设备作为root设备,如果没有指定root参数,则使用/proc/sys/kernel/real-root-dev指定的设备,这个命令除了将创建/dev目录中相应的root设备文件以外,还将更新 fstab文件,将当前找到的root文件的mount参数写入/etc/fstab文件,这样在接下来的命令中,可以直接使用mount命令加载根分区,成功加载完根分区以后,init使用nash内建的setuproot命令,将所有的sys,proc,dev等这些已经挂载在initrd文件系统中的目录重新转移至新的根分区,然后使用nash内建的switchroot命令(内核2.6以上的版本)将当前文件系统切换至新的根分区,并且执行新的根分区的init命令,这样.initrd也完成了自己的使命,剩下的事情就是真实的根分区中的init程序的工作了。