[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内核早期就有一个基于内存的文件系统叫
ramfs
。ramfs
非常简单,它只是将Page Cache(页面缓存)和Dentry Cache(目录项缓存)的功能导出为一个文件系统。但ramfs
有一个致命缺陷:它的大小会无限制地增长,直到耗尽所有物理内存,这很容易导致系统崩溃。并且,ramfs
中的数据无法被交换到swap空间。 - shmem/tmpfs的诞生:
shmem.c
的实现从ramfs
中借鉴了核心思想,但增加了两个关键特性:- 大小限制:
tmpfs
实例在挂载时可以指定大小限制,防止其耗尽系统内存。 - 可交换性(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
中可以显著缩短编译时间。
- /dev/shm:几乎所有的主流Linux发行版都会默认将一个
核心原理与设计
它的核心工作原理是什么?
shmem.c
的核心原理是利用内核的页面缓存(Page Cache)作为文件内容的直接存储介质,并将磁盘上的交换空间(Swap Space)作为其最终的后备存储。
- 虚拟文件系统:
tmpfs
是一个虚拟文件系统,它没有对应的物理块设备。 当你创建一个tmpfs
文件时,内核只是在内存中创建了对应的inode和dentry结构。 - 按需分配内存:向
tmpfs
文件写入数据时,并不会立即占用所有声明的空间。相反,内核会按需分配物理内存页,并将这些页加入到Page Cache中,与该文件的inode关联起来。 所有对文件的读写操作,实际上都是对Page Cache中这些内存页的直接读写,因此速度极快。 - 与Swap的交互:
tmpfs
中的页面被认为是“可交换的匿名页”。 当系统物理内存(RAM)不足时,内核的页面回收机制(kswapd)会像对待普通进程的内存一样,将tmpfs
中不常被访问的“文件内容”(即那些内存页)写入到磁盘上的swap分区。 此时,物理内存被释放,但在Page Cache中会留下一个指向swap位置的条目(swap entry)。 - 透明的Swap-in:当进程再次访问被换出的
tmpfs
文件部分时,会触发一个缺页异常(Page Fault)。内核会捕获这个异常,从swap分区中读回相应的数据到新的物理内存页中,并重新建立映射。这个过程对用户进程是完全透明的。 - 动态调整大小:当
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 | /* |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 wdfk-prog的个人博客!
评论