这里是 linux kernel Documentation 阅读系列第一篇,在这个系列中,我会记录每一次阅读到的 linux 内核文档。这一篇阅读的是 ramfs, rootfs and initramfs,主要是介绍 initramfs
。
What is ramfs?
ramfs
是 linux 中利用linux的disk caching
机制(page cache 和 dentry cache)实现的可动态伸缩的基于RAM做存储的文件系统。
文件一般放在磁盘上,当需要对其读写的时候会将其加载到内存中。为了提高文件的读写效率,根据局部性原理,linux 基本上都会文件缓存到内存中。
- 当文件的数据被 OS 从后端存储读取到内存的时候,因为这段数据可能之后会被用到,并不会被马上释放,这块数据将会被标记为
clean
,就是说可以被释放掉。只有当 VM 系统需要用到这块内存做其他事情的时候,这些数据才会被释放掉。 - 当有数据写到文件后,这段数据也会被标记为
clean
,但是仍然保存在内存中而不释放,以用作缓存的目的。只有当 VM 系统重新申请这块内存的时候,这段数据才会被释放掉。
对于 dentry cache
,linux 也有类似的机制,从而大大加快了对于目录的访问。
上述说的是我们常见文件系统的机制,对于 ramfs
而言,这里根本没有所谓的后端存储。当你要向 ramfs
写文件时,我们像原来一样,在内存上分配 page cache
和dentry cache
,但是这些 cache 不会被写到磁盘这些后端存储中 。所以,这些 cache根本不会被标记为 clean
,而会一直存在于内存中,VM系统也无法回收他们的内存,重新分配做它用。
实现 ramfs
需要的代码量非常的少,因为他们基本上就是依靠Linux现有的caching infastructure
。对于用户而言,我们只是把一个 disk cache
加载成文件系统。因此,ramfs
就不是通过menuconfig
可以被移去的可选模块,它底层的机制是 linux 所必须的。
下面做了一个简单的实验,这是当前系统能够看到的文件系统。
1 | vagrant@cosmos:~$ df -a |
我们可以通过下列命令自己创建 ramfs
1 | vagrant@cosmos:~$ mkdir ramfs |
ramfs and ramdisk
ramdisk
是比ramfs
出现更早的机制,是利用 RAM 模拟生成一个块设备,以此作为文件系统的后端存储。这个块设备是固定大小的,所以它上面 mount 的文件系统也是固定大小的。和实际的块设备一样,我们需要把page cache
从这块假的块设备复制到内存,然后把改变复制回去,对于 dentry cache
也是一样。除此之外,它还需要文件系统驱动(比如 ext2)去格式化和解释这段数据。
相较于ramfs
,ramdisk
会浪费更多的内存,占用更多的内存总线带宽,给 CPU 带来更多的工作,并且污染 CPU 的 cache。相较而言,ramfs
的实现机制更加简单和高效。
loopback devices
是导致 ramdisk
淘汰的另一个原因,它相对于ramdisk
而言提供了一种更加灵活和方便的方式来创建块设备,现在是通过文件而不是通过内存。
ramfs and tmpfs
ramfs
的一个问题是,你可以一直往 ramfs
里面写数据,直到你用完了所有的内存。而且 VM 系统也不能释放这段内存,因为 VM 认为这些数据应该被写到后端存储,而对于 ramfs
而言他没有后端存储。因此,只有 root用户能够往 ramfs
写数据。
为了解决上述问题,linux 内核开发者又发明了 tmpfs
,给添加了大小的限制和普通用户写数据的权限。
特性 | tmpfs | ramfs |
---|---|---|
达到空间上限时继续写入 | 提示错误信息并终止 | 可以继续写尚未分配的空间 |
是否固定大小 | 是 | 否 |
是否使用swap | 是 | 否 |
具有易失性 | 是 | 是 |
What is rootfs?
rootfs
是ramfs
或者tmpfs
的一种特殊实例,根文件系统包含系统启动时所必须的目录和关键性的文件,以及使其他文件系统得以挂载(mount)所必要的文件。例如:
- init进程的应用程序必须运行在根文件系统上
- 根文件系统提供了根目录“/”
- linux挂载分区时所依赖的信息存放于根文件系统/etc/fstab这个文件中
- shell命令程序必须运行在根文件系统上,譬如ls、cd等命令
一套linux体系,只有内核本身是不能工作的,必须要rootfs(上的etc目录下的配置文件、/bin /sbin等目录下的shell命令,还有/lib目录下的库文件等···)相配合才能工作。
Linux启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。成功之后可以自动或手动挂载其他的文件系统。
下面是 linux 的内核代码。rootfs是基于内存的文件系统,所有操作都在内存中完成;也没有实际的存储设备,所以不需要设备驱动程序的参与。基于以上原因,linux在启动阶段使用rootfs文件系统,当磁盘驱动程序和磁盘文件系统成功加载后,linux系统会将系统根目录从rootfs切换到磁盘文件系统。
1 | init/main.c-> |
下面是 init_rootfs
的代码,可以看到,如果 CONFIG_TMPFS
开启,rootfs 将会默认使用 tmpfs
,否则使用 ramfs
。
1 | int __init init_rootfs(void) |
What is initramfs?
initramfs是一种ramfs文件系统,在内核启动完成后把它复制到rootfs中,作为内核初始的根文件系统,它的任务是挂载系统真正的根文件系统。所有的 2.6 版本的 linux 内核都包含一个gzip 压缩过的 cpio 存档,在kernel 启动的时候,将会将其解压成 rootfs
。解压之后,内核将会检查 rootfs 是否有一个 init
文件,如果有的话,将会执行 init
程序作为 PID 为 1 的进程。在这之后,init
进程将会负责其启动整个系统,包括找到并加载真正的根设备。
相对于老的 initrd
机制,initramfs
有以下几点区别:
- 老的
initrd
总是一个独立的文件,而initramfs
存档是被链接到内核镜像中去的 - 老的
initrd
文件是一个 gzip 压缩过的文件系统镜像,新的initramfs
是 gzip 压缩过的 cpio存档,相对而言更简单 - 以往的基于ramdisk 的initrd 使用pivot_root命令切换到新的根文件系统,然后卸载ramdisk。但是initramfs是rootfs,而rootfs既不能pivot_root,也不能umount。为了从initramfs中切换到新根文件系统,需要作如下处理:
- 删除rootfs的全部内容,释放空间
find -xdev / -exec rm '{}' ';'
- 安装新的根文件系统,并切换
cd /newmount; mount --move . /; chroot .
- 把stdin/stdout/stderr 附加到新的/dev/console,然后执行新文件系统的init程序
- 删除rootfs的全部内容,释放空间
上述步骤比较麻烦,而且要解决一个重要的问题:第一步删除rootfs的所有内容也删除了所有的命令,那么后续如何再使用这些命令完成其他步骤?busybox的解决方案是,提供了switch_root命令,完成全部的处理过程,使用起来非常方便。
switch_root
命令的格式是:
1 | $ switch_root [options] <newrootdir> <init> <args to init> |
newrootdir
是实际的根文件系统的挂载目录,执行switch_root命令前需要挂载到系统中- init
是实际根文件系统的init程序的路径,一般是/sbin/init;
- args to init`则是传递给实际的根文件系统的init程序的参数,也是可选的。
需要特别注意的是:switch_root命令必须由PID=1的进程调用,也就是必须由initramfs的init程序直接调用,不能由init派生的其他进程调用,否则会出错,提示:switch_root: not rootfs
。也是同样的原因,init脚本调用switch_root命令必须用exec命令调用,否则也会出错,提示:switch_root: not rootfs
Contents of initramfs
An initramfs archive is a complete self-contained root filesystem for Linux.
1 | $ cat > hello.c << EOF |