Linux 文件系统Ramfs, rootfs and initramfs

文章目录

  • 前言
  • 一、ramfs简介
  • 二、ramfs 和 ram disk比较
  • 三、ramfs 和 tmpfs的比较
  • 四、rootfs
  • 五、initramfs
  • 六、Populating initramfs
  • 七、External initramfs images
  • 八、Contents of initramfs
  • 九、Why cpio rather than tar?
  • 十、Future directions

前言

这篇文章参考内核官方文档:Ramfs, rootfs and initramfs

关于Ramfs, rootfs and initramfs内核文档一直没有更新,Ramfs, rootfs and initramfs 这篇文档写的时候内核版本是:2.6.16。

一、ramfs简介

Ramfs是Linux中的一个简单文件系统,它利用了现有的磁盘缓存机制(页面缓存和目录项缓存),使用系统的物理内存作为存储空间,将其作为一个可调整大小的基于RAM的文件系统。

通常情况下,Linux会将所有文件缓存在内存中。从后备存储(通常是文件系统所挂载的块设备)读取的数据页面会被保留下来,以便在需要时再次使用,但被标记为“干净”(可释放),以便虚拟内存系统在需要内存时可以释放它们。同样,写入文件的数据在写入后被标记为干净,但仍然保留在缓存中以供缓存目的,直到虚拟内存重新分配内存。类似的机制(目录项缓存)大大加快了对目录的访问速度。

而使用ramfs时,没有后备存储。写入ramfs的文件会像往常一样分配目录项和页面缓存,但没有特定的位置(磁盘块设备)可以写入。这意味着这些页面永远不会被标记为干净,因此虚拟内存在寻找回收内存时无法释放它们。

实现ramfs所需的代码量非常小,因为所有工作都由现有的Linux缓存基础设施完成。基本上,你是将磁盘缓存挂载为一个文件系统。正因如此,ramfs不是通过menuconfig可选移除的组件,因为几乎不会有节省空间的效果。

ramfs是Linux中的一个简单文件系统,通过利用现有的磁盘缓存机制来创建基于RAM的文件系统。它没有持久化存储,常用于临时存储、系统初始化等需要临时非持久性文件系统的场景。它提供了直接在内存中快速访问数据的能力,而无需涉及物理存储设备的读写。需要注意的是,ramfs在系统重启或断电时会丢失所有数据。

Ramfs可以像其他文件系统一样处理文件和目录。你可以通过常规的文件和目录操作来创建、读取、写入和删除文件。此外,ramfs还支持权限和属性的管理。

还有另一个类似的文件系统叫做tmpfs,它更常用。Tmpfs与ramfs类似,但增加了在内存不足时将数据写入交换空间的功能。这使得tmpfs可以提供比可用物理内存更大的文件系统大小的假象。因此,tmpfs更适合需要临时存储大量数据的场景。

二、ramfs 和 ram disk比较

在旧版本中,"ram disk"机制会将一块RAM区域作为合成块设备,并将其用作文件系统的后备存储。这个块设备的大小是固定的,因此挂载在其上的文件系统也是固定大小的。使用ram disk还需要将内存从虚拟块设备复制到页面缓存(并复制更改),以及创建和销毁目录项。此外,它需要一个文件系统驱动程序(如ext2)来格式化和解释这些数据。

与ramfs相比,这种方法浪费了内存(和内存总线带宽),给CPU带来了不必要的工作,并且污染了CPU缓存。(有一些通过操作页表来避免这种复制的技巧,但它们非常复杂且最终的开销与复制操作相当。)更重要的是,ramfs正在做的所有工作都必须无论如何发生,因为所有文件访问都经过页面和目录缓存。RAM disk是多余的;ramfs在内部更简单。

Ramdisk半过时的另一个原因是引入了回环设备(loopback devices),它提供了一种更灵活和方便的方式来创建合成块设备,而不是使用内存块。现在可以从文件中创建合成块设备。有关详细信息,请参阅"losetup"。

综上所述,以下是两者的差异:
(1)内存利用:旧的ram disk机制在内存中分配了一个固定大小的块设备,如果分配的空间没有完全利用,往往会导致内存浪费。相比之下,ramfs根据内存需求动态调整大小,高效地利用可用内存资源。

(2)CPU开销:ram disk机制需要额外的CPU开销,因为需要在虚拟块设备和页面缓存之间复制数据。这个复制过程消耗CPU周期和内存总线带宽。而ramfs利用现有的页面和目录缓存,消除了不必要的数据复制,降低了CPU开销。

(3)简单性:相比较于ram disk机制,ramfs在内部更为简单。它无需额外的文件系统驱动程序(如ext2)来格式化和解释块设备上的数据。ramfs直接利用现有的缓存基础设施,使其更加轻量和高效。

(4)灵活性和便利性:引入回环设备(loopback devices)提供了一种更灵活、更便利的方法来创建合成块设备。回环设备允许从常规文件创建块设备,提供了更多管理存储资源的灵活性。这使得旧的ram disk机制变得不那么必要,同时提供了更好的替代方案。

(5)性能:ramfs利用现有的页面和目录缓存,这些缓存针对文件访问进行了优化。它提供了更快、更高效的对存储在内存中的数据访问,无需额外的复制或解释过程。相比较于旧的ram disk机制,ramfs具有更好的性能表现。

三、ramfs 和 tmpfs的比较

ramfs的一个缺点是,您可以不断向其中写入数据,直到填满所有内存,而虚拟内存(VM)无法释放它,因为VM认为文件应该被写入后备存储(而不是交换空间),但ramfs没有任何后备存储。因此,只有root用户(或受信任的用户)应该被允许对ramfs挂载点进行写访问。

为了解决这个问题,创建了一个名为tmpfs的ramfs衍生版本,它添加了大小限制和将数据写入交换空间的能力。普通用户可以被允许对tmpfs挂载点进行写访问。

tmpfs特点:
(1)目的:tmpfs是在ramfs的基础上进行改进的,旨在解决无限内存消耗的问题。它增加了设置大小限制的功能,并在内存不足时将数据写入交换空间。

(2)大小限制:tmpfs允许管理员为文件系统设置大小限制。这确保tmpfs挂载不会超出指定的限制,避免过度消耗内存。当达到限制时,尝试向tmpfs写入更多数据将导致错误,指示空间不足。

(3)交换空间利用:当内存完全使用时,tmpfs可以利用交换空间作为后备存储。如果系统物理内存不足,tmpfs可以将访问频率较低的数据写入交换空间,释放宝贵的物理内存以供更关键的进程使用。

(4)用户访问:与ramfs不同,tmpfs允许普通用户对tmpfs挂载点具有写访问权限。这使得tmpfs适用于非root用户创建临时文件或共享内存区域的场景。

(5)性能考虑:与传统文件系统相比,tmpfs提供更快的数据访问,因为它完全驻留在内存中。然而,需要注意的是,过度使用tmpfs,特别是与交换空间利用相结合时,可能会对系统性能产生影响,因为会增加磁盘I/O操作。

(6)配置:通过修改/etc/fstab文件中的相关设置,可以调整tmpfs的配置。这使得管理员可以根据特定需求指定大小限制、挂载选项和其他参数。

总而言之,tmpfs是ramfs的一个衍生版本,通过添加大小限制和将数据写入交换空间来解决无限内存消耗的问题。它允许普通用户具有写访问权限,适用于创建临时文件和共享内存区域的场景。然而,管理员应仔细管理大小限制并监控使用情况,以防止过度消耗内存并对系统性能产生潜在影响。

更多tmpfs信息,请参阅Tmpfs。

四、rootfs

特殊实例:Rootfs是ramfs或tmpfs(如果启用)的特殊实例,始终存在于Linux 2.6内核的系统中。它作为在引导过程的早期阶段挂载的初始根文件系统。

无法卸载限制:由于内核的设计考虑,无法卸载rootfs。与无法终止init进程类似,对于内核来说,确保某些关键列表(如根文件系统)不会变为空更有效。这简化了代码并确保系统的稳定性。

在大多数系统中的使用:在大多数系统中,rootfs在引导过程中作为初始文件系统被挂载,然后另一个文件系统被挂载在其上。实际上,rootfs被忽略,它的目的是为文件系统层次结构提供一个起点。

最小的空间占用:用于rootfs的空的ramfs或tmpfs实例占用的空间可以忽略不计。这是因为文件系统最初是空的,直到添加文件和目录,为其分配的空间非常小。

默认文件系统:如果内核配置选项CONFIG_TMPFS被启用,则rootfs将默认使用tmpfs而不是ramfs。这允许使用tmpfs提供的功能,例如设置大小限制和利用交换空间。但是,可以通过在内核命令行中添加"rootfstype=ramfs"参数来强制使用ramfs。

总之,rootfs是ramfs或tmpfs的特殊实例,在引导过程中作为初始根文件系统使用。由于内核设计选择,无法卸载rootfs。大多数系统在rootfs上挂载另一个文件系统并忽略它。空的rootfs实例占用的空间很小。根据内核配置,默认使用的文件系统取决于是否启用了tmpfs。

五、initramfs

所有2.6版本的Linux内核都包含一个经过gzip压缩的“cpio”格式存档,当内核启动时会将其解压到rootfs中。解压后,内核会检查rootfs是否包含一个名为“init”的文件,如果存在,则将其作为PID 1执行。如果找到了这个init进程,它负责将系统完全启动起来,包括定位和挂载真正的根设备(如果有的话)。如果在嵌入的cpio存档解压到rootfs后,rootfs中没有包含init程序,那么内核将继续使用旧的代码来定位和挂载根分区,然后执行其中的某个变种,如/sbin/init。

Rootfs存档:2.6版Linux内核中包含一个经过gzip压缩的“cpio”格式存档,其中包含一组文件和目录,构成初始根文件系统。该存档被嵌入在内核镜像中。

解压和初始化:在引导过程中,内核将rootfs存档的内容解压到内存中,创建初始的文件系统层次结构。解压后,内核会检查rootfs中是否存在一个名为“init”的文件。

Init进程:如果找到了“init”文件,则将其作为PID 1执行。这个进程通常称为init进程,它在系统启动过程中起着关键的作用。它执行诸如配置设备、启动系统服务和启动用户会话等任务。

挂载真实根设备:执行init进程后,它负责定位和挂载实际的根设备。它搜索根文件系统,可以通过内核参数指定或通过引导加载程序配置等机制确定。

备用机制:如果rootfs存档中不存在“init”文件,则内核会回退到旧的代码路径。在这种情况下,它依赖传统方法来定位和挂载根分区,然后从挂载的根文件系统中执行“/sbin/init”的变种程序。

自定义和替代方案:在内核编译或引导配置过程中,可以自定义rootfs存档、是否存在“init”文件以及根设备的位置。根据系统配置,还可以使用诸如systemd或BusyBox的init等传统init进程的替代方案。

新的initramfs与旧的initrd在几个方面有所不同:
存储方式:旧的initrd是一个单独的文件,需要由引导加载程序加载并在引导过程中传递给内核。相比之下,initramfs存档被整合到Linux内核映像中。在内核构建过程中,目录"linux-*/usr"负责生成initramfs存档。

文件系统格式:旧的initrd文件是一个经过gzip压缩的文件系统镜像(使用某种文件格式,如ext2,需要在内核中内置驱动程序),而新的initramfs存档是一个经过gzip压缩的cpio存档(类似于tar,但更简单,请参阅cpio和initramfs buffer format)。内核的cpio解压缩代码非常小,它是__init文本和数据,在引导过程中可以丢弃。

运行程序:旧的initrd运行的程序(称为/initrd,而不是/init)进行一些设置,然后返回给内核,而initramfs中的init程序不会返回给内核(如果/init需要交出控制权,它可以使用新的根设备覆盖/mount,并执行另一个init程序。请参阅下面的switch_root实用程序)。

切换根设备:在切换到另一个根设备时,initrd会执行pivot_root并卸载ramdisk。但是initramfs是rootfs:既不能执行pivot_root操作,也不能卸载它。相反,需要删除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”。

六、Populating initramfs

2.6内核构建过程始终会创建一个经过gzip压缩的cpio格式的initramfs存档,并将其链接到生成的内核二进制文件中。默认情况下,此存档为空(在x86上占用134字节)。

配置选项CONFIG_INITRAMFS_SOURCE(在menuconfig的General Setup中,位于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

Initramfs存档:
Initramfs存档是在Linux内核引导过程的早期阶段使用的初始根文件系统,它是一个压缩的cpio格式存档。
内核构建过程会自动生成一个initramfs存档,并将其链接到生成的内核二进制文件中。
默认情况下,initramfs存档是空的,但您可以使用CONFIG_INITRAMFS_SOURCE配置选项指定存档的来源。

CONFIG_INITRAMFS_SOURCE:
CONFIG_INITRAMFS_SOURCE配置选项位于menuconfig的General Setup部分(位于usr/Kconfig中),允许您指定initramfs存档的来源。
该选项可以指向不同的来源,包括现有的经过gzip压缩的cpio存档、包含要归档文件的目录或文本文件规范。

内核构建过程和自包含性:
内核的构建过程不依赖于外部cpio工具来创建initramfs存档。
如果您指定一个目录作为来源,内核的构建基础设施会使用usr/gen_initramfs.sh自动生成一个配置文件。
内核构建时的cpio创建代码位于usr/gen_init_cpio.c中,完全自包含。
类似地,内核引导时的提取器在引导过程中负责提取initramfs存档,也是自包含的。

外部cpio工具:
虽然内核的构建过程不需要外部cpio工具,但如果您想创建或提取自己预先准备的cpio文件以供内核构建使用,则可能需要这些工具。
这些外部cpio工具将用于创建或提取cpio文件,而不是使用配置文件或目录作为initramfs存档的来源。

总体而言,内核的构建过程提供了一个自包含的机制来创建和使用initramfs存档,允许灵活指定存档的来源,并减少对外部工具的依赖性。

以下命令行可以将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

七、External initramfs images

(1)外部cpio.gz存档作为Initrd的替代:
如果内核启用了initrd支持,可以将外部的cpio.gz存档传递给内核,以取代使用initrd。
内核会自动检测存档的类型(initramfs而不是initrd),并在尝试运行/init程序之前将外部cpio存档提取到根文件系统(rootfs)中。
这种方法结合了initramfs的内存效率优势(无需ramdisk块设备)和initrd的独立打包方式,提供了灵活性和兼容性。

(2)内存效率和独立打包:
通过使用外部cpio.gz存档而不是initrd,您可以享受initramfs的内存效率优势,它不需要ramdisk块设备。
此外,外部存档的独立打包允许包含非GPL代码,可以从initramfs中执行,而不会与GPL许可的Linux内核二进制文件混合。
这种分离非常有用,当您想要在Linux内核中运行专有或非GPL代码,而不违反许可限制时。

(3)补充内核内置的initramfs镜像:
外部cpio.gz存档还可以用于补充内核内置的initramfs镜像。
外部存档中存在的文件将覆盖内置initramfs镜像中的冲突文件,允许自定义和添加特定的文件或配置,而无需重新编译内核。
这种方法特别适用于发行商,他们希望使用特定任务的initramfs镜像自定义单个内核镜像,简化部署和维护过程。

总体而言,使用外部cpio.gz存档提供了灵活性、内存效率和自定义选项,可以更好地利用Linux内核的initramfs功能。它允许包含非GPL代码,补充内置initramfs镜像,并提供了传统initrd用法的更高效替代方案。

八、Contents of initramfs

initramfs归档是一个完整的、自包含的Linux根文件系统。如果您还不了解启动并运行最小根文件系统所需的共享库、设备和路径,下面是一些参考资料:
https://www.tldp.org/HOWTO/Bootdisk-HOWTO/
https://www.tldp.org/HOWTO/From-PowerUp-To-Bash-Prompt-HOWTO.html
http://www.linuxfromscratch.org/lfs/view/stable/

(1)klibc:
“klibc”软件包是一个小型的C库,专为与早期用户空间代码进行静态链接而设计。
它提供了在完整的C库(如glibc)可用之前,在引导过程的早期阶段所需的基本功能和实用工具。
klibc专门针对内存和存储资源有限的小型嵌入式系统或轻量级环境进行设计。
它采用BSD许可证,允许更自由地使用和重新分发。

(2)uClibc:
uClibc(Micro C库)是另一种流行的用于嵌入式系统的替代方案。
它旨在提供比glibc更小的占用空间和更低的内存使用,适用于资源受限的环境。
uClibc采用LGPL(Lesser General Public License)许可证,允许与专有软件进行静态和动态链接。

(3)busybox:
busybox是一组轻量级且常用的Unix实用工具的集合,打包成一个单一的可执行文件。
它提供了一个紧凑高效的替代方案,取代了通常在类Unix系统中找到的许多单独的二进制文件。
busybox采用GPL(General Public License)许可证,要求任何修改或衍生作品都必须在相同许可证下发布。

(4)glibc:
glibc(GNU C库)是大多数Linux发行版中使用的标准C库。
它提供了广泛的功能和特性,适用于通用计算。
然而,与uClibc或klibc等替代方案相比,glibc相对较大且对资源的消耗较高。
对于内存和资源有限的小型嵌入式系统或特定环境,glibc可能不是最优选择。

总结一下,klibc、uClibc和busybox都是标准glibc库的替代方案,专为需要小型占用空间和高效资源利用的特定用例而设计。这些库提供了基本功能,同时最小化内存使用,非常适用于嵌入式系统和其他资源受限的环境。

九、Why cpio rather than tar?

这个决定是在2001年12月做出的。具体讨论内容请参考以下两篇文章:
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1538.html
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1587.html

(1)cpio是一个标准格式,它诞生于几十年前的AT&T时期,并且在Linux系统中广泛使用,尤其是在RPM软件包和Red Hat的设备驱动盘中。尽管cpio被使用广泛,但它不像tar那样受欢迎,主要是因为传统的cpio命令行工具需要非常复杂和难以理解的命令参数。
1996年,《Linux Journal》杂志发表了一篇关于cpio的文章,你可以在以下链接中找到该文章:
http://www.linuxjournal.com/article/1213

(2)Linux内核选择的cpio归档格式旨在相对于各种tar归档格式更加简单和清晰。选择使用cpio的决策是基于其易于创建和解析的特点。尽管tar具有多种不同的归档格式和选项,cpio提供了一种更直接的方法。

(3)要了解Linux内核中使用的完整initramfs归档格式,可以参考buffer-format.txt文件。该文件解释了initramfs归档的结构和组织方式。usr/gen_init_cpio.c文件负责生成initramfs归档,而init/initramfs.c文件在内核初始化过程中处理归档的提取。这些文件包含简明扼要且易于阅读的格式说明,总共不到26千字节的文本内容。

(4)需要注意的是,GNU项目选择标准化使用tar与Linux内核选择使用cpio是两个独立的决策。作为一个独立的操作系统,Linux有自由根据自身需求做出技术选择,不受其他项目或平台的决策限制。

(5)尽管出于兼容性和互操作性的考虑,使用现有的标准格式如cpio是首选,但内核也可以选择完全新的格式。内核提供了自己的工具来创建和提取cpio归档,确保对该格式的无缝集成和支持。

总体而言,Linux内核选择cpio格式展示了Linux社区在技术决策方面的灵活性和自主权,以最好地满足内核的需求和设计原则。

十、Future directions

这篇文章写的时候内核版本是:2.6.16。

在当今(2.6.16)的内核版本中,initramfs始终被编译进内核,但并非总是被使用。如果initramfs中不包含/init程序,内核将回退到传统的引导代码。这种回退是为了确保平稳过渡,并逐步将早期引导功能移至“早期用户空间”(即initramfs)。

在Linux内核中,initramfs(初始RAM文件系统)作为早期用户空间环境,在启动过程中被加载到内存中。它的目的是在转换到实际的根文件系统之前,处理与查找和挂载真正的根设备相关的复杂任务。

查找和挂载根设备的复杂性来自于各种因素。例如,根分区可以跨越多个设备,比如在RAID配置中或使用独立的日志设备时。它也可以位于网络上,需要通过DHCP获取网络设置,设置特定的MAC地址进行网络识别,并可能需要与服务器进行身份验证。此外,根设备也可以存在于可移动介质上,导致动态分配的主/次设备号和持久化命名方面的挑战,需要使用完整的udev实现来解决。此外,根设备可以被压缩、加密、写时复制、回环挂载,或者具有非常规的分区方案。

为了有效处理这种复杂性并将与策略相关的决策与内核分离,转向早期用户空间(特别是initramfs)变得必要。为此,开发了诸如klibc和busybox/uClibc等用户空间工具,用于创建简单的initramfs软件包,可以轻松集成到内核构建过程中。

例如,klibc软件包已被接受到Andrew Morton的2.6.17-mm树中,表示其包含在内核源代码树中以供进一步开发。计划是将内核当前的早期引导代码,包括分区检测和相关功能,迁移到默认的initramfs中。这个默认的initramfs将在内核构建过程中自动生成和使用,简化引导过程,并为处理查找和挂载根设备的复杂性提供更灵活和可管理的解决方案。

通过使用initramfs,Linux可以逐渐将早期引导功能的执行从内核空间转移到用户空间,为处理复杂的启动要求提供了更可定制和可扩展的环境。

你可能感兴趣的:(Linux,文件系统,linux,c语言)