Ramfs, rootfs 和 initramfs

什么是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

  1. cpio 是一个标准。它已有几十年历史(从 AT&T 时代开始),并且已在 Linux 上广泛使用(在 RPM 中,Red Hat 的设备驱动程序磁盘)。它不像 tar 那样流行,因为传统的 cpio 命令行工具需要 truly_hideous 命令行参数。但这并没有说明存档格式,还有其他替代工具。
  2. 内核选择的 cpio 存档格式比任何(实际上有几十种)不同的 tar 存档格式都更简单、更干净(因此更容易创建和解析)。完整的 initramfs 存档格式在 buffer-format.rst 中进行了说明,在 usr/gen_init_cpio.c 中创建,并在 init/initramfs.c 中提取。这三个文件加起来总共不到 26k 的人类可读文本。
  3. GNU 项目对 tar 的标准化与 Windows 对 zip 的标准化大致相同。Linux 不属于任何一个组织,并且可以自由地做出自己的技术决策。
  4. 由于这是内核内部格式,因此它很容易成为一种全新的格式。内核提供了自己的工具来创建和提取此格式。使用现有标准是可取的,但不是必需的。

未来方向

如今 (2.6.16),initramfs 始终被编译,但并非始终被使用。内核会回退到旧式引导代码,只有当 initramfs 不包含 /init 程序时才会使用。回退是旧式代码,用于确保平稳过渡并允许早期引导功能逐渐移至"早期用户空间"(即 initramfs)。

迁移到早期用户空间是必要的,因为查找和安装真正的根设备很复杂。根分区可以跨越多个设备(RAID 或单独的日志)。它们可以在网络上(需要 DHCP、设置特定的 MAC 地址、登录到服务器等)。它们可以存在于可移动媒体上,具有动态分配的主/次编号和持续的命名问题,需要完整的 udev 实现来解决。它们可以被压缩、加密、写时复制、环回安装、奇怪的分区等等。

这种复杂性(不可避免地包括策略)在用户空间中得到了正确的处理。klibc busybox/uClibc 都在开发简单的 initramfs 包,以便将其放入内核构建中。

klibc 软件包现已被纳入 Andrew Morton 的 2.6.17-mm 树。内核当前的早期启动代码(分区检测等)可能会被迁移到默认的 initramfs 中,由内核构建自动创建和使用。

相关推荐
山茶花开时。28 分钟前
[SAP ABAP] 静态断点的使用
开发语言·sap·abap
纠结哥_Shrek28 分钟前
Java 有很多常用的库
java·开发语言
蓝染k9z1 小时前
在Ubuntu上使用Docker部署DeepSeek
linux·人工智能·ubuntu·docker·deepseek+
苏-言1 小时前
Linux环境下的Java项目部署技巧:安装 Mysql
linux·运维·mysql
加油,旭杏2 小时前
【go语言】函数
开发语言·后端·golang
南玖yy2 小时前
C语言:结构体
c语言·开发语言
代码对我眨眼睛2 小时前
重回C语言之老兵重装上阵(十三)C 预处理器
linux·c语言
张文君2 小时前
ubuntu直接运行arm环境qemu-arm-static
linux·arm开发·ubuntu
lljss20202 小时前
在 WSL2 中重启 Ubuntu 实例
linux·运维·ubuntu
engchina3 小时前
在 Ubuntu 上安装 Node.js 23.x
linux·ubuntu·node.js