rootfs 与 Root FileSystem 与 FHS

前言

最近在看 lanxintao 老师博客的 Docker 的原理 时,看到了 rootfs 这个概念,虽然了解过文件系统和 Linux 的 VFS,但是对 rootfs 这个概念还是不太清楚,所以就查了一下相关文章。这一查不要紧,rootfs、Root FileSystem、FHS 三个概念在各个文章的描述似乎有冲突。举例来说,UnderStanding The Linux Kernel 3rd Edition 书中的描述是:

As mentioned in the section “An Overview of the Unix Filesystem” in Chapter 1,
Unix directories build a tree whose root is the / directory. The root directory is contained in the root filesystem, which in Linux, is usually of type Ext2 or Ext3. All other filesystems can be “mounted” on subdirectories of the root filesystem.

Linux Filesystem Hierarchy Standard 中说:

The contents of the root filesystem must be adequate to boot, restore, recover, and/or repair the system.

然而在 kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt 中却说:

Rootfs is a special instance of ramfs (or tmpfs, if that's enabled), which is
always present in 2.6 systems. You can't unmount rootfs for approximately the
same reason you can't kill the init process; rather than having special code
to check for and handle an empty list, it's smaller and simpler for the kernel
to just make sure certain lists can't become empty.

Most systems just mount another filesystem over rootfs and ignore it. The
amount of space an empty instance of ramfs takes up is tiny.

如果 rootfs 是一个特殊的 ramfs 实例,大多数系统都会挂载另一个文件系统并覆盖在 rootfs 上,那么 rootfs 怎么具有 recover 和 repair 的能力呢?而且 rootfs 的类型是 Ext2 或 Ext3 又有什么意义呢?在我十分迷茫的时候,我突然有一个十分合理的想法:有没有可能,rootfs 并不是 root filesystem 的缩写?至此,我才突然领悟到为什么这三者的描述会有冲突,又查询了下 rootfs 究竟是个什么东西,便有了这篇文章。

rootfs

正如上文所说,rootfs 是一个 ramfs 的实例,那么我们首先要知道 ramfs 是什么。

ramfs 是一个内存文件系统(由名字就可以看出),它的主要目的就是提供了 Linux 的磁盘缓存机制(页缓存与目录项缓存)的导出。我们知道 Linux 会将文件缓存在内存中,待到需要时写入后备存储(通常为磁盘),而 ramfs 作为一个内存文件系统,不存在后备存储。因此写入 ramfs 的数据页面始终是 dirty,不会被释放。

介绍完 ramfs,我们再来看看 tmpfs。我们根据上文可知,写入 ramfs 的页面始终不会被释放,这很显然有着极大的风险,因此,通常只有 root 用户才有权限写入 ramfs 所 mount 的目录。不过这样的限制也过大了些,因此 tmpfs 作为 ramfs 的派生而诞生。它相比于 ramfs 增加了可写入的文件大小限制,最重要的是,它允许向 swap space 写入数据,这样普通用户也可以写入 tmpfs 所 mount 的目录了。

最后我们来说明 rootfs,rootfs 就是一个 ramfs(或者是 tmpfs,如果其被启用的话)的一个特殊实例。这样我们就理解了为什么大多数系统都会直接 mount 另一个文件系统覆盖在 rootfs 上了,因为它的任务只有导出缓存机制,初始化一些数据结构和外设,然后就可以等待真正使用的文件系统挂载上来了。

最后我们来讨论一下有趣的小 tips,前文我使用了覆盖这个词,但其实 rootfs 并不会因此消失,而是类似栈一样被挂载的文件系统覆盖(而看起来消失了)。如果我们 unmount 这个覆盖的文件系统(虽然极其不推荐),rootfs 就会重新出现。并且根据此 Unix 的文件系统特点,我们可以巧妙的通过 mount 文件系统到一个目录下来实现隐藏此目录文件的目的。

Root FileSystem

了解完 rootfs 之后,其实 Root FileSystem 就没什么好说的了。它就是指 Linux 根目录(在我查询资料的过程,此处几乎可以说为覆盖 rootfs 的那个,且不指 rootfs)的文件系统,遵循 FHS 规范。不过为了这章节不那么空洞,我们来讲讲 rootfs 和 此 Root FileSystem 的安装过程。

在 Linux 启动时,它会安装 rootfs 文件系统,这是由 init_rootfs() 方法和 init_mount_tree() 方法实现的。init_rootfs() 方法仅仅注册这个特殊的文件系统类型 rootfs,主要操作都在 init_mount_tree() 方法中。init_mount_tree() 方法会调用 do_kern_mount() 方法并将文件系统类型参数 rootfs 传递给它,其就可以调用 rootfs_get_sb() 来初始化超级块并分配相关的索引节点对象和目录项对象。随后为进程 0 创建一个 namespace 并初始化(如果是缺省的话)所有其他进程的 namespace。最后将进程 0 的根目录和当前工作目录都设置为根文件系统。

随后 Linux 便会在系统初始化结束前挂载实际的根文件系统,只考虑该文件系统存储在磁盘上,主要调用 prepare_namespace() 方法。其主要调用 mount_root() 方法来创建设备文件 /dev/root,随后分配文件系统类型名的链表,对每个元素调用 sys_mount(),最后调用 sys_chdir("/root") 来设置当前工作目录。随后将 rootfs 根目录上已安装的文件系统重新 mount 到新的根文件系统上即可。

FHS

最后我们就可以来简单的说明一下 FHS 规范了。关于细节方面,直接去官网查看即可,在这里就简单的提及一下我新学到的或者是有意思的部分。

我们可以将文件分为 shareable 和 unshareable 两类,这样区分是因为 shareable 文件可以被多个系统共享,如果我们只需 mount 几个存储 shareable 的目录就可以构成系统的话就十分方便,不再需要一个个剔除 unshareable 文件。此外,文件还可以分为 static 和 variable 两类,这种区分就很直观了。总的来说,我们可以有这样一个表格:

ShareableUnshareable
static/usr/etc
/opt/boot
variable/var/mail/var/run
/var/spool/news/var/lock

最后我们来看看 /lib 目录,这个目录包含用于引导系统和运行 Root FileSystem 中命令所需的共享库,如 /bin/sbin 中的命令。/bin 目录主要包含普通用户和管理员都可能需要在单用户下使用的命令,并且要求可以在没有文件系统挂载时仍需可用,对于 /sbin 目录则是用于系统管理的工具,通常只有管理员需要使用,来 boot,restore,recover 和 repair 系统。

Filesystem Hierarchy Standard
kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
The Linux System Administrator's Guide
UnderStanding The Linux Kernel 3rd Edition
Last modification:April 15, 2024