#!
/bin/ash
echo
echo"Simpleinitrdisactive"
echo
mount-tproc/proc/proc
mount-tsysfsnone/sys
/bin/ash--login
EOF
chmod+x/mnt/initrd/linuxrc
#Finishup...
umount/mnt/initrd
gzip-9/tmp/ramdisk.img
cp/tmp/ramdisk.img.gz/boot/ramdisk.img.gz
为了创建initrd,我们最开始创建了一个空文件,这使用了/dev/zero(一个由零组成的码流)作为输入,并将其写入到ramdisk.img文件中。
所生成的文件大小是4MB(4000个1K大小的块)。
然后使用mke2fs命令在这个空文件上创建了一个ext2(即secondextended)文件系统。
现在这个文件变成了一个ext2格式的文件系统,我们使用loop设备将这个文件挂载到/mnt/initrd上了。
在这个挂载点上,我们现在就有了一个目录,它以ext2文件系统的形式呈现出来,我们可以对自己的initrd文件进行拼装了。
接下来的脚本提供了这种功能。
下一个步骤是创建构成根文件系统所需要的子目录:
/bin、/sys、/dev和/proc。
这里只列出了所需要的目录(例如没有库),但是其中包含了很多功能。
为了可以使用根文件系统,我们使用了BusyBox。
这个工具是一个单一映像,其中包含了很多在Linux系统上通常可以找到的工具(例如ash、awk、sed、insmod等)。
BusyBox的优点是它将很多工具打包成一个文件,同时还可以共享它们的通用元素,这样可以极大地减少映像文件的大小。
这对于嵌入式系统来说非常理想。
将BusyBox映像从自己的源目录中拷贝到自己根目录下的/bin目录中。
然后创建了很多符号链接,它们都指向BusyBox工具。
BusyBox会判断所调用的是哪个工具,并执行这个工具的功能。
我们在这个目录中创建了几个链接来支持init脚本(每个命令都是一个指向BusyBox的链接。
)
下一个步骤是创建几个特殊的设备文件。
我从自己当前的/dev子目录中直接拷贝了这些文件,这使用了-a选项(归档)来保留它们的属性。
倒数第二个步骤是生成linuxrc文件。
在内核挂载RAM磁盘之后,它会查找init文件来执行。
如果没有找到init文件,内核就会调用linuxrc文件作为自己的启动脚本。
我们在这个文件中实现对环境的基本设置,例如挂载/proc文件系统。
除了/proc之外,我还挂载了/sys文件系统,并向终端打印一条消息。
最后,我们调用了ash(一个BourneShell的克隆),这样就可以与根文件系统进行交互了。
linuxrc文件然后使用chmod命令修改成可执行的。
最后,我们的根文件系统就完成了。
我们将其卸载掉,然后使用gzip对其进行压缩。
所生成的文件(ramdisk.img.gz)被拷贝到/boot子目录中,这样就可以通过GNUGRUB对其进行加载了。
要构建初始RAM磁盘,我们可以简单地调用mkird,这样就会自动创建这个映像文件,并将其拷贝到/boot目录中。
测试定制的初始RAM磁盘
新的initrd映像现在已经在/boot目录中了,因此下一个步骤是使用默认的内核来对其进行测试。
现在我们可以重新启动Linux系统了。
在出现GRUB界面时,按C键启动GRUB中的命令行工具。
我们现在可以与GRUB进行交互,从而定义要加载哪个内核和initrd映像文件。
kernel命令让我们可以指定内核文件,initrd命令可以用来指定initrd映像文件。
在定义好这些参数之后,就可以使用boot命令来引导内核了,如清单5所示。
清单5.使用GRUB手工引导内核和initrd
GNUGRUBversion0.95(638Klower/97216Kuppermemory)
[MinimalBASH-likelineeditingissupported.Forthefirstword,TAB
listspossiblecommandcompletions.AnywhereelseTABliststhepossible
completionsofadevice/filename.ESCatanytimeexits.]
grub>kernel/bzImage-2.6.1
[Linux-bzImage,setup=0x1400,size=0x29672e]
grub>initrd/ramdisk.img.gz
[Linux-initrd@0x5f2a000,0xb5108bytes]
grub>boot
UncompressingLinux...OK,bootingthekernel.
在内核启动之后,它会检查是否有initrd映像文件可用(稍后会更详细介绍),然后将其加载,并将其挂载成根文件系统。
在清单6中我们可以看到这个Linux启动过程最后的样子。
在启动之后,ashshell就可以用来输入命令了。
在这个例子中,我们将浏览一下根文件系统的内容,并查看一下虚拟proc文件系统中的内容。
我们还展示了如何通过touch命令在文件系统中创建文件。
注意所创建的第一个进程是linuxrc(通常都是init)。
清单6.使用简单的initrd引导Linux内核
...
md:
AutodetectingRAIDarrays
md:
autorun
md:
...autorunDONE.
RAMDISK:
Compressedimagefoundatblock0
VFS:
Mountedroot(ext2filesystem).
Freeingunusedkernelmemory:
208kfreed
/$ls
binetclinuxrcprocsys
devliblost+foundsbin
/$cat/proc/1/cmdline
/bin/ash/linuxrc
/$cdbin
/bin$ls
ashcatechomountsysctl
busyboxdmesglsps
/bin$touchzfile
/bin$ls
ashcatechomountsysctl
busyboxdmesglspszfile
使用初始RAM磁盘来引导系统
现在我们已经了解了如何构建并使用定制的初始RAM磁盘,本节将探索内核是如何识别initrd并将其作为根文件系统进行挂载的。
我们将介绍启动链中的几个主要函数,并解释一下到底在进行什么操作。
引导加载程序,例如GRUB,定义了要加载的内核,并将这个内核映像以及相关的initrd拷贝到内存中。
我们可以在Linux内核源代码目录中的./init子目录中找到很多这种功能。
在内核和initrd映像被解压并拷贝到内存中之后,内核就会被调用了。
它会执行不同的初始化操作,最终您会发现自己到了init/main.c:
init()(subdir/file:
function)函数中。
这个函数执行了大量的子系统初始化操作。
此处会执行一个对init/do_mounts.c:
prepare_namespace()的调用,这个函数用来准备名称空间(挂载dev文件系统、RAID或md、设备以及最后的initrd)。
加载initrd是通过调用init/do_mounts_initrd.c:
initrd_load()实现的。
initrd_load()函数调用了init/do_mounts_rd.c:
rd_load_image(),它通过调用init/do_mounts_rd.c:
identify_ramdisk_image()来确定要加载哪个RAM磁盘。
这个函数会检查映像文件的magic号来确定它是minux、etc2、romfs、cramfs或gzip格式。
在返回到initrd_load_image之前,它还会调用init/do_mounts_rd:
crd_load()。
这个函数负责为RAM磁盘分配空间,并计算循环冗余校验码(CRC),然后对RAM磁盘映像进行解压,并将其加载到内存中。
现在,我们在一个适合挂载的块设备中就有了这个initrd映像。
现在使用一个init/do_mounts.c:
mount_root()调用将这个块设备挂载到根文件系统上。
它会创建根设备,并调用init/do_mounts.c:
mount_block_root()。
在这里调用init/do_mounts.c:
do_mount_root(),后者又会调用fs/namespace.c:
sys_mount()来真正挂载根文件系统,然后chdir到这个文件系统中。
这就是我们在清单6中所看到的熟悉消息VFS:
Mountedroot(ext2filesystem).的地方。
最后,返回到init函数中,并调用init/main.c:
run_init_process。
这会导致调用execve来启动init进程(在本例中是/linuxrc)。
linuxrc可以是一个可执行程序,也可以是一个脚本(条件是它有脚本解释器可用)。
这些函数的调用层次结构如清单7所示。
尽管此处并没有列出拷贝和挂载初始RAM磁盘所涉及的所有函数,但是这足以为我们提供一个整体流程的粗略框架。
清单7.initrd加载和挂载过程中所使用的主要函数的层次结构
init/main.c:
init
init/do_mounts.c:
prepare_namespace
init/do_mounts_initrd.c:
initrd_load
init/do_mounts_rd.c:
rd_load_image
init/do_mounts_rd.c:
identify_ramdisk_image
init/do_mounts_rd.c:
crd_load
lib/inflate.c:
gunzip
init/do_mounts.c:
mount_root
init/do_mounts.c:
mount_block_root
init/do_mounts.c:
do_mount_root
fs/namespace.c:
sys_mount
init/main.c:
run_init_process
execve
无盘引导
与嵌入式引导的情况类似,本地磁盘(软盘或CD-ROM)对于引导内核和ramdisk根文件系统来说都不是必需的。
DHCP(DynamicHostConfigurationProtocol)可以用来确定网络参数,例如IP地址和子网掩码。
TFTP(TrivialFileTransferProtocol)可以用来将内核映像和初始ramdisk映像传输到本地设备上。
传输完成之后,就可以引导Linux内核并挂载initrd了,这与本地映像引导的过程类似。
压缩initrd
在构建嵌入式系统时,我们可能希望将initrd映像文件做得尽可能小,这其中有一些技巧需要考虑。
首先是使用BusyBox(本文中已经展示过了)。
BusyBox可以将数MB的工具压缩成几百KB。
在这个例子中,BusyBox映像是静态链接的,因此它不需要其他库。
然而,如果我们需要标准的C库(我们自己定制的二进制可能需要这个库),除了巨大的glibc之外,我们还有其他选择。
第一个较小的库是uClibc,这是为对空间要求非常严格的系统准备的一个标准C库。
另外一个适合空间紧张的环境的库是dietlib。
要记住我们需要使用这些库来重新编译想在嵌入式系统中重新编译的二进制文件,因此这需要额外再做一些工作(但是这是非常值得的)。
结束语
初始RAM磁盘最初是设计用来通过一个临时根文件系统来作为内核到最终的根文件系统之间的桥梁。
initrd对于在嵌入式系统中加载到RAM磁盘里的非持久性根文件系统来说也非常有用。
参考资料
学习
∙您可以参阅本文在developerWorks全球站点上的英文原文。
∙“Linux引导过程内幕”(developerWorks,2006年5月)探索了Linux从最初的bootstrap到启动第一个用户空间应用程序的过程。
∙在“从FireWire设备引导Linux”(developerWorks,2004年7月)中,我们可以学习在各种平台的各种设备上如何(使用initrd)启动Linux。
∙cpio文件格式既简单又简洁。
因此Fedora团队选择