使用初始 RAM 盘 (initrd)

由 Werner Almesberger <werner.almesberger@epfl.ch> 和 Hans Lermen <lermen.fgan.de> 于 1996, 2000 年编写

initrd 提供了通过引导加载程序加载 RAM 盘的功能。然后可以将此 RAM 盘挂载为根文件系统,并从中运行程序。之后,可以从不同的设备挂载一个新的根文件系统。先前的根目录(来自 initrd)随后会被移动到一个目录中,并可以在之后被卸载。

initrd 的主要设计目的是允许系统启动分两个阶段进行:内核带着一套最基础的内置驱动启动,然后从 initrd 加载额外的模块。

本文档简要概述了 initrd 的使用。关于启动过程的更详细讨论可以在 [1] 中找到。

运行原理

当使用 initrd 时,系统通常按下述方式启动:

  1. 引导加载程序加载内核和初始 RAM 盘

  2. 内核将 initrd 转换为“普通”RAM 盘并释放 initrd 所使用的内存

  3. 如果根设备不是 /dev/ram0,则会执行旧的(已废弃的)change_root 流程。请参见下文的“过时的根更改机制”一节。

  4. 挂载根设备。如果是 /dev/ram0,则将 initrd 镜像挂载为根文件系统

  5. 执行 /sbin/init(这可以是任何有效的可执行文件,包括 shell 脚本;它以 uid 0 运行,基本上可以做标准 init 能做的一切)。

  6. init 挂载“真正的”根文件系统

  7. init 使用 pivot_root 系统调用将根文件系统放置到根目录

  8. init 在新的根文件系统上执行 /sbin/init,从而执行通常的启动序列

  9. 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 盘(速度快,但会占用物理内存)

  • 回环设备(最优雅的解决方案)

我们将介绍回环设备方法:

  1. 确保内核中已配置了回环块设备支持

  2. 创建一个合适大小的空文件系统,例如:

    # dd if=/dev/zero of=initrd bs=300k count=1
    # mke2fs -F -m0 initrd
    

    (如果空间非常宝贵,你可能希望使用 Minix 文件系统代替 Ext2)

  3. 挂载文件系统,例如:

    # mount -t ext2 -o loop initrd /mnt
    
  4. 创建控制台设备

    # mkdir /mnt/dev
    # mknod /mnt/dev/console c 5 1
    
  5. 复制正确使用 initrd 环境所需的所有文件。不要忘记最重要的文件 /sbin/init

    注意

    /sbin/init 的权限必须包含 “x”(执行权限)。

  6. 通常甚至不需要重启,就可以通过以下命令测试 initrd 环境是否能正确运行:

    # chroot /mnt /sbin/init
    

    这当然仅限于那些不会干扰通用系统状态的 initrd(例如不会重新配置网络接口、覆盖已挂载的设备、尝试启动已经在运行的守护进程等。不过请注意,通常在此类 chroot 的 initrd 环境中是可以运行 pivot_root 的。)

  7. 卸载文件系统

    # umount /mnt
    
  8. 现在 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 的主要动机是为了在系统安装时支持模块化的内核配置。该过程如下:

  1. 系统使用一个极简内核(例如支持 RAM 盘、initrd、a.out 和 Ext2 文件系统)从软盘或其他介质启动并加载 initrd

  2. /sbin/init 确定挂载“真正的”根文件系统(即设备类型、设备驱动、文件系统)以及安装介质(例如 CD-ROM、网络、磁带……)所需的内容。这可以通过询问用户、自动探测或采用混合方法来实现。

  3. /sbin/init 加载必要的内核模块

  4. /sbin/init 创建并填充根文件系统(此时它还不需要是一个功能完备的可用系统)

  5. /sbin/init 调用 pivot_root 来更改根文件系统,并通过 chroot 执行一个继续进行安装的程序

  6. 安装引导加载程序

  7. 配置引导加载程序,以加载包含启动系统所需模块集的 initrd(例如,可以修改 /initrd,然后将其卸载,最后将镜像从 /dev/ram0/dev/rd/0 写入文件)

  8. 至此系统即可引导,并且可以执行后续的安装任务了

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= 设备)。

资源