什么是初始 RAM磁盘?
初始 RAM磁盘(initrd)是在实际根文件系统可用之前挂载到系统中的一个初始根文件系统。initrd与内核绑定在一起,并作为内核引导过程的一部分进行加载。内核然后会将这个 initrd文件作为其两阶段引导过程的一部分来加载模块,这样才能稍后使用真正的文件系统,并挂载实际的根文件系统。
initrd中包含了实现这个目标所需要的目录和可执行程序的最小集合,例如将内核模块加载到内核中所使用的 insmod
工具。
在桌面或服务器 Linux 系统中,initrd是一个临时的文件系统。其生存周期很短,只会用作到真实文件系统的一个桥梁。在没有存储设备的嵌入式系统中,initrd是永久的根文件系统。本文将对这两种情况进行探索。
initrd剖析
initrd 映像中包含了支持 Linux 系统两阶段引导过程所需要的必要可执行程序和系统文件。
根据我们运行的 Linux 的版本不同,创建初始 RAM 磁盘的方法也可能会有所不同。在 Fedora Core 3之前,initrd 是使用 loop设备 来构建的。loop设备 是一个设备驱动程序,利用它可以将文件作为一个块设备挂载到系统中,然后就可以查看这个文件系统中的内容了。在您的内核中可能并没有loop
设备,不过这可以通过内核配置工具(makemenuconfig
)选择 DeviceDrivers > Block Devices > Loopback DeviceSupport 来启用。我们可以按照下面的方法来查看loop 设备的内容(initrd 文件的名字可能会稍有不同):
清单 1. 查看 initrd 的内容(适用于FC3 之前的版本)
# mkdir temp ; cd temp
# cp /boot/initrd.img.gz .
# gunzip initrd.img.gz
# mount -t ext2 -o loop initrd.img /mnt/initrd
# ls -la /mnt/initrd
#
现在我们就可以查看 /mnt/initrd 子目录中的内容了,这就代表了 initrd 文件的内容。注意,即使您的 initrd映像文件不是以 .gz 结尾,它也可能是一个压缩文件,您可以给这个文件添加上 .gz 后缀,然后再使用 gunzip对其进行解压。
从 Fedora Core 3 开始,默认的 initrd 映像变成了一个经过压缩的 cpio 归档文件。我们不用再使用 loop设备来将 initrd 作为压缩映像进行挂载,而是可以将其作为 cpio 归档文件来使用。要查看 cpio归档文件的内容,可以使用下面的命令:
清单 2. 查看 initrd 的内容(适用于FC3 及其以后的版本)
# mkdir temp ; cd temp
# cp /boot/initrd-2.6.14.2.img initrd-2.6.14.2.img.gz
# gunzip initrd-2.6.14.2.img.gz
# cpio -i --make-directories < initrd-2.6.14.2.img
#
结果会生成一个很小的根文件系统,如清单 3 所示。在 ./bin目录中有一组很少但却非常必要的应用程序,包括nash
(即 not ashell,是一个脚本解释器)、insmod
(用来加载内核模块)和 lvm
(逻辑卷管理工具)。
清单 3. 默认的 Linux initrd目录结构
# ls -la
#
drwxr-xr-x 10 root root 4096 May 7 02:48 .
drwxr-x--- 15 root root 4096 May 7 00:54 ..
drwxr-xr-x 2 root root 4096 May 7 02:48 bin
drwxr-xr-x 2 root root 4096 May 7 02:48 dev
drwxr-xr-x 4 root root 4096 May 7 02:48 etc
-rwxr-xr-x 1 root root 812 May 7 02:48 init
-rw-r--r-- 1 root root 1723392 May 7 02:45 initrd-2.6.14.2.img
drwxr-xr-x 2 root root 4096 May 7 02:48 lib
drwxr-xr-x 2 root root 4096 May 7 02:48 loopfs
drwxr-xr-x 2 root root 4096 May 7 02:48 proc
lrwxrwxrwx 1 root root 3 May 7 02:48 sbin -> bin
drwxr-xr-x 2 root root 4096 May 7 02:48 sys
drwxr-xr-x 2 root root 4096 May 7 02:48 sysroot
#
清单 3 中比较有趣的是 init 文件就在根目录中。与传统的 Linux 引导过程类似,这个文件也是在将 initrd 映像解压到RAM 磁盘中时被调用的。在本文稍后我们将来探索这个问题。
创建 initrd所使用的工具
cpio 命令
使用 cpio
命令,我们可以对cpio 文件进行操作。cpio 是一种文件格式,它简单地使用文件头将一组文件串接在一起。cpio 文件格式可以使用 ASCII和二进制文件。为了保证可移植性,我们可以使用 ASCII格式。为了减小文件大小,我们可以使用二进制的版本。
下面让我们回到最开始,来看一下 initrd 映像最初是如何构建的。对于传统的 Linux 系统来说,initrd 映像是在Linux 构建过程中创建的。有很多工具,例如 mkinitrd
,都可以用来使用必要的库和模块自动构建initrd,从而用作与真实的根文件系统之间的桥梁。mkinitrd
工具实际上就是一个
shell脚本,因此我们可以看到它究竟是如何来实现这个结果的。还有一个 YAIRD
(即Yet Another Mkinitrd)工具,可以对 initrd 构建过程的各个方面进行定制。
手工构建定制的初始 RAM磁盘
由于在很多基于 Linux 的嵌入式系统上没有硬盘,因此 initrd 也会作为这种系统上的永久根文件系统使用。清单 4显示了如何创建一个 initrd 映像文件。我使用了一个标准的 Linux桌面,这样您即使没有嵌入式平台,也可以按照下面的步骤来执行了。除了交叉编译,其他概念(也适用于 initrd的构建)对于嵌入式平台都是相同的。
清单 4. 创建定制 initrd的工具(mkird)
#!/bin/bash
# Housekeeping...
rm -f /tmp/ramdisk.img
rm -f /tmp/ramdisk.img.gz
# Ramdisk Constants
RDSIZE=4000
BLKSIZE=1024
# Create an empty ramdisk image
dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE
# Make it an ext2 mountable file system
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE
# Mount it so that we can populate
mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop=/dev/loop0
# Populate the filesystem (subdirectories)
mkdir /mnt/initrd/bin
mkdir /mnt/initrd/sys
mkdir /mnt/initrd/dev
mkdir /mnt/initrd/proc
# Grab busybox and create the symbolic links
pushd /mnt/initrd/bin
cp /usr/local/src/busybox-1.1.1/busybox .
ln -s busybox ash
ln -s busybox mount
ln -s busybox echo
ln -s busybox ls
ln -s busybox cat
ln -s busybox ps
ln -s busybox dmesg
ln -s busybox sysctl
popd
# Grab the necessary dev files
cp -a /dev/console /mnt/initrd/dev
cp -a /dev/ramdisk /mnt/initrd/dev
cp -a /dev/ram0 /mnt/initrd/dev
cp -a /dev/null /mnt/initrd/dev
cp -a /dev/tty1 /mnt/initrd/dev
cp -a /dev/tty2 /mnt/initrd/dev
# Equate sbin with bin
pushd /mnt/initrd
ln -s bin sbin
popd
# Create the init file
cat >> /mnt/initrd/linuxrc << EOF
#!/bin/ash
echo
echo "Simple initrd is active"
echo
mount -t proc /proc /proc
mount -t sysfs none /sys
/bin/ash --login
EOF
chmod +x /mnt/initrd/linuxrc
# Finish up...
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(即second extended)文件系统。现在这个文件变成了一个 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
(一个 Bourne Shell的克隆),这样就可以与根文件系统进行交互了。linuxrc 文件然后使用 chmod
命令修改成可执行的。
最后,我们的根文件系统就完成了。我们将其卸载掉,然后使用 gzip
对其进行压缩。所生成的文件(ramdisk.img.gz)被拷贝到/boot 子目录中,这样就可以通过 GNU GRUB 对其进行加载了。
要构建初始 RAM 磁盘,我们可以简单地调用 mkird
,这样就会自动创建这个映像文件,并将其拷贝到/boot 目录中。