使用初始 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 存档填充 ramdisk。在这样的系统上,ramdisk 镜像的创建不需要涉及特殊的块设备或环回;您只需在磁盘上创建一个具有所需 initrd 内容的目录,cd 到该目录,然后运行(例如)

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 FS 而不是 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,您需要将选项 INITRD=<路径> 添加到全局部分或 /etc/lilo.conf 中相应内核的部分,并使用 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

其中 what-follows 是新根下的程序,例如 /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 FS)并加载 initrd

  2. /sbin/init 确定需要什么来 (1) 挂载“真正的”根 FS(即设备类型、设备驱动程序、文件系统)和 (2) 分发介质(例如,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 或由其读取的文件才需要不同。

第三个场景是更方便的恢复磁盘,因为诸如根 FS 分区位置之类的信息不必在启动时提供,而是从 initrd 加载的系统可以调用用户友好的对话框,并且还可以执行一些健全性检查(甚至某种形式的自动检测)。

最后但并非最不重要的一点是,CD-ROM 分发商可以使用它来更好地从 CD 安装,例如,通过使用启动软盘并通过 CD 上的 initrd 引导更大的 RAM 磁盘;或者通过像 LOADLIN 这样的加载程序或直接从 CD-ROM 启动,并从 CD 加载 RAM 磁盘,而无需软盘。

过时的根更改机制

以下机制是在引入 pivot_root 之前使用的。当前内核仍然支持它,但您_不应_依赖其持续可用性。

它的工作原理是在 linuxrc 退出时,将“真正的”根设备(即在内核映像中用 rdev 设置的或在引导命令行中用 root=... 设置的设备)挂载为根文件系统。然后卸载 initrd 文件系统,或者,如果它仍然繁忙,则将其移动到目录 /initrd,如果新根文件系统上存在这样的目录。

为了使用此机制,您不必指定引导命令选项 root、init 或 rw。(如果指定,它们将影响真正的根文件系统,而不是 initrd 环境。)

如果挂载了 /proc,则可以通过将新根 FS 设备的编号写入特殊文件 /proc/sys/kernel/real-root-dev,从 linuxrc 中更改“真正的”根设备,例如

# 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 的职责是构建正确的环境(可能使用在 cmdline 上传递的 root= device),然后最终执行真正的 /sbin/init

资源