关于 tmpfs

“tmpfs” 是 Linux 内核中另一个让人困惑的名字,它的实现是在 mm/shmem.c,”shmem” 猛一看和 “tmpfs” 根本不沾边,虽然我们知道 tmpfs 是基于内存的!我们可以通过看一下 tmpfs 都被用到哪些地方来了解它到底为什么叫这个名字。

你的桌面 Linux 系统中基本上都会挂载了 tmpfs:

% grep tmpfs /proc/mounts
devtmpfs /dev devtmpfs rw,seclabel,nosuid,relatime,size=1958956k,nr_inodes=489739,mode=755 0 0
tmpfs /dev/shm tmpfs rw,seclabel,nosuid,nodev,relatime 0 0
tmpfs /run tmpfs rw,seclabel,nosuid,nodev,relatime,mode=755 0 0
tmpfs /sys/fs/cgroup tmpfs rw,seclabel,nosuid,nodev,noexec,relatime,mode=755 0 0
tmpfs /media tmpfs rw,rootcontext=system_u:object_r:mnt_t:s0,seclabel,nosuid,nodev,noexec,relatime,mode=755 0 0

正如这篇文章中提到的,/dev/shm 是 POSIX IPC 用到的,用来实现进程间通信。除了 sem_open(3) 的中 oflag 参数,基本上看不出来和文件有什么相关。

除此之外,另一个用到它的地方是 anonymous shared mapping!

mmap_region():

        if (file) {
        //...
        } else if (vm_flags & VM_SHARED) {
                error = shmem_zero_setup(vma);
                if (error)
                        goto free_vma;
        }

shmem_zero_setup() 在内核通过 kern_mount() 挂载的(用户不可见的) tmpfs 的根目录中创建了一个”dev/zero”的文件,注意,这里可以重复创建哦,因为内核跳过了类似 may_create() 的检查,而且这个文件本身很特殊,它一开始就是 unlinked 的。所以内核实际上是通过 tmpfs 中的一个文件来实现了匿名的共享映射!到此,你可以看出,tmpfs 这个名字其实名副其实了。

另外,tmpfs 本身可以随意挂载,通过 mount -t tmpfs,你可以在上面进行任意文件操作。所以,内核通过 tmpfs 一套代码把下面三个东西给统一起来了:1. 匿名共享映射;2. POSIX IPC;3. tmpfs 文件操作。Mel Gorman 这样解释到:

This is a very clean interface that is conceptually easy to understand but it does not help anonymous pages as there is no file backing. To keep this nice interface, Linux creates an artifical file-backing for anonymous pages using a RAM-based filesystem where each VMA is backed by a “file” in this filesystem. Every inode in the filesystem is placed on a linked list called shmem_inodes so that they may always be easily located. This allows the same file-based interface to be used without treating anonymous pages as a special case.

之所以用 tmpfs 而不是同样基于内存的 ramfs 是因为:1) tmpfs 文件是可以 swap 的;2) tmpfs 是有大小限制的,不会允许用户无限制使用内存从而导致 OOM。(注:只有当 CONFIG_SHMEM=n 时,tmpfs 才会调用 ramfs 代码。)