本文要点
- tmpfs 是拿内存当文件系统:
df显示的容量就是它的上限,size=是硬墙。实测往size=16M的 tmpfs 里写 32M,dd报No space left on device、退出码 1,只写进去 16M,df显示100% - tmpfs 占的内存记在
free的 shared 列(实测写 16M 后shared从 0 变成 16),也记在/proc/meminfo的Shmem里。Shmem虽然算在Cached那堆里,但清缓存清不掉它:实测drop_caches前后Shmem都是66536 kB一分未动,而Cached从 1.5G 掉到 190M - tmpfs 可以不停机扩容:
mount -o remount,size=48M不需要卸载,文件还在(扩前扩后md5sum一致);反过来缩到比已用量还小会直接失败(实测返回码 32),文件系统保持原样 - ramfs 是「没有任何账本」的兄弟:
findmnt和df都报容量 0,写进 8M 之后还是报 0;它占的内存落在buff/cache而不是shared——和磁盘页缓存混在一起,从账面上看不出是它,而且完全没有上限 - 机器上本来就有好几处 tmpfs(
/dev/shm默认是内存的一半、/run、/run/lock)。内存账本怎么看、available为什么不能只看free,见 Linux 内存排查:从 free 到 /proc/meminfo 与 OOM Killer 定位
Linux 内存文件系统 tmpfs 与 ramfs:容量上限、内存账本与常见坑
/tmp 挂 tmpfs、容器里的 /dev/shm、编译时的中间目录、放密钥的临时空间——这些做法的共同点是:文件不落盘,直接住在内存里。好处很直接(快、重启即失),代价却不那么直观:
df -h显示/dev/shm有 989M,这个数字是从哪来的?- 往 tmpfs 里写文件,占用记在
free的哪一列?为什么buff/cache涨了、shared却没动? - 磁盘写满了会报
ENOSPC,tmpfs 写满了报什么?会不会把整台机器拖死? tmpfs和ramfs差在哪,为什么df对 ramfs 报的容量是 0?
本文在一台 1 核 1.9G、没有 swap 的 Ubuntu 22.04 演示机(内核 5.15.0-30-generic)上,把这几件事逐个实测一遍。所有操作都在 /tmp 下用临时挂载点做,演示完即卸载,磁盘和内存都不留残留。
机器上本来就有好几处 tmpfs
不用自己创建,先看看系统已经在用的:
findmnt -t tmpfs -o TARGET,SIZE,USED,AVAIL,OPTIONS
echo "--- 物理内存与 swap ---"
free -m | head -2
cat /proc/swaps
echo "--- /dev/shm 的默认大小 ---"
df -h /dev/shm | tail -1
这张图里有四个值得注意的地方:
① /dev/shm 的 988.8M 是内存的一半。 这台机器 free -m 显示 total 1977MB,而 /dev/shm 的容量正好是它的一半左右。这是 tmpfs 的默认规则:不写 size= 时,容量取物理内存的 50%。所以同一份 fstab 配置,在 2G 的机器和 64G 的机器上行为完全不同——这也是「小机器上往 /dev/shm 写大文件突然失败」的根源。
② /run、/run/lock、/run/user/0 都是 tmpfs。 系统自己就在用:/run/lock 只有 5M(锁文件本来就该很小),/run/user/0 是每个登录用户的运行时目录。注意它的挂载选项里有 nr_inodes=50627——tmpfs 除了限制空间,还能限制 inode 数量,空间没满但 inode 用尽时同样会报 ENOSPC。
③ 这些 tmpfs 目前一个字节都没占(USED 全是 0 或 4K),free 里的 shared 也是 0。 tmpfs 是惰性的:挂载本身不预留任何内存,只有真的写进文件才扣内存。所以「机器上挂了一堆 tmpfs」本身不需要紧张,紧张的是往里写了多少。
④ /proc/swaps 只有一行表头——这台机器没有 swap。 这一点很关键:tmpfs 的内容属于 shmem,在有 swap 的机器上还能被换出到磁盘(代价是之后访问它要再读回来,表现为变慢);而在这台机器上无处可换,写满 tmpfs 就是直接吃掉物理内存,压力全部落在物理内存和 OOM Killer 上。演示机这种 1.9G 无 swap 的配置,/dev/shm 里放一个 900M 的文件就足以把系统送走。判断方法就一行:cat /proc/swaps 有没有内容。
size= 是硬墙:写超了会怎样
自己建一个 16M 的 tmpfs,然后故意往里写 32M:
mkdir -p /tmp/tpdemo
mount -t tmpfs -o size=16M tmpfs /tmp/tpdemo
findmnt -no TARGET,SIZE,OPTIONS /tmp/tpdemo
echo "--- 往 16M 的盘里写 32M ---"
dd if=/dev/zero of=/tmp/tpdemo/a.bin bs=1M count=32 2>&1; echo "dd 退出码=$?"
df -h /tmp/tpdemo | tail -1
echo "--- 这 16M 在内存账本上记在哪 ---"
free -m | head -2
echo "--- 在线扩容到 48M,文件还在吗 ---"
md5sum /tmp/tpdemo/a.bin
mount -o remount,size=48M /tmp/tpdemo && echo "扩容返回码=0"
df -h /tmp/tpdemo | tail -1
md5sum /tmp/tpdemo/a.bin
umount /tmp/tpdemo && rmdir /tmp/tpdemo && echo "演示挂载点已清理"
输出里的几个细节:
dd: error writing '/tmp/tpdemo/a.bin': No space left on device
17+0 records in
16+0 records out
16777216 bytes (17 MB, 16 MiB) copied, 0.0166534 s, 1.0 GB/s
dd 退出码=1
tmpfs 16M 16M 0 100% /tmp/tpdemosize=16M就是硬墙,和磁盘写满一样报No space left on device(ENOSPC)。tmpfs 不会「超卖」:它知道自己的额度,写超的部分直接拒绝;16+0 records out说明 dd 写了一半就被打断,留下一个 16M 的残缺文件。所以脚本里往 tmpfs 写东西,必须检查退出码——dd 退出码=1才是判断依据,光看日志很容易漏;df显示100%,USED是 16M、AVAIL是 0。这个USED不是磁盘占用,而是已经吃掉的内存;free -m的shared列从 0 变成了 16——这就是下一节要说的账本。
顺带一提速度:tmpfs 的强项确实是快,但差距没有想象中那么夸张。同一份 64MiB 数据,都带 conv=fsync 强制落盘:
mkdir -p /tmp/spd /tmp/disk && mount -t tmpfs -o size=64M tmpfs /tmp/spd
( time dd if=/dev/zero of=/tmp/spd/f bs=1M count=64 conv=fsync ) 2>&1 | tail -3
( time dd if=/dev/zero of=/tmp/disk/f bs=1M count=64 conv=fsync ) 2>&1 | tail -3实测 tmpfs 是 real 0m0.102s(其中 sys 0m0.101s,全是内存拷贝),磁盘目录是 real 0m0.262s(sys 0m0.149s)——大约 2.6 倍。换算成吞吐是 640MB/s 对 250MB/s,对绝大多数应用来说,这点差距远不如「读写模式」重要。tmpfs 真正的价值不在快,而在「文件即内存」带来的语义:重启即失、天然不落盘、进程间通过文件共享内存。
这 16M 记在谁头上:shared 与 Shmem
上一节的 free -m 里,shared 从 0 变成 16——tmpfs 占的内存在 free 里是记在 shared 列的,不会记到 used(那是进程自己的 RSS),也不会记到 buff/cache(那是磁盘页缓存)。这个区分很有用:
used涨 → 找进程(ps aux --sort=-rss);shared涨 → 找 tmpfs / 共享内存(df -h | grep tmpfs、ipcs -m);buff/cache涨 → 多半是文件读写留下的页缓存,属于「可以回收」的那部分。
更细的账本在 /proc/meminfo 的 Shmem 里。把 tmpfs 里放一个 64M 文件,然后清一次页缓存看看会发生什么:
grep -E "^(MemFree|MemAvailable|Cached|Shmem|Buffers):" /proc/meminfo
sync; echo 3 > /proc/sys/vm/drop_caches
grep -E "^(MemFree|MemAvailable|Cached|Shmem|Buffers):" /proc/meminfo
md5sum /tmp/spd/f实测结果:
# drop_caches 之前
MemFree: 85124 kB
MemAvailable: 1601240 kB
Buffers: 93392 kB
Cached: 1520604 kB
Shmem: 66536 kB
# sync + drop_caches 之后
MemFree: 1633076 kB
MemAvailable: 1636788 kB
Buffers: 1344 kB
Cached: 194648 kB
Shmem: 66536 kBCached 从 1.5G 掉到 190M(页缓存确实被放掉了),Shmem 却一动不动——还是 66536 kB,而且 tmpfs 里那个文件的 md5sum 照样能算出来(数据完好)。
原因很简单:Shmem 属于 Cached 这个大类,但它的背后是真实的、有主的内存页(tmpfs 文件、共享内存段),不是「磁盘内容的副本」。清缓存能丢掉「副本」(丢了再读一次磁盘就行),丢不掉「原件」——丢掉原件就等于丢数据。所以:
drop_caches不是「内存不够时的救命稻草」,它只回收磁盘页缓存,对 tmpfs / 共享内存完全无效;- 反过来也一样:看到
Cached很高、以为「随时能回收」是有条件的——里面有多少是Shmem,就是多少收不回来的部分; - 磁盘页缓存与
drop_caches的完整实测(以及available为什么纹丝不动)见 Linux 内存排查:从 free 到 /proc/meminfo 与 OOM Killer 定位,那篇讲的是「能回收的那部分」,本文这一节讲的是收不回来的那部分。
在线扩容:不用卸载、数据不丢
tmpfs 的 size 是挂载参数,但不需要卸载再挂就能改——mount -o remount 是原子的:
mount -o remount,size=48M /tmp/tpdemo && echo "扩容返回码=0"
df -h /tmp/tpdemo | tail -1
md5sum /tmp/tpdemo/a.bin扩容返回码=0
tmpfs 48M 16M 32M 34% /tmp/tpdemo
2c7ab85a893283e98c931e9511add182 /tmp/tpdemo/a.bin三点结论:
- 扩容不需要卸载:容器、
/dev/shm、编译目录里的进程全程感知不到,业务不中断; - 数据不丢:扩前扩后
md5sum完全一致(2c7ab85a…),因为改的只是额度,页面本来就在内存里; - 扩容是「额度」变化,不是「重新分配」:
df里USED仍是 16M,只是AVAIL从 0 变成了 32M。
但反过来缩小是另一回事。 对一个已经 100% 占满的 tmpfs 执行 mount -o remount,size=8M,实测直接失败:
mount: /tmp/spd: mount point not mounted or bad option.
缩小 remount 返回码=32
tmpfs 64M 64M 0 100% /tmp/spd返回码 32,文件系统保持原样(还是 64M、还是 100%)——内核不会为了满足新额度去丢数据。这条规则很有用:/dev/shm 撑大之后想改回来,得先确认里面的文件已经被删干净,否则改不动。目标额度的报错信息是 mount point not mounted or bad option,看起来像「挂载点不存在」,实际原因是「缩不下去」,别被这句话带偏。
ramfs:连账本都没有的兄弟
ramfs 比 tmpfs 更老、更简单:它就没有「容量」这个概念,也不参与任何内存统计。同样做一遍挂载、写入、再看账面:
mkdir -p /tmp/rfdemo
mount -t ramfs ramfs /tmp/rfdemo
echo "--- ramfs 报出来的容量 ---"
findmnt -no TARGET,FSTYPE,SIZE,USED /tmp/rfdemo
df -h /tmp/rfdemo | tail -1
free -m | head -2
echo "--- 往里面写 8M ---"
dd if=/dev/zero of=/tmp/rfdemo/r.bin bs=1M count=8 2>&1 | tail -1
ls -l /tmp/rfdemo/r.bin
echo "--- 再报一次容量,以及内存的变化 ---"
findmnt -no TARGET,FSTYPE,SIZE,USED /tmp/rfdemo
free -m | head -2
umount /tmp/rfdemo && rmdir /tmp/rfdemo && echo "ramfs 演示挂载点已清理"
对比着看输出:
/tmp/rfdemo ramfs 0 0
none 0 0 0 - /tmp/rfdemo
-rw-r--r-- 1 root root 8388608 Sep 30 21:12 /tmp/rfdemo/r.binfindmnt报SIZE 0、df报0 0 0:不是「没有空间」,而是「不统计」。文件照样写得进去(8388608字节,8M);- 写完之后再查,还是 0。所以 ramfs 上没有任何容量配额:它能一直吃内存,直到把机器吃垮(在没有 swap 的机器上就是 OOM Killer 登场);
- 它占的内存落在
buff/cache而不是shared:写 8M 前后对比,free的shared保持 0,而buff/cache从1608涨到1616、free从 171 掉到 165。ramfs 的页面直接进了页缓存账本,从free的视角看,它和「读了几个磁盘文件」长得一模一样——这是排查时最容易漏掉的一类内存占用。
两者的取舍很清楚:
| tmpfs | ramfs | |
|---|---|---|
| 容量上限 | 有(size=,默认内存的 50%) | 没有 |
df / findmnt 报容量 | 报 | 报 0 |
| 内存记在哪 | shared / Shmem | buff/cache(和磁盘缓存混在一起) |
| 写满之后 | ENOSPC,安全失败 | 继续吃内存,直到 OOM |
| 内存吃紧时 | 有 swap 时可换出到磁盘(表现为变慢) | 不能换出,只能删文件释放 |
| 典型用途 | /dev/shm、/run、/tmp | 内核 initramfs(早期启动阶段) |
日常使用选 tmpfs:它至少知道自己的边界,写超了只是那一次写失败,而不是把整台机器拖下水。ramfs 基本只出现在内核自己的场景里(initramfs 阶段用它做临时根文件系统),用户态几乎没有任何理由主动挂 ramfs——「没有上限」听起来自由,实际是「没有刹车」。
什么时候该用、什么时候会出事
适合用 tmpfs 的场景:
- 进程间共享数据(
/dev/shm):比管道灵活、比临时文件快,进程退出即释放; - 敏感中间产物:私钥、解密后的数据放在 tmpfs,机器一重启就没了,不用记得擦;
- 高频临时读写:编译中间产物、socket 文件、会话缓存;
- 容器/
/tmp:很多发行版默认把/tmp挂成 tmpfs,好处是重启自动清空。
必须警惕的坑:
- 没有 swap 的机器上,tmpfs 写满就是 OOM。先看
cat /proc/swaps确认有没有 swap,再看df -h数一数所有 tmpfs 的size之和——那才是这台机器「最坏情况下会被 tmpfs 吃掉多少内存」; /dev/shm默认是内存的 50%,容器里通常被压到 64M。做数据处理、机器学习、多进程共享数组时,写/dev/shm之前先df -h /dev/shm看一眼额度,否则会拿到一个莫名其妙的ENOSPC;- 监控要盯
shared/Shmem,而不是磁盘。tmpfs 的占用不会体现在任何磁盘分区的df上(那个df显示的USED是内存),磁盘监控全绿、内存却悄悄被吃光,是很典型的场景; du看不见「已删除但仍被打开」的文件占的空间。tmpfs 上删了文件但如果还有进程开着它,内存不会立刻还回来——排查时要看lsof +L1,而不是只看du(用法见 lsof 打开文件排查);ramfs不要用在用户态。理由如上:没有上限、没有账本、写爆了才发现。
小结
把 tmpfs 和 ramfs 放回内存子系统的坐标系里,三句话就能记住:
- tmpfs 是一个「知道自己有多大」的内存文件系统:
size=(默认内存 50%)是硬额度,超出报ENOSPC并留下残缺文件;可以remount在线扩容(数据不丢),但缩不到已用量以下; - 它占的内存记在
shared/Shmem:Shmem属于Cached大类却清不掉(drop_caches前后实测一分未动),所以「Cached高就等于可回收」这个直觉对 tmpfs 不成立; - ramfs 是它的无账本版本:
df报 0、没有上限、内存混进buff/cache,用户态应该避开。
排查顺序也很固定:先 free -m 看哪一列在涨(used → 进程,shared → tmpfs,buff/cache → 页缓存),再 df -h | grep tmpfs 找是哪个挂载点,最后 du -sh 定位到具体文件。 内存整体排查(available 怎么算、OOM Killer 怎么挑人)见 Linux 内存排查:从 free 到 /proc/meminfo 与 OOM Killer 定位;进程间共享内存的用法(SysV 共享内存段与 /dev/shm)见 Linux 进程间通信:FIFO、共享内存与信号量;如果内存真的紧张到要压缩换出,那就是 Linux zram:压缩内存当 swap 用 的地盘了。
本文全部演示在一台 1 核 1.9G、无 swap 的 Ubuntu 22.04(内核 5.15.0-30-generic)上完成,零安装(findmnt、free、dd、md5sum 全是系统自带);演示挂载点已全部卸载并删除,/tmp 下无残留,机器上原有的 tmpfs 未做任何改动。
评论 (0)
暂无评论,快来抢沙发吧!