[TOC]

mm/shmem.c 共享内存文件系统(Shared Memory Filesystem) tmpfs与POSIX共享内存的基石

历史与背景

这项技术是为了解决什么特定问题而诞生的?

这项技术以及由它实现的tmpfs文件系统,主要是为了解决两大类问题:高性能的临时文件存储高效的进程间通信(IPC)

  • 高速临时存储:传统的磁盘文件系统读写速度受限于物理硬件,速度较慢。很多程序在运行过程中需要创建临时文件(例如编译器中间文件、Web服务器的会话文件等),这些文件不需要持久化存储,在系统重启后即可丢弃。shmem通过在内存中实现一个完整的文件系统,提供了比磁盘快几个数量级的读写速度,极大地提升了这类应用的性能。
  • 进程间通信(IPC):在shmem出现之前,Linux主要支持System V IPC标准的共享内存。POSIX标准则提出了一套基于文件系统的共享内存API(shm_open, mmap)。为了在内核中实现一个统一、高效的后端来支持这两种共享内存机制,shmem被开发出来。它通过将共享内存区域抽象成内存中的“文件”,完美地融入了Linux“一切皆文件”的设计哲学。

它的发展经历了哪些重要的里程碑或版本迭代?

shmem的发展是逐步演进的,旨在提供一个比早期内存文件系统更完善的方案。

  • ramfs的局限性:Linux内核早期就有一个基于内存的文件系统叫ramfsramfs非常简单,它只是将Page Cache(页面缓存)和Dentry Cache(目录项缓存)的功能导出为一个文件系统。但ramfs有一个致命缺陷:它的大小会无限制地增长,直到耗尽所有物理内存,这很容易导致系统崩溃。并且,ramfs中的数据无法被交换到swap空间。
  • shmem/tmpfs的诞生shmem.c的实现从ramfs中借鉴了核心思想,但增加了两个关键特性:
    1. 大小限制tmpfs实例在挂载时可以指定大小限制,防止其耗尽系统内存。
    2. 可交换性(Swappiness):当物理内存紧张时,tmpfs中不常用的数据可以像普通进程的内存一样,被交换到磁盘上的swap分区或swap文件中,从而释放物理内存供更紧急的任务使用。
  • 成为共享内存后端:随着其功能的完善,shmem成为了内核中实现System V共享内存和POSIX共享内存的标准后端。 对于用户可见的POSIX共享内存,glibc库期望有一个tmpfs挂载在/dev/shm上。

目前该技术的社区活跃度和主流应用情况如何?

shmem及其用户态接口tmpfs是现代Linux系统中一个极其稳定、成熟且不可或缺的核心组件。

  • 社区活跃度:作为内存管理和虚拟文件系统的核心部分,shmem.c的代码非常稳定。相关的改动通常是为了进行性能优化、修复罕见bug或与内存管理子系统的其他部分(如页面回收)进行同步。
  • 主流应用
    • /dev/shm:几乎所有的主流Linux发行版都会默认将一个tmpfs挂载在/dev/shm,作为POSIX共享内存的标准实现。 许多应用(如Oracle数据库、多媒体应用、Python的SharedArray库)都利用它进行高性能的进程间数据共享。
    • /tmp:很多系统管理员选择将/tmp目录也挂载为tmpfs,以加速临时文件的读写。
    • 系统运行时目录:像/run这样的目录也通常是tmpfs,用于存放系统守护进程的运行时数据(如PID文件、socket文件等)。
    • 编译构建:在编译大型项目时,将构建目录放在tmpfs中可以显著缩短编译时间。

核心原理与设计

它的核心工作原理是什么?

shmem.c的核心原理是利用内核的页面缓存(Page Cache)作为文件内容的直接存储介质,并将磁盘上的交换空间(Swap Space)作为其最终的后备存储

  1. 虚拟文件系统tmpfs是一个虚拟文件系统,它没有对应的物理块设备。 当你创建一个tmpfs文件时,内核只是在内存中创建了对应的inode和dentry结构。
  2. 按需分配内存:向tmpfs文件写入数据时,并不会立即占用所有声明的空间。相反,内核会按需分配物理内存页,并将这些页加入到Page Cache中,与该文件的inode关联起来。 所有对文件的读写操作,实际上都是对Page Cache中这些内存页的直接读写,因此速度极快。
  3. 与Swap的交互tmpfs中的页面被认为是“可交换的匿名页”。 当系统物理内存(RAM)不足时,内核的页面回收机制(kswapd)会像对待普通进程的内存一样,将tmpfs中不常被访问的“文件内容”(即那些内存页)写入到磁盘上的swap分区。 此时,物理内存被释放,但在Page Cache中会留下一个指向swap位置的条目(swap entry)。
  4. 透明的Swap-in:当进程再次访问被换出的tmpfs文件部分时,会触发一个缺页异常(Page Fault)。内核会捕获这个异常,从swap分区中读回相应的数据到新的物理内存页中,并重新建立映射。这个过程对用户进程是完全透明的。
  5. 动态调整大小:当tmpfs中的文件被删除时,其占用的Page Cache中的内存页会被立即释放,如果这些页之前被换出到swap,swap空间也会被释放。这使得tmpfs的大小是动态变化的。

它的主要优势体现在哪些方面?

  • 速度极快:所有操作都在内存中进行,避免了与慢速块设备的I/O交互,性能远超任何基于磁盘的文件系统。
  • 动态大小tmpfs只占用实际需要的内存和swap空间,而不是像ramdisk那样一次性预留所有空间。
  • 易于使用:它表现为一个标准的文件系统,可以使用所有标准的文件操作命令和API(ls, cp, open, write等),无需特殊的编程接口。
  • 非持久性(作为优势):系统重启后自动清空,确保了临时数据的清洁,无需手动清理。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 数据易失性:这是其最显著的特点也是最大的劣势。任何存储在tmpfs中的数据在系统重启或掉电后都会永久丢失
  • 消耗系统内存tmpfs会与系统中的其他应用程序争用宝贵的物理内存和swap空间。如果一个tmpfs实例被填满,可能会耗尽系统可用内存,导致其他进程因内存不足(OOM, Out-of-Memory)而被杀死。
  • 不适合大文件:虽然理论上tmpfs的大小可以达到物理内存加swap的总和,但用它来存储非常大的文件通常不是一个好主意,因为这会严重挤占系统为其他任务准备的内存资源。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

  • POSIX进程间通信:当多个进程需要共享大量数据时,在/dev/shm中创建一个文件,然后各自通过mmap进行内存映射,这是Linux下最高效的IPC方式之一。
  • 高I/O的临时文件:Web服务器可以用tmpfs来存储PHP的session文件,从而加速用户会话的读写。数据库系统(如Oracle)也可能使用/dev/shm来实现其自动内存管理特性。
  • 加速编译过程:在编译大型软件项目(如内核本身或大型C++项目)时,将输出目录设置在tmpfs挂载点上,可以大幅减少因生成大量中间文件而产生的I/O等待时间。

是否有不推荐使用该技术的场景?为什么?

  • 需要持久化存储的任何场景:绝对不能用tmpfs来存储任何需要长期保存的数据,如数据库文件、用户文档、系统配置等。一旦系统关闭,数据将无法恢复。
  • 内存极其有限的系统:在内存非常小的嵌入式设备上,大量使用tmpfs可能会迅速耗尽内存,导致系统不稳定。在这种情况下,传统的基于闪存的文件系统是更好的选择。

对比分析

请将其 与 其他相似技术 进行详细对比。

对比一:tmpfs vs. ramfs

特性 tmpfs (由 mm/shmem.c 实现) ramfs
核心机制 利用Page Cache,可被交换到Swap。 仅利用Page Cache,是其最简化的表现形式。
大小限制 。可以在挂载时通过size选项指定上限。 。会一直增长直到耗尽所有物理内存。
可交换性 可交换。当内存不足时,数据可以被换出到swap。 不可交换。数据始终驻留在物理内存中。
持久性 重启后数据丢失。 重启后数据丢失。
使用场景 通用的、安全的内存文件系统,如/dev/shm, /tmp 主要用于内核调试或某些特定场景,因其不可控的增长性而在通用场景中较少使用。

对比二:tmpfs vs. Ramdisk (/dev/ram)

特性 tmpfs Ramdisk (块设备)
设备类型 是一个文件系统,不是块设备。 是一个块设备,模拟了一块硬盘在内存中。
格式化 无需格式化,直接mount即可使用。 必须在创建后,使用mkfs(如mkfs.ext4)对其进行格式化才能使用。
大小 动态调整。只消耗实际使用的空间。 固定大小。在创建时即占用全部指定的内存空间。
缓存机制 它本身就是Page Cache的一种应用,没有双重缓存问题。 数据会经过双重缓存:一次在Ramdisk自己的内存里,一次在访问它的文件系统的Page Cache里,效率较低。
可交换性 可交换 不可交换
使用场景 现代Linux系统内存文件系统的首选。 较老的技术,主要用于启动过程中的initrd/initramfs,或某些需要模拟块设备的特殊测试场景。

mm/shmem.c

shmem_init 共享内存(Shared Memory)初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* tiny-shmem:使用 ramfs 代码实现的简单 shmemfs 和 tmpfs
*
* 该实现面向小型系统,在这些系统中,完整 shmem 代码(支持 swap 和资源限制)的复杂性
* 超过了其带来的好处。对于没有 swap 的系统,这段代码的效果应与完整实现等效,
* 但更加轻量。
*/

static struct file_system_type shmem_fs_type = {
.name = "tmpfs",
.init_fs_context = ramfs_init_fs_context,
.parameters = ramfs_fs_parameters,
.kill_sb = ramfs_kill_sb,
.fs_flags = FS_USERNS_MOUNT,
};

void __init shmem_init(void)
{
BUG_ON(register_filesystem(&shmem_fs_type) != 0);

shm_mnt = kern_mount(&shmem_fs_type);
BUG_ON(IS_ERR(shm_mnt));
}