使用初始 RAM 盘 (initrd)¶
由 Werner Almesberger <werner.almesberger@epfl.ch> 和 Hans Lermen <lermen.fgan.de> 于 1996, 2000 年编写
initrd 提供了通过引导加载程序加载 RAM 盘的功能。然后可以将此 RAM 盘挂载为根文件系统,并从中运行程序。之后,可以从不同的设备挂载一个新的根文件系统。先前的根目录(来自 initrd)随后会被移动到一个目录中,并可以在之后被卸载。
initrd 的主要设计目的是允许系统启动分两个阶段进行:内核带着一套最基础的内置驱动启动,然后从 initrd 加载额外的模块。
本文档简要概述了 initrd 的使用。关于启动过程的更详细讨论可以在 [1] 中找到。
运行原理¶
当使用 initrd 时,系统通常按下述方式启动:
引导加载程序加载内核和初始 RAM 盘
内核将 initrd 转换为“普通”RAM 盘并释放 initrd 所使用的内存
如果根设备不是
/dev/ram0,则会执行旧的(已废弃的)change_root 流程。请参见下文的“过时的根更改机制”一节。挂载根设备。如果是
/dev/ram0,则将 initrd 镜像挂载为根文件系统执行 /sbin/init(这可以是任何有效的可执行文件,包括 shell 脚本;它以 uid 0 运行,基本上可以做标准 init 能做的一切)。
init 挂载“真正的”根文件系统
init 使用 pivot_root 系统调用将根文件系统放置到根目录
init 在新的根文件系统上执行
/sbin/init,从而执行通常的启动序列initrd 文件系统被移除
请注意,更改根目录并不涉及将其卸载。因此,在此过程中可以让进程继续在 initrd 上运行。还要注意,挂载在 initrd 下的文件系统仍然可以访问。
引导命令行选项¶
initrd 添加了以下新选项
initrd=<path> (e.g. LOADLIN)
Loads the specified file as the initial RAM disk. When using LILO, you
have to specify the RAM disk image file in /etc/lilo.conf, using the
INITRD configuration variable.
noinitrd
initrd data is preserved but it is not converted to a RAM disk and
the "normal" root file system is mounted. initrd data can be read
from /dev/initrd. Note that the data in initrd can have any structure
in this case and doesn't necessarily have to be a file system image.
This option is used mainly for debugging.
Note: /dev/initrd is read-only and it can only be used once. As soon
as the last process has closed it, all data is freed and /dev/initrd
can't be opened anymore.
root=/dev/ram0
initrd is mounted as root, and the normal boot procedure is followed,
with the RAM disk mounted as root.
压缩的 cpio 镜像¶
近期的内核支持从压缩的 cpio 归档文件填充 RAM 盘。在这样的系统上,创建 RAM 盘镜像不需要使用特殊的块设备或回环设备;你只需在磁盘上创建一个包含所需 initrd 内容的目录,切换到该目录,然后运行(例如):
find . | cpio --quiet -H newc -o | gzip -9 -n > /boot/imagefile.img
检查现有镜像文件的内容同样简单:
mkdir /tmp/imagefile
cd /tmp/imagefile
gzip -cd /boot/imagefile.img | cpio -imd --quiet
安装¶
首先,必须在“普通”根文件系统上为 initrd 文件系统创建一个目录,例如:
# mkdir /initrd
名称并不重要。更多详情可以在 pivot_root(2) 手册页中找到。
如果根文件系统是在启动过程中动态创建的(即如果你正在制作安装软盘),那么根文件系统创建过程应当创建 /initrd 目录。
如果在某些情况下 initrd 不会被挂载,只要创建了以下设备,其内容仍然是可以访问的:
# mknod /dev/initrd b 1 250
# chmod 400 /dev/initrd
其次,内核必须在编译时启用 RAM 盘支持以及初始 RAM 盘支持。此外,至少所有从 initrd 执行程序所需的组件(例如可执行文件格式和文件系统)必须直接编译进内核。
第三,你必须创建 RAM 盘镜像。这可以通过在块设备上创建文件系统、按需复制文件,然后将块设备的内容复制到 initrd 文件中来完成。对于近期的内核,至少有三种类型的设备适用于此:
软盘(到处通用,但速度极慢)
RAM 盘(速度快,但会占用物理内存)
回环设备(最优雅的解决方案)
我们将介绍回环设备方法:
确保内核中已配置了回环块设备支持
创建一个合适大小的空文件系统,例如:
# dd if=/dev/zero of=initrd bs=300k count=1 # mke2fs -F -m0 initrd(如果空间非常宝贵,你可能希望使用 Minix 文件系统代替 Ext2)
挂载文件系统,例如:
# mount -t ext2 -o loop initrd /mnt创建控制台设备
# mkdir /mnt/dev # mknod /mnt/dev/console c 5 1复制正确使用 initrd 环境所需的所有文件。不要忘记最重要的文件
/sbin/init注意
/sbin/init的权限必须包含 “x”(执行权限)。通常甚至不需要重启,就可以通过以下命令测试 initrd 环境是否能正确运行:
# chroot /mnt /sbin/init这当然仅限于那些不会干扰通用系统状态的 initrd(例如不会重新配置网络接口、覆盖已挂载的设备、尝试启动已经在运行的守护进程等。不过请注意,通常在此类 chroot 的 initrd 环境中是可以运行 pivot_root 的。)
卸载文件系统
# umount /mnt现在 initrd 已经在文件 “initrd” 中了。如果需要,现在可以对其进行压缩
# gzip -9 initrd
为了试验 initrd,你可能想拿一张救援软盘,并只需添加一个从 /sbin/init 到 /bin/sh 的符号链接。或者,你也可以尝试实验性的 newlib 环境 [2] 来创建一个精简的 initrd。
最后,你必须启动内核并加载 initrd。几乎所有 Linux 引导加载程序都支持 initrd。由于启动过程仍然兼容旧机制,因此必须提供以下启动命令行参数:
root=/dev/ram0 rw
(只有在向 initrd 文件系统写入数据时才需要 rw 参数。)
使用 LOADLIN,你只需执行:
LOADLIN <kernel> initrd=<disk_image>
例如:
LOADLIN C:\LINUX\BZIMAGE initrd=C:\LINUX\INITRD.GZ root=/dev/ram0 rw
使用 LILO,你可以在 /etc/lilo.conf 的全局部分或相应内核的段(section)中添加选项 INITRD=<path>,并使用 APPEND 传递选项,例如:
image = /bzImage
initrd = /boot/initrd.gz
append = "root=/dev/ram0 rw"
然后运行 /sbin/lilo
对于其他引导加载程序,请参考其各自的文档。
现在你可以启动系统并开始使用 initrd 了。
更改根设备¶
在完成其职责后,init 通常会更改根设备,并继续在“真正的”根设备上启动 Linux 系统。
- 该过程包含以下步骤:
挂载新的根文件系统
将其切换为根文件系统
消除对旧的 (initrd) 根文件系统的所有访问
卸载 initrd 文件系统并释放 RAM 盘内存
挂载新的根文件系统很简单:只需将其挂载到当前根目录下的某个目录中即可。例如:
# mkdir /new-root
# mount -o ro /dev/hda1 /new-root
根目录的更改是通过 pivot_root 系统调用完成的,该调用也可以通过 pivot_root 工具来使用(参见 pivot_root(8) 手册页;pivot_root 随 util-linux 2.10h 或更高版本一同发布 [3])。pivot_root 会将当前的根目录移动到新根目录下的一个目录中,并将新的根目录置于其原位。在调用 pivot_root 之前,用于存放旧根目录的目标目录必须已经存在。例如:
# cd /new-root
# mkdir initrd
# pivot_root . initrd
此时,init 进程可能仍然通过其可执行文件、共享库、标准输入/输出/错误以及当前的根目录来访问旧的根目录。通过执行以下命令可以丢弃所有这些引用:
# exec chroot . what-follows <dev/console >dev/console 2>&1
其中后跟的是新根目录下的一个程序,例如 /sbin/init。如果新的根文件系统将与 udev 一起使用且没有有效的 /dev 目录,则必须在调用 chroot 之前初始化 udev,以便提供 /dev/console。
注意:pivot_root 的实现细节可能会随时间而变化。为确保兼容性,应遵循以下几点:
在调用
pivot_root之前,调用进程的当前工作目录应指向新的根目录使用
.作为第一个参数,并将旧根目录所在目录的*相对*路径作为第二个参数在旧根和新根文件系统下必须都可用 chroot 程序
随后 chroot 到新的根目录
在 exec 命令中对
dev/console使用相对路径
现在,可以卸载 initrd 并释放 RAM 盘所分配的内存
# umount /initrd
# blockdev --flushbufs /dev/ram0
也可以将 initrd 与通过 NFS 挂载的根目录结合使用,详情请参见 pivot_root(8) 手册页。
使用场景¶
实现 initrd 的主要动机是为了在系统安装时支持模块化的内核配置。该过程如下:
系统使用一个极简内核(例如支持 RAM 盘、initrd、a.out 和 Ext2 文件系统)从软盘或其他介质启动并加载 initrd
/sbin/init确定挂载“真正的”根文件系统(即设备类型、设备驱动、文件系统)以及安装介质(例如 CD-ROM、网络、磁带……)所需的内容。这可以通过询问用户、自动探测或采用混合方法来实现。
/sbin/init加载必要的内核模块
/sbin/init创建并填充根文件系统(此时它还不需要是一个功能完备的可用系统)
/sbin/init调用pivot_root来更改根文件系统,并通过 chroot 执行一个继续进行安装的程序安装引导加载程序
配置引导加载程序,以加载包含启动系统所需模块集的 initrd(例如,可以修改
/initrd,然后将其卸载,最后将镜像从/dev/ram0或/dev/rd/0写入文件)至此系统即可引导,并且可以执行后续的安装任务了
initrd 在此处的关键作用是在正常系统运行期间复用配置数据,而无需使用臃肿的“通用”内核,也无需重新编译或重新链接内核。
第二种场景适用于在单一管理域内、运行于不同硬件配置上的 Linux 系统安装。在这种情况下,我们希望只生成少数几个内核(理想情况下只有一个),并使与系统相关的配置信息尽可能少。此时,可以生成一个包含所有必要模块的通用 initrd。然后,只需让 /sbin/init 或它所读取的文件有所不同即可。
第三种场景是用于制作更便捷的救援盘,因为诸如根文件系统分区位置之类的信息不必在启动时提供,从 initrd 加载的系统可以调出用户友好的对话框,并且还可以执行一些完整性检查(甚至某种形式的自动检测)。
最后同样重要的是,光盘(CD-ROM)发行商可以利用它来实现更好的光盘安装,例如:使用一张引导软盘,通过 initrd 从光盘引导一个更大的 RAM 盘;或者通过像 LOADLIN 这样的引导程序或直接从光盘启动,并直接从光盘加载 RAM 盘而无需软盘。
过时的根更改机制¶
以下机制是在引入 pivot_root 之前使用的。当前的内核仍然支持它,但你不应该依赖它会一直可用。
它的工作原理是:当 linuxrc 退出时,将“真正的”根设备(即在内核镜像中通过 rdev 设置的,或在启动命令行中通过 root=... 设置的设备)挂载为根文件系统。接着,initrd 文件系统会被卸载;如果它仍然处于繁忙状态,且新根文件系统上存在 /initrd 目录,则会被移动到该目录中。
为了使用此机制,你不需要指定启动命令行选项 root、init 或 rw。(如果指定了,它们将影响真正的根文件系统,而不是 initrd 环境。)
如果挂载了 /proc,可以在 linuxrc 内部通过将新根文件系统设备的编号写入特殊文件 /proc/sys/kernel/real-root-dev 来更改“真正的”根设备,例如:
# echo 0x301 >/proc/sys/kernel/real-root-dev
请注意,该机制与 NFS 及类似的文件系统不兼容。
这个旧的、已被废弃的机制通常称为 change_root,而新的、受支持的机制称为 pivot_root。
混合使用 change_root 和 pivot_root 机制¶
如果你不想使用 root=/dev/ram0 来触发 pivot_root 机制,你可以在 initrd 镜像中同时创建 /linuxrc 和 /sbin/init。
/linuxrc 将仅包含以下内容:
#! /bin/sh
mount -n -t proc proc /proc
echo 0x0100 >/proc/sys/kernel/real-root-dev
umount -n /proc
一旦 linuxrc 退出,内核会再次将你的 initrd 挂载为根文件系统,这次执行的是 /sbin/init。同样,这个 init 的职责是在最终执行真正的 /sbin/init 之前构建出正确的环境(可能会使用命令行上传递的 root= 设备)。