使用初始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=<路径>(例如LOADLIN) 将指定的文件加载为初始RAM磁盘。使用LILO时,您必须在/etc/lilo.conf中使用INITRD配置变量指定RAM磁盘映像文件。 noinitrd 保留initrd数据,但不将其转换为RAM磁盘,并挂载“正常”的根文件系统。在这种情况下,initrd数据可以从/dev/initrd读取。请注意,在此情况下,initrd中的数据可以具有任何结构,并且不一定是文件系统映像。此选项主要用于调试。 注意:/dev/initrd是只读的,只能使用一次。一旦最后一个进程关闭它,所有数据都将被释放,无法再打开/dev/initrd。 root=/dev/ram0 将initrd挂载为根,并按照正常的引导过程进行,将RAM磁盘挂载为根。
压缩的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磁盘(速度快,但分配物理内存)
- 回环设备(最优雅的解决方案)
我们将描述回环设备方法:
- 确保回环块设备已配置到内核中
- 创建适当大小的空文件系统,例如:
# dd if=/dev/zero of=initrd bs=300k count=1 # mke2fs -F -m0 initrd
(如果空间紧张,您可能希望使用Minix FS而不是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'ed 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,您将INITRD=<path>
选项添加到/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或更高版本一起分发)。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的主要动机是允许在系统安装时进行模块化内核配置。该过程如下:
- 系统从软盘或其他介质引导,使用最小内核(例如支持RAM磁盘、initrd、a.out和Ext2 FS),并加载initrd
- /sbin/init确定所需的内容,以便(1)挂载“真实”根文件系统(即设备类型、设备驱动程序、文件系统)和(2)分发介质(例如CD-ROM、网络、磁带等)。这可以通过询问用户、自动探测或使用混合方法来完成。
- /sbin/init加载必要的内核模块
- /sbin/init创建并填充根文件系统(这不一定是一个非常可用的系统)
- /sbin/init调用pivot_root来更改根文件系统,并通过chroot执行一个程序来继续安装
- 安装引导加载程序
- 配置引导加载程序以加载使用的模块集的initrd(例如/initrd可以被修改,然后卸载,并最终,将镜像从/dev/ram0或/dev/rd/0写入文件)
- 现在系统可以启动,并且可以执行其他安装任务
initrd在这里的关键作用是在正常系统操作期间重用配置数据,而无需使用臃肿的“通用”内核或重新编译或重新链接内核。
第二种情况是在Linux在单个管理域中运行具有不同硬件配置的系统的安装。在这种情况下,理想情况下只需生成一小组内核(最好只有一个),并尽可能保持系统特定部分的配置信息较小。在这种情况下,可以生成一个通用的initrd,其中包含所有必要的模块。然后,只需/sbin/init或由其读取的文件需要有所不同。
第三种情况是更方便的恢复磁盘,因为无需在启动时提供根FS分区的位置,但从initrd加载的系统可以调用用户友好的对话框,并且还可以执行一些健全性检查(甚至某种形式的自动检测)。
最后,CD-ROM发行商可以使用它来更好地从CD进行安装,例如通过使用引导软盘并通过initrd从CD引导一个更大的RAM磁盘;或者通过使用LOADLIN等加载程序直接从CD-ROM引导,并从CD加载RAM磁盘,而无需使用软盘。
过时的根更改机制
在引入pivot_root之前使用了以下机制。当前内核仍然支持它,但您不应该依赖其持续可用性。
它的工作原理是在linuxrc退出时将“真实”根设备(即在内核映像中使用rdev或在引导命令行中使用root=...设置的设备)挂载为根文件系统。然后卸载initrd文件系统,或者如果它仍在使用中,则将其移动到新根文件系统上的一个目录/initrd,如果新根文件系统上存在这样的目录。
要使用此机制,您不必指定引导命令选项root、init或rw。(如果指定了,它们将影响真实根文件系统,而不是initrd环境。)
如果已挂载/proc,则可以通过在linuxrc中向特殊文件/proc/sys/kernel/real-root-dev写入新根FS设备的编号来从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的职责,在最终执行真正的/sbin/init之前构建正确的环境(可能使用在命令行上传递的root=设备)。
资源
- Almesberger, Werner; "Booting Linux: The History and the Future" https://www.almesberger.net/cv/papers/ols2k-9.ps.gz
- newlib package (experimental), with initrd example https://www.sourceware.org/newlib/
- util-linux: Miscellaneous utilities for Linux https://www.kernel.org/pub/linux/utils/util-linux/