Linux 系统启动过程(initrd部分) --- Linux boot process (initrd part)

来源:互联网 发布:考雅思软件 编辑:程序博客网 时间:2024/04/29 17:50
以前一直没有研究过initrd部分,只是知道linux内核启动后会首先由引导装载器读取initrd映像来启动ramdisk.它的作用是鸡和蛋问题的解决方案,即首先安装一个内存盘作为临时的root,然后加载其上的磁盘/网络磁盘驱动程序,从而找到真正的硬盘设备/网络文件系统,再挂载它为真正的root,从而进入整个linux world。但这里面到底干了些什么,以及怎么实现的没有仔细研究,也认为比较简单,直到有一天,第一次升级我的home box,linux2.4内核到linux2.6内核,在启动过程中kernel panic -- 傻眼了。当时由于工作的缘故没有深究下去,但那时第一次遇到这个问题。最有由于自己想搭建一个linux平台,迫不得已只好开始仔细研究这个东西了。当然最后收获还是颇丰,把过程简单记录一下,希望对其它人有帮助。
我一直使用redhat的系统,包括最近的FC。所以我主要以RH/FC的启动过程为例进行说明。其他系统略有不同,但基本原理是一样的。

initrd放在RH/FC系统的/boot目录中,在grub,conf中指定。如:
initrd /initrd-2.6.23.1-42.fc8.img
注意这里的/是指grub的root,那是grub的root命令所指磁盘的boot目录下。

这个映像文件在2.6.1.x内核以前的版本中是一个被压缩的loop设备文件,但在2.6.1.x以后的内核支持它可以是一个被压缩的cpio包了。如果是cpio包可以:

mkdir initrd && mv /boot/initrd-2.6.23.1-42.fc8.img initrd && cd initrd
gunzip -c < initrd-2.6.23.1-42.fc8.img | cpio -imd      #注意它将ramdisk内容解压到inird当前目录bin sbin

如果是loop设备,必须通过mount命令加载它才能访问:
mkdir /mnt/initrd
gunzip -c < initrd-2.6.23.1-42.fc8.img > initrd.loop
mount -o loop,rw initrd.loop /mnt/initrd

在initrd目录中就是系统启动时临时root的内容。目录下有一个init文件,该文件最为重要,它就是内核启动后执行的第一个脚本。实际上内核启动后寻找的就是/init ; /sbin/init ;/bin/init,找到任何一个就执行它。整个的初始化从它开始。它的内容如下(FC8):

#!/bin/nash

mount -t proc /proc /proc
setquiet
echo Mounting proc filesystem
echo Mounting sysfs filesystem
mount -t sysfs /sys /sys
echo Creating /dev
mount -o mode=0755 -t tmpfs /dev /dev
mkdir /dev/pts
mount -t devpts -o gid=5,mode=620 /dev/pts /dev/pts
mkdir /dev/shm
mkdir /dev/mapper
echo Creating initial device nodes
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/systty c 4 0
mknod /dev/tty c 5 0
mknod /dev/console c 5 1
mknod /dev/ptmx c 5 2
mknod /dev/rtc c 10 135
mknod /dev/tty0 c 4 0
mknod /dev/tty1 c 4 1
mknod /dev/tty2 c 4 2
mknod /dev/tty3 c 4 3
mknod /dev/tty4 c 4 4
mknod /dev/tty5 c 4 5
mknod /dev/tty6 c 4 6
mknod /dev/tty7 c 4 7
mknod /dev/tty8 c 4 8
mknod /dev/tty9 c 4 9
mknod /dev/tty10 c 4 10
mknod /dev/tty11 c 4 11
mknod /dev/tty12 c 4 12
mknod /dev/ttyS0 c 4 64
mknod /dev/ttyS1 c 4 65
mknod /dev/ttyS2 c 4 66
mknod /dev/ttyS3 c 4 67
echo Setting up hotplug.
hotplug
echo Creating block device nodes.
mkblkdevs
echo "Loading ehci-hcd.ko module"
insmod /lib/ehci-hcd.ko
echo "Loading ohci-hcd.ko module"
 insmod /lib/ohci-hcd.ko
echo "Loading uhci-hcd.ko module"
insmod /lib/uhci-hcd.ko
mount -t usbfs /proc/bus/usb /proc/bus/usb
echo "Loading mbcache.ko module"
insmod /lib/mbcache.ko
echo "Loading jbd.ko module"
insmod /lib/jbd.ko
echo "Loading ext3.ko module"
insmod /lib/ext3.ko
echo "Loading scsi_mod.ko module"
insmod /lib/scsi_mod.ko
echo "Loading sd_mod.ko module"
insmod /lib/sd_mod.ko
echo "Loading scsi_transport_spi.ko module"
insmod /lib/scsi_transport_spi.ko
echo "Loading mptbase.ko module"
insmod /lib/mptbase.ko
echo "Loading mptscsih.ko module"
insmod /lib/mptscsih.ko
echo "Loading mptspi.ko module"
insmod /lib/mptspi.ko
echo "Loading dm-mod.ko module"
insmod /lib/dm-mod.ko
echo "Loading dm-mirror.ko module"
insmod /lib/dm-mirror.ko
echo "Loading dm-zero.ko module"
insmod /lib/dm-zero.ko
echo "Loading dm-snapshot.ko module"
insmod /lib/dm-snapshot.ko
echo "Loading libata.ko module"
insmod /lib/libata.ko
echo "Loading ata_piix.ko module"
insmod /lib/ata_piix.ko
echo Waiting for driver initialization.
stabilized --hash --interval 250 /proc/scsi/scsi
echo Making device-mapper control node
mkdmnod
insmod /lib/scsi_wait_scan.ko
rmmod scsi_wait_scan
mkblkdevs
echo Scanning logical volumes
lvm vgscan --ignorelockingfailure
echo Activating logical volumes
lvm vgchange -ay --ignorelockingfailure  VolGroup00
resume /dev/VolGroup00/LogVol01
echo Creating root device.
mkrootdev -t ext3 -o defaults,ro /dev/VolGroup00/LogVol00
echo Mounting root filesystem.
mount /sysroot
echo Setting up other filesystems.
setuproot
echo Switching to new root and running init.
switchroot
echo Booting has failed.
sleep -1

第一行#!/bin/nash是一个redhat自己的微型解释器。不是bash。里面的命令像,mount, echo, mkrootdev等等并不是shell 命令。而是nash的内部命令。能做基本的mount等的功能。可能是为了减小initrd的体积而设计的吧。只有insmod,lvm是外部命令。如果nash在当前的路径上找到名字相同的外部命令,它会只执行外部的命令,不执行内部版本。nash的文档很不全,man nash得到很少的信息,如mkrootdev,而且某些命令的文档是错的,如mkrootdev,mount,以及部分命令根本就没有文档,如mkblkdevs,setuproot。我被迫读nash源码才了解到。nash是随mkinitrd包一起发行的,里面有nash的源代码。

前面的部分功能比较简单。看名字就基本都可以理解了,mount命令加载各种内核文件系统proc,sys等,mknod创建系统启动所必须的/dev设备节点(其实大多数不是必须的,只有null,console,zero可能是内核所必须的)。hotplug开始监听系统总线热插拔的磁盘等uevent事件。mkblkdevs将这些热插拔的磁盘节点创建到/dev下。接着insmod开始安装usb,block设备,scsi设备,ide设备,device-mapper的设备驱动,随后系统总线就会收到这些事件,mkblkdevs又会把他们的节点创建到/dev下。我们的硬盘多数是在这个阶段被找到并创建了相应的设备节点。然后是加载启动逻辑卷管理,使我们的设备能使用逻辑卷名称。这一步可以不要的,在启动阶段硬盘完全可以用LABEL或/dev/sda1这样的名字来表示。

最重要的部分从mkrootdev开始。mkrootdev的工作流程是这样:

首先从/proc/cmdline中查找root=内核参数,再解析mkrootdev后面的-t 和-o参数,如果-t是nfs,而且,-o中包含dhcp,那么root的相关信息从dhcp主机取得,如果-o选项不包含dhcp或者-t不是nfs,则从root=内核参数取真正root文件系统的位置,如果没有root=参数,使用mkrootdev后面的设备作为root设备。如果是-t nfs那么直接添加入口到/etc/fstab文件,包括-t的文件系统类型和-o的选项。如果-t指定的文件系统不是nfs,那么它创建/dev/root节点,它的Major,Minor设备号使用root=参数的设备号(如果有),或mkrootdev后面的设备的设备号。

所以,mkrootdev -t ext3 -o defaults,ro /dev/VolGroup00/LogVol00这行,会将一条入口写入内存中的/etc/fstab,如: /dev/root  /sysroot ext3 defaults,ro 0 0 ;注意设备是/dev/root.而不是/dev/VolGroup00/LogVol00,但他们的设备号是一样的。

紧接着后面的mount /sysroot就会读取/etc/fstab并加载刚刚mkrootdev写入的这条入口所指定的设备,这就是加载/dev/root /sysroot下。这时我们的真正的root才被加载到了系统中。nash的mount要求,如果只有一个参数,以它作为root加载点,且它必须是/sysroot(会同fstab中的做比较)。如果,有加载点和设备参数,-t -o必须也被提供,mount丢弃/etc/fstab中的入口的内容。而使用参数提供的信息加载root设备。若如本例,至此/sysroot下是真正的root分区。/是initrd的内存盘。

setuproot并不理睬后面的参数,以/sysroot作为我们的root。安装所有的子分区到/sysroot下。也就是建立我们的根文件系统数,如果我们的root不是由单个分区组成的话。这些信息从/sysroot/etc/fstab.sys中读取。如果它不存在,就从initrd的/etc/fstab.sys中读取。如果仍然不存在,就建立默认的文件系统树:

 挂载(Bind挂载方式)/proc 到/sysroot/proc. /sys到/sysroot/sys

最后switchroot卸载/dev,/proc,/sys文件系统,挂载(移动挂载方式)/sysroot到/下面.,然后卸载initrd的/。打开/dev/console到描述符3,将stdin,stdout,stderr全部定向到3(console),分析内核参数,寻找init=,如果有以它作为init程序执行,否则,执行默认:搜寻第一个找到的/sbin/init;/etc/init;/bin/init;/bin/sh执行,这就是所有进程的父进程0

至此initrd完成了启动过程,switchroot不会返回,sleep -1将永远不会执行,除非上面的switchroot不能找到一个init和sh。

综观此过程,如果不用nash是完全可以实现的,但initrd里面必须copy很多命令进去。且必须copy 一个shell:sh/bash。在加载模块部分完全可以加入其他的模块,比如,网络smb,nfs,或光盘文件系统模块,以这些种类的文件系统作为根。来搭建无盘工作站系统,或者live CD系统。也可以做救援磁盘。来寻找,加载出现故障的root分区。在调试过程中,可以启动一个交互式shell(sh -i),方便我们直接运行命令。nash中,可以用exec --nokill /bin/bash -i -l命令来给我们一个shell。当然,你要先copy需要的文件(包括bash等)进入initrd的/bin目录中,这里给出如何自己压缩initrd的命令:

用于大于2.6.1.x以后的内核:

cd initrd
find . | cpio -o -H newc | gzip -c > initrd.img

用于旧内核:

dd if=/dev/zero of=initrd bs=300k count=1
mke2fs -F -m0 initrd
mount -t ext2 -o loop initrd /mnt/initrd
cp -R initrd/* /mnt/initrd
umount /mnt/initrd
gzip -9 -c initrd > initrd.img