分类: LINUX
2010-07-05 18:05:46
一直以来,都认为nash仅仅是作为initrd.img当中加载驱动所使用的命令解释器而已,并没有对initrd.img当中的linuxrc脚本作 一个深入的了解。例如,Redhat ES3当中的initrd.img内容(各系统略有不同)如下:
#!/bin/nash
echo "Loading scsi_mod.o module"
insmod
/lib/scsi_mod.o
echo "Loading sd_mod.o module"
insmod
/lib/sd_mod.o
echo "Loading libata.o module"
insmod /lib/libata.o
echo
"Loading ata_piix.o module"
insmod /lib/ata_piix.o
echo "Loading
jbd.o module"
insmod /lib/jbd.o
echo "Loading ext3.o module"
insmod
/lib/ext3.o
echo Mounting /proc filesystem
mount -t proc /proc
/proc
echo Creating block devices
mkdevices /dev
echo Creating
root device
mkrootdev /dev/root
echo 0×0100 >
/proc/sys/kernel/real-root-dev
echo Mounting root filesystem
mount
-o defaults –ro -t ext3 /dev/root /sysroot
pivot_root /sysroot
/sysroot/initrd
umount /initrd/proc
前面是加载驱动部分,而后面是什么用处,就没有深究了。而我自己用的虚拟机里通常会把驱动和文件系统支持编译到内核,也压根用不着initrd.img,
所以把这个宝矿放了过去。今天几个学生在Redhat
9的基础上升级内核到2.6.19,并且是驱动、文件系统用模块方式支持,终于出了问题。该来的还是要来的,应了那句话“出来混,迟早都是要还的”。学生
们把前面的硬盘驱动、文件系统驱动都替换成2.6的模块,后半部分在我的误导下删除(当然没有删除的也多半没有成功),结果死活不能进入系统,报无法挂载
根文件系统的kernel
panic错误。查了半天,发现所有的驱动都加载正常,即便是在linuxrc当中加入bash获得一个shell,检查设备和文件系统的状况也都是正
常,可就是提示找不到根文件系统。
开始引起我注意的是,通常在硬盘驱动和文件系统支持都完备的情况下它并没有提示说VFS的root=xxxx错误,而直接提示的“VFS: Unable
to mount root fs on (0,
0)”。这意味着它并未识别在grub.conf当中传递给kernel的root=/dev/xxx参数,因为Linux的设备主设备号是不会为0的。
我曾怀疑root=参数没有被接受,但却注意到“Please append a correct "root=" boot option”的提示。
为了找出问题,我在linuxrc中用bash取得了一个shell,然后挂载proc文件系统后检查/proc/sys/kernel
/real_root_dev,发现其值为0。这说明kernel得到的真实root文件系统的设备号不正确(当然有例外,注意后面的解释)。我试着把正
确的设备值(例如/dev/sda2用0×802)写入,退出bash之后启动果然正常了。这说明kernel解析参数root=/dev/xxx出现了
错误。
经过核对内核代码,发现kernel确实从root=这个参数传递中获得真实的根文件系统的位置,但是这些/dev/xxx会被转换成内核可识别的设备表
示形式。转换的过程中内核会试图检查传递进来的/dev/xxx设备是否存在,而这一步是kernel初始化过程中完成的。当我们把硬盘驱动编译为模块
时,kernel初始化过程中硬盘尚不能被识别(要等到kernel初始化完,使用initrd.img才能加载硬盘驱动),于是真实根文件系统的设备号
就不能被正确转换,使得其保留为初始值0。
那对于使用硬盘驱动的情况下如何解决这个问题呢?那就是initrd.img后半部分的工作。在加载完硬盘驱动后,脚本会用mkrootdev生成设备
/dev/root,通过man
nash你会发现这个命令的作用是根据内核传递参数当中的root=来创建对应该设备的节点,节点名就是/dev/root。之后它会把这个设备挂载到
/sysroot这个位置,然后用pivot_root这个根交换的命令把真正运行的根文件系统切换到/sysroot之下,而运行linuxrc的
initrd的文件系统将被挂载到真正的根系统下的initrd目录。至于“echo 0×0100 >
/proc/sys/kernel/real-root-dev”这个命令,看起来是告诉内核真正的文件系统是/dev/ram0,其实是利用内核判断使
用/dev/ram0为根文件系统时不再mount其他设备作为根文件系统的分支,作了一个小小的技巧骗过内核。
整个脚本的关键在于mkrootdev这个命令。它不仅能够根据root=/dev/xxx来生成对应的设备节点,还能够在碰到root=LABEL=
/的情况下探测所有的硬盘分区,以便找到对应着卷标为/的分区。这也解开了我一直没弄清楚的为什么root=LABEL=xxx的参数有些环境可以用而有
些却不行的谜团。这个LABEL=/的解析根本不是内核完成的,而是initrd.img当中linuxrc脚本的mkrootdev命令来完成的。脚本
的第二个关键在于它负责完成了本是由内核做的挂载真实根文件系统的动作,并对当前根(即initrd使用的内存文件系统)和真实根文件系统进行根切换,又
利用欺骗让内核误认为当前使用/dev/ram0作为根文件系统可以不再挂载真实的根文件系统,可谓用心良苦啊 ^_^
当然,如果完全不用initrd.img的机制,你会发现/proc/sys/kernel/real_root_dev的值也是0。这并不是内核也弄错
了,而是只有在使用initrd.img机制时,real_root_dev才反映了内核中记录的真实根文件系统的值,虽然它不一定准确。
最后,总结一下:
1、当硬盘驱动以模块形式提供时root=xxx传递给内核的参数可能不会直接起作用,内核在检查这个参数时可能
会发现这个设备(因为没有加载驱动而)不存在,因此导致内核没有接受root=xxx的参数。
2、在这种情况下,initrd.img机制的作用
不单单是加载驱动这么简单,还肩负着把正确的真实根的设备位置告知内核的艰巨任务。不过实现的方法有两种:
2.1
把正确的根设备号写入/proc/sys/kernel/real_root_dev,或者
2.2
自己挂载真正的根设备,并进行根交换把根系统切换到真实的根系统上,还要阻止内核去挂载它认为的真实根系统
对于2.2,挂载真正的根系统用nash解释器的内置命令mkrootdev完成,它可以根据传递给kernel的参数(估计读了/proc
/cmdline)获得/dev/xxx的字符串,或者得到LABEL=xxx的字符串后查询各个分区的卷标来得到对应的真实根设备号。
3、如果
没有使用initrd.img的机制,/proc/sys/kernel/real_root_dev的值没有任何处理,因为它只有在调用
initrd.img的时候才会被同步于内核中的操作对象。
另外,redhat
9所带的nash解释器(版本3.4.42)在处理mkrootdev的流程上是有bug的。如果传给kernel的参数使用root=/dev/xxx
的形式,那么nash会从/proc/sys/kernel/real_root_dev里获取用户想使用的真实根文件系统位置,显然这在我们上面的分析
当中是0,也就是错误的;而如果参数传递是root=LABEL=/的形式,那么nash的处理走的另外一个分支,它会通过解析所有分区的卷标来查找所要
的真实根文件系统,并不再查询/proc/sys/kernel/real_root_dev,这倒让我们可以得到正确的结果。bug呀bug。
不过FC6所带的nash 5.1.19已经完全不是这个流程了,应当不存在这样的bug。