什么是ramfs
Ramfs 是一个非常简单的文件系统,它将 Linux 的磁盘缓存机制(页面缓存和 dentry 缓存)导出为可动态调整大小的基于 RAM 的文件系统。
通常,Linux 会将所有文件缓存在内存中。从后备存储(通常是文件系统所安装的块设备)读取的数据页面会保留以备再次需要,但会标记为干净(可释放),以防虚拟内存系统需要内存用于其他用途。同样,写入文件的数据在写入后备存储后会立即标记为干净,但会保留以备缓存,直到 VM 重新分配内存。类似的机制(dentry 缓存)大大加快了对目录的访问速度。
使用 ramfs 时,没有后备存储。写入 ramfs 的文件会像往常一样分配 dentry 和页面缓存,但没有地方写入它们。这意味着页面永远不会被标记为干净,因此当 VM 想要回收内存时,它们无法被释放。
实现 ramfs 所需的代码量很小,因为所有工作都由现有的 Linux 缓存基础结构完成。基本上,您将磁盘缓存作为文件系统安装。因此,ramfs 不是可通过 menuconfig 移除的可选组件,因为这样节省的空间可以忽略不计。
ramfs和ramdisk
较旧的“ram disk”机制从 RAM 的某个区域创建一个合成块设备,并将其用作文件系统的后备存储。此块设备的大小是固定的,因此安装在其上的文件系统的大小也是固定的。使用 ram disk 还需要不必要地将内存从伪块设备复制到页面缓存中(并将更改复制回来),以及创建和销毁 dentry。此外,它还需要文件系统驱动程序(例如 ext2)来格式化和解释这些数据。
与 ramfs 相比,这会浪费内存(和内存总线带宽)、给 CPU 带来不必要的工作,并污染 CPU 缓存。(有一些技巧可以通过使用页表来避免这种复制,但它们非常复杂,而且成本与复制差不多。)更重要的是,ramfs 所做的所有工作无论如何都必须发生,因为所有文件访问都要经过页面和 dentry 缓存。RAM 磁盘根本就没有必要;ramfs 内部要简单得多。
ramdisk 半过时的另一个原因是,引入了回送设备,提供了一种更灵活、更方便的方法来创建合成块设备,现在可以通过文件而不是内存块来创建。
ramfs和tmpfs
ramfs 的一个缺点是,您可以不断向其中写入数据,直到填满所有内存,而 VM 无法释放它,因为 VM 认为文件应该写入后备存储(而不是交换空间),但 ramfs 没有任何后备存储。因此,只有 root(或受信任的用户)才应被允许对 ramfs 挂载进行写访问。
ramfs 的衍生产品 tmpfs 是为了增加大小限制和将数据写入交换空间的能力而创建的。普通用户可以对 tmpfs 挂载进行写访问 。
什么是rootfs
Rootfs 是 ramfs(或 tmpfs,如果启用的话)的一个特殊实例,它始终存在于 2.6 系统中。您无法卸载 rootfs 的原因与您无法终止 init 进程的原因大致相同;与使用特殊代码检查和处理空列表相比,内核只需确保某些列表不会变为空,这样更小更简单。
大多数系统只是在 rootfs 上安装另一个文件系统并忽略它。ramfs 的空实例占用的空间很小。
如果启用了 CONFIG_TMPFS,rootfs 将默认使用 tmpfs 而不是 ramfs。要强制使用 ramfs,请在内核命令行中添加“rootfstype=ramfs”。
什么是initramfs
所有 2.6 Linux 内核都包含一个 gzip 压缩的“cpio”格式档案,内核启动时会将其解压到 rootfs 中。解压后,内核会检查 rootfs 是否包含文件“init”,如果是,则以 PID 1 执行该文件。如果找到,则此 init 进程负责启动系统,包括定位和安装实际根设备(如果有)。如果将嵌入式 cpio 档案解压到 rootfs 中后,rootfs 中不包含 init 程序,则内核将转到较旧的代码来定位和安装根分区,然后从中执行 /sbin/init 的某个变体。
这与旧的 initrd 有以下几个不同之处:
-
旧的 initrd 始终是一个单独的文件,而 initramfs 档案链接到 linux 内核映像中。(该目录linux-*/usr专门用于在构建期间生成此档案。)
-
旧的 initrd 文件是一个经过 gzip 压缩的文件系统映像(采用某些文件格式,例如 ext2,需要内置驱动程序到内核中),而新的 initramfs 存档是一个经过 gzip 压缩的 cpio 存档(类似于 tar,但更简单,请参阅 cpio(1) 和initramfs 缓冲区格式)。内核的 cpio 提取代码不仅非常小,而且它还是可以在启动过程中丢弃的 __init 文本和数据。
-
旧 initrd(称为 /initrd,而不是 /init)运行的程序进行了一些设置,然后返回到内核,而 initramfs 中的 init 程序预计不会返回到内核。(如果 /init 需要移交控制权,它可以使用新的根设备覆盖 / 并执行另一个 init 程序。请参阅下面的 switch_root 实用程序。)
-
当切换另一个根设备时,initrd 会先pivot_root,然后卸载ramdisk。但是 initramfs 是rootfs:您既不能pivot_root rootfs,也不能卸载它。相反,删除rootfs中的所有内容以释放空间(find -xdev / -exec rm ‘{}’ ‘;’),用新的根覆盖rootfs(cd /newmount; mount --move . /; chroot .),将stdin/stdout/stderr附加到新的/dev/console,然后执行新的init。
由于这是一个非常棘手的过程(并且需要在运行命令之前删除它们),klibc 包引入了一个辅助程序(utils/run_init.c)来为您完成所有这些工作。大多数其他包(例如 busybox)将此命令命名为“switch_root”。
填充initramfs
2.6 内核构建过程始终会创建一个 gzip 压缩的 cpio 格式的 initramfs 存档并将其链接到生成的内核二进制文件中。默认情况下,此存档为空(在 x86 上占用 134 个字节)。
配置选项 CONFIG_INITRAMFS_SOURCE(位于 menuconfig 中的常规设置中,位于 usr/Kconfig 中)可用于指定 initramfs 存档的源,该源将自动合并到生成的二进制文件中。此选项可以指向现有的 gzip 压缩的 cpio 存档、包含要存档的文件的目录或文本文件规范,例如以下示例:
dir /dev 755 0 0
nod /dev/console 644 0 0 c 5 1
nod /dev/loop0 644 0 0 b 7 0
dir /bin 755 1000 1000
slink /bin/sh busybox 777 0 0
file /bin/busybox initramfs/busybox 755 0 0
dir /proc 755 0 0
dir /sys 755 0 0
dir /mnt 755 0 0
file /init initramfs/init.sh 755 0 0
运行“usr/gen_init_cpio”(内核构建之后)以获取记录上述文件格式的使用消息。
配置文件的一个优点是不需要 root 访问权限即可在新存档中设置权限或创建设备节点。(请注意,这两个示例“文件”条目希望在 linux-2.6.* 目录下的“initramfs”目录中找到名为“init.sh”和“busybox”的文件。 有关更多详细信息,请参阅早期用户空间支持。)
内核不依赖外部 cpio 工具。如果您指定目录而不是配置文件,内核的构建基础结构将从该目录创建一个配置文件(usr/Makefile 调用 usr/gen_initramfs.sh),然后继续使用该配置文件打包该目录(通过将其提供给 usr/gen_init_cpio,后者是从 usr/gen_init_cpio.c 创建的)。内核构建cpio 时创建代码完全是自包含的,内核的启动提取也是自包含的。
您可能需要安装外部 cpio 实用程序来创建或提取您自己预先准备好的 cpio 文件以提供给内核构建(而不是配置文件或目录)。
以下命令行可以将 cpio 映像(通过上述脚本或内核构建)提取回其组件文件中:
cpio -i -d -H newc -F initramfs_data.cpio --no-absolute-filenames
下面的 shell 脚本可以创建一个预建的 cpio 档案,您可以用它代替上面的配置文件:
#!/bin/sh
if [ $# -ne 2 ]
then
echo "usage: mkinitramfs directory imagename.cpio.gz"
exit 1
fi
if [ -d "$1" ]
then
echo "creating $2 from $1"
(cd "$1"; find . | cpio -o -H newc | gzip) > "$2"
else
echo "First argument must be a directory"
exit 1
fi
外部initramfs镜像
如果内核启用了 initrd 支持,也可以将外部 cpio.gz 存档传递到 2.6 内核中以代替 initrd。在这种情况下,内核将自动检测类型(initramfs,而不是 initrd)并在尝试运行 /init 之前将外部 cpio 存档提取到 rootfs 中。
这具有 initramfs(无 ramdisk 块设备)的内存效率优势,但具有 initrd 的单独打包优势(如果您有非 GPL 代码想要从 initramfs 运行,而又不将其与 GPL 许可的 Linux 内核二进制文件混淆,那么这非常好)。
它还可用于补充内核的内置 initramfs 映像。外部存档中的文件将覆盖内置 initramfs 存档中的任何冲突文件。一些分销商还喜欢使用特定于任务的 initramfs 映像自定义单个内核映像,而无需重新编译。
为什么选择cpio而不是tar
- cpio 是一个标准。它已有几十年历史(从 AT&T 时代开始),并且已在 Linux 上广泛使用(在 RPM 中,Red Hat 的设备驱动程序磁盘)。它不像 tar 那样流行,因为传统的 cpio 命令行工具需要 truly_hideous 命令行参数。但这并没有说明存档格式,还有其他替代工具。
- 内核选择的 cpio 存档格式比任何(实际上有几十种)不同的 tar 存档格式都更简单、更干净(因此更容易创建和解析)。完整的 initramfs 存档格式在 buffer-format.rst 中进行了说明,在 usr/gen_init_cpio.c 中创建,并在 init/initramfs.c 中提取。这三个文件加起来总共不到 26k 的人类可读文本。
- GNU 项目对 tar 的标准化与 Windows 对 zip 的标准化大致相同。Linux 不属于任何一个组织,并且可以自由地做出自己的技术决策。
- 由于这是内核内部格式,因此它很容易成为一种全新的格式。内核提供了自己的工具来创建和提取此格式。使用现有标准是可取的,但不是必需的。
未来方向
如今 (2.6.16),initramfs 始终被编译,但并非始终被使用。内核会回退到旧式引导代码,只有当 initramfs 不包含 /init 程序时才会使用。回退是旧式代码,用于确保平稳过渡并允许早期引导功能逐渐移至“早期用户空间”(即 initramfs)。
迁移到早期用户空间是必要的,因为查找和安装真正的根设备很复杂。根分区可以跨越多个设备(RAID 或单独的日志)。它们可以在网络上(需要 DHCP、设置特定的 MAC 地址、登录到服务器等)。它们可以存在于可移动媒体上,具有动态分配的主/次编号和持续的命名问题,需要完整的 udev 实现来解决。它们可以被压缩、加密、写时复制、环回安装、奇怪的分区等等。
这种复杂性(不可避免地包括策略)在用户空间中得到了正确的处理。klibc busybox/uClibc 都在开发简单的 initramfs 包,以便将其放入内核构建中。
klibc 软件包现已被纳入 Andrew Morton 的 2.6.17-mm 树。内核当前的早期启动代码(分区检测等)可能会被迁移到默认的 initramfs 中,由内核构建自动创建和使用。