原作者:M. Tim Jones (mtj@mtjones.com), Emulex公司工程、顾问
原文链接: IBM DeveloperWorks – Linux initial RAM disk (initrd) overview
摘要: Linux® initial RAM disk (initrd)
是一个临时的根文件系统(root file system),它在系统引导期间被挂载,以提供“双阶段引导”过程的支持。initrd
包含各种可执行文件和驱动以允许真正的根文件系统的加载。在这之后 initrd
会被解挂载并且释放它所占用的内存。在许多嵌入式的Linux系统中,initrd是它们的最终根文件系统。本文介绍了Linux
2.6的initial RAM disk,并将探讨它在内核中的创建与使用。
什么是 initial RAM disk?
initial RAM disk (initrd)
在真正的根文件系统被加载前加载,它是一个初始的根文件系统。initrd与内核绑定在一起,并且作为内核引导过程的一部分而被加载。内核将initrd
作双阶段引导过程的一部分加载,然后加载模块并使真正的根文件系统可用。
initrd 包含了最小化的目录与可执行文件(例如insmod以安装内核模块)的集合来实现此过程。
就桌面及服务器Linux系统而言,initrd是瞬态的文件系统,它的生命期很短,只作为连接真实根文件系统的桥梁而存在。而在没有可擦写存储器的嵌入式系统中,它是永久的根文件系统。本文将讲讲述这两个部分。
initrd 剖析
initrd 镜像包含必要的可执行文件和系统文件以支持Linux系统第二阶段的引导。根据你所使用的Linux版本不同,创建initrd RAM disk 的方法也不尽相同。先于Fedora Core 3的版本中,initrd是由一种叫做loop device的设备驱动构建,它允许你将一个文件挂载为块设备,并且通知文件系统。loop device也许不会在你的内核中出现,但是你能够从内核配置工具make menuconfig中启用它:Device Drivers > Block Devices > Loopback Device Support。你可以通过如下方式查看loop device设备(你的initrd文件名可能会不同):
Listing 1. 检查initrd(限FC3之前的版本)
# cp /boot/initrd.img.gz .
# gunzip initrd.img.gz
# mount -t ext -o loop initrd.img /mnt/initrd
# ls -la /mnt/initrd
#
现在你就可以查看/mnt/initrd子目录中initrd的内容了。注意:即使你的initrd镜像文件不以.gz扩展名结尾(这是个压缩文件),你也可以添加上去,然后用gunzip解压缩。
从Fedora Core 3开始,默认的initrd镜像是一个压缩过的cpio存档文件。因此,你应该用cpio工具,而不是将镜像解压缩后挂载为loop device,来查看其中的内容。要查看cpio文档内容,应该使用以下命令:
Listing 2. 检查initrd(限FC3在内的之后版本)
# 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
#
其结果是一个迷你的根文件系统,如Listing 3所示。虽然小,但却非常重要。工具集在./bin/目录中,包括nash(不是shell,而是脚本解释器),insmod(用来载入内核模块),还有lvm(logical volume manager tools,逻辑卷管理工具)。
Listing 3. 默认Linux initrd目录结构
#
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
#
有趣的是,在Listing 3中,init文件在根目录里。这个文件会像传统的Linux引导进程,在initrd镜像解压缩进RAM Disk后运行。本文稍后就会介绍。
创建initrd的工具
现在,我们先回到开始正式理解initrd镜像是如何构建的地方。对于传统的Linux系统,initrd镜像是在build过程中创建的。很多工具,像mkinitrd,也能自动地创建拥有必要可执行文件、模块的initrd镜像。mkinitrd工具事实上是一个shell脚本,所以你可以确切地知道它到底是如何生成结果的。还有YAIRD(Yet Another Mkinitrd)工具,它允许自定义initrd结构的各个方面。
手动构建一个自定义的initial RAM disk
由于在基于Linux系统的嵌入式系统中没有可用的硬件驱动,initrd也用作永久的根文件系统。Listing 4给出了如何创建initrd镜像的过程。
我使用的标准的桌面版本的Linux,所以即使你没有嵌入式目标机,你也可以跟着我做。即使对于嵌入式目标机,initrd的概念也不会不同,除非你使用的是交叉编译平台。
Listing 4. 使用mkird创建自定义的initrd
# 清理...
rm -f /tmp/ramdisk.img
rm -f /tmp/ramdisk.img.gz
# Ramdisk 常量
RDSIZE=4000
BLKSIZE=1024
# 创建一个空 ramdisk 镜像
dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE
# 使它成为可挂载的ext2文件系统
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE
# 挂载它,以便迁移文件
mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop=/dev/loop0
# 迁移文件系统 (子目录)
mkdir /mnt/initrd/bin
mkdir /mnt/initrd/sys
mkdir /mnt/initrd/dev
mkdir /mnt/initrd/proc
# 抓取 busybox 并创建符号链接
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
# 抓取必要的设备文件
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
# 连结sbin与bin
pushd /mnt/initrd
ln -s bin sbin
popd
# 创建 init 文件
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
# 完成...
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)文件系统文件,再将它以loop设备的类型挂载到/mnt/initrd。在这个挂载点上,你能看到一个具有ext2文件系统的目录,并将你的initrd迁移到其中。余下的脚本也就是实现这样的功能。
接下来,我们创建必要的构建根文件系统的子目录:/bin, /sys, /dev以及/proc。虽然我们只用到一部分目录,但是他们包含主要的功能。
为了使你的根文件系统有用,我们使用BusyBox。它是一个包含许多Linux工具(如awk, sed,
insmod等)的镜像。使用BusyBox的优点是,它将许多常用工具打包进一个单一的镜像,这使文件更小。对嵌入式系统来说,这是求之不得的。于是,
我们将BusyBox镜像复制到根文件系统下的/bin目录,并建立一系列指向BusyBox的符号链接。BusyBox会知道应该启动哪个工具。这些符号链接会为你的init脚本提供支持。
下一步是创建特殊设备文件。我直接从我现有的/dev子目录中复制,并使用选项-a (archive)来保留他们的属性。
倒数第二步,生成linuxrc文件。在内核挂载到RAM disk之后,它会搜索一个init文件并执行之。如果init文件没有找到,内核启动linuxrc文件作为启动脚本。在这个脚本中,你创建一个基本的环境,例如挂载/proc文件系统。除了/proc,还要挂载/sys文件系统并向控制台发出一个消息。最后,启动ash(a Bourne Shell的副本)以便与跟文件系统交互。linuxrc文件还要标记为可执行。
最后,你的根文件系统完成了。随后将它解挂载并用gzip压缩。将最终文件(ramdisk.img.gz)复制到/boot目录,以便它能随着GNU GRUB一起启动。要建立RAM disk,只要启动mkird,然后镜像就会自动创建并拷贝到/boot。
测试自定义的初始化RAM盘(initial RAM disk)
现在你的新initrd镜像在/boot下,所以我们接下来可以用你的默认内核测试它。首先重新启动你的Linux系统,当出现GRUB时,按C来GRUB的命令行模式。(如果你的计算机没有出现GRUB引导菜单,请在启动时反复按SHIFT——Lesca 注)现在你就可以和GRUB交互并指定要加载内核和initrd镜像。命令kernel允许你定义内核文件,initrd允许你指定initrd镜像文件。指定好之后,使用boot命令引导内核。如Listing 5所示:
Listing 5. 用GRUB手动挂载内核与initrd
[ Minimal BASH-like line editing is supported. For the first word, TAB
lists possible command completions. Anywhere else TAB lists the possible
completions of a device/filename. ESC at any time exits.]
grub> kernel /bzImage-2.6.1
[Linux-bzImage, setup=0x1400, size=0x29672e]
grub> initrd /ramdisk.img.gz
[Linux-initrd @ 0x5f2a000, 0xb5108 bytes]
grub> boot
Uncompressing Linux... OK, booting the kernel.
在内核启动之后,它会检查initrd是否有效,再加载并将它挂载为根文件系统。你可以在Listing 6中看到这次特别的启动过程。开始之后,ash就开始等待命令的输入。在本例中,我浏览了根文件系统并查询了proc文件系统项,并证明了你可以使用touch命令在文件系统中创建文件。注意,此处第一个处理的脚本文件是linuxrc(通常会是init)。
Listing 6. 用一个简单的initrd引导Linux内核
md: Autodetecting RAID arrays
md: autorun
md: ... autorun DONE.
RAMDISK: Compressed image found at block 0
VFS: Mounted root (ext2 file system).
Freeing unused kernel memory: 208k freed
/ $ ls
bin etc linuxrc proc sys
dev lib lost+found sbin
/ $ cat /proc/1/cmdline
/bin/ash/linuxrc
/ $ cd bin
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps
/bin $ touch zfile
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps zfile
使用initrd引导
到目前为止,你已经知道如何建立自定义的初始化RAM盘了,本节将介绍内核是如何识别并将initrd挂载为根文件系统的。我将介绍一些引导链中的主要函数并解释其中发生了什么。
像GRUB那样的boot loader能够识别将被加载的内核,并且将它以及相关initrd复制到内存中。在./init子目录中(Linux内核源文件目录下),你能够发现很多类似的功能。
在内核与initrd被解压并加载到内存之后,内核就启动了。各种各样的初始化过程也随之开始,最后你会发现自己在init/main.c:init()函数中(格式说明:subdir/file:function)。这个函数为许多子系统进行初始化,例如其间会调用这样一个函数:init/do_mounts.c:prepare_namespace(),它将准备命名空间(挂载dev文件系统,RAID,md,其他设备,最后还有initrd)。调用init/do_mounts_initrd.c:initrd_load()之后,就开始加载initrd。
initrd_load()函数调用init/do_mounts_rd.c:rd_load_image(),这又会调用init/do_mounts_rd.c:identify_ramdisk_image()以加载initrd。这个函数会检查镜像的魔数(magic number)以确定它的文件系统类型:minux, etc2, romfs, cramfs, or gzip。一旦返回到initrd_load_image,一个新的调用随之开始。init/do_mounts_rd:crd_load()会为RAM disk分配空间,计算CRC(循环冗余校检码),然后解压加载RAM disk镜像到内存。此时,你就成功的加载了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(改变当前目录到)那里。从这里,你也许找到了与Listing 6中描述的信息的相似之处。
最后,返回到init函数,并调用init/main.c:run_init_process,它会继续调用execve以启动init进程(本例中是/linuxrc)。linuxrc可以是一个可执行文件或者一个脚本(只要脚本解释器支持)。
这里的函数调用体系结构如Listing 7所示。这其中并没有列出所有与加载、调用RAM disk有关的函数,但它能表示一个调用流程的概览
Listing 7. initrd加载过程中的主要函数调用体系结构
init/main.c:initinit/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协议可以鉴别网络参数,例 如IP地址和子网掩码。TFTP协议可以传输内核镜像以及ramdisk镜像到本地设备。一旦传输完毕,就可以引导Linux内核并挂载initrd,这 就像是在本地完成的。
压缩你的initrd
在嵌入式系统上,你会需要尽可能小的initrd镜像。我们有一些提示可供参考。首先是使用BusyBox(前面提到过),它能将几兆字节的工具压缩到只有几百K字节。
本例中,BusyBox镜像被静态连接,以便不再需要库文件。然而,如果你需要标准C库文件(作为你的自定义二进制文件),与glibc的臃肿相比,我们有其他更好的选择。第一个是uClibc,对于空间有限的系统,它是一个最小化的标准C库版本;另一个库,dietlib,同样适合空间受限的环境。记住:你需要用这些库重新编译要在你嵌入式系统上运行的二进制文件,也就是说你需要一点额外的工作(但这很值得)。
总结
initial RAM disk最初是为将内核引入最终文件系统而产生的。initrd对于在嵌入式系统中的挂载到RAM disk的非永久的根文件系统也很有用。
References:
[1] IBM DeveloperWorks – Linux initial RAM disk (initrd) overview
[2] Andrew S. Tanenbaum – Moden Operating Systems 3rd Edition