同样写 20 MB:磁盘不 fsync 10.88 毫秒、内存 8.88 毫秒、磁盘每次 fsync 26.03 毫秒。快的那两条干的是同一件事——一次内存拷贝。tmpfs 的特别之处是:它把【抄进缓存】当成了终点。
7.12 讲的是【放在盘上】的文件系统,这一节讲【不放在盘上】的那一种:数据只在内存里,掉电就没。
一、tmpfs 是什么
tmpfs 是一个【把内容放在内存与 Swap 里】的文件系统。它有完整的目录树、inode、权限——从接口上看和 ext4 没有任何区别。
区别只在【数据的去处】:ext4 的数据最终落在块设备上;tmpfs 的数据只在内存里,需要时被换出到 Swap。
所以它的两个性格:① 快——没有设备这一层,就是内存操作。② 危险——掉电就没,而且没有【已经落盘】这个状态。
mount -t tmpfs -o size=1G tmpfs /mnt/ram 就能造一个。不指定 size 的话,默认上限是物理内存的一半。
一句话:tmpfs 是【用文件接口包装起来的一块内存】。
二、快在哪、危险在哪
实测三个数:磁盘不 fsync 10.88 毫秒;内存 8.88 毫秒;磁盘每次 fsync 26.03 毫秒。
前两个在一个量级——因为它们干的是同一件事:一次内存拷贝。不 fsync 的磁盘写入,其实只是抄进了页缓存,根本没到盘上。
所以【写文件很快】这件事本身说明不了任何问题。它可能只是抄进了缓存。要确认真的落盘,只有 fsync 的返回能作数。
这就是 tmpfs 危险的地方:它把【抄进缓存】当成了终点,从来没有下一步。而普通文件至少还有一个【回写线程在后台慢慢写盘】。
反过来看,这也是 tmpfs 的好处:不需要回写,所以不会有【写放大】、不会有【突发的 IO 抖动】。
三、哪些目录本来就是 tmpfs
/tmp:临时文件。下次开机就该没了——所以放内存是合理的。
/run:进程号、锁文件、socket。重启后必须重新生成,所以【重启后 /run 里的东西消失】不是 bug,是设计。
/dev/shm:进程间共享内存。POSIX 共享内存就是靠这个目录实现的——两个进程 mmap 同一个文件,就共享了同一块内存。
这几个目录的共同点是:内容都是【运行期的状态】,本来就不该跨重启。把这种状态放进 tmpfs 是【设计对了】,不是【省事】。
而【把重要东西放在 /tmp 然后重启丢了】是用户自己的锅——/tmp 的语义就是【随时可以消失】。
四、tmpfs 与【文件被缓存】的区别
两者看着像(都占内存、都快),其实是两件事。
磁盘文件在缓存里是【副本】——盘上永远有一份真的。内存不够时,缓存可以被直接丢掉,因为盘上有。
tmpfs 里的东西【只有内存这一份】——内存不够时不能丢,只能换出到 Swap。所以它计入【内存已用】,而页缓存计入【可回收】。
这就是 free 里那几个数的关系(5.1 讲过):buff/cache 那部分是【可以随时丢的】;而 tmpfs 占的部分算在【已用】里。
所以【df -h /tmp 显示用了一半】和【free 显示内存紧张】可能是同一件事的两面。排查内存问题时,别忘了看一眼 /dev/shm 和 /tmp 有多大。
五、动手 Demo:基于内存的文件系统——把 /tmp 放进内存
这一节比较三件事:写 20 MB 到磁盘(不 fsync)、写 20 MB 到磁盘(每次 fsync)、写 20 MB 到预分配的 bytearray。
import os
import tempfile
import time
MB = 1024 * 1024
BUF = b"x" * MB
d = tempfile.mkdtemp(prefix="ch29c7_")
MEMBUF = bytearray(20 * MB)
def disk_write(sync):
p = os.path.join(d, "w%d.bin" % (1 if sync else 0))
with open(p, "wb") as g:
for _ in range(20):
g.write(BUF)
g.flush()
if sync:
os.fsync(g.fileno())
def mem_write():
for i in range(20):
MEMBUF[i * MB:(i + 1) * MB] = BUF
for name, fn in (("磁盘 不fsync", lambda: disk_write(False)),
("磁盘 fsync", lambda: disk_write(True)),
("内存", mem_write)):
t0 = time.perf_counter()
fn()
ms = (time.perf_counter() - t0) * 1000
print(name, "%.2f 毫秒" % ms, "%.0f MB/秒" % (20 / (ms / 1000)))
实跑输出:
--------------------------------------------------------------------
基于内存的文件系统——把 /tmp 放进内存
--------------------------------------------------------------------
① 同样写 20 MB:磁盘 vs 内存
写到哪 20 MB 耗时 折算速度
---------------------------------------------------
磁盘(不 fsync) 12.19 毫秒 1641 MB/秒
磁盘(每次 fsync) 25.02 毫秒 800 MB/秒
内存(bytearray) 13.48 毫秒 1484 MB/秒
**三行里有两行在同一个量级,第三行明显慢一截。**
**快的那两行——【写内存】与【不 fsync 的磁盘】——干的是同一件事:一次内存拷贝。**
**慢的那一行(每次 fsync)才真的把数据交给了设备**,**多花 2.1 倍时间。**
**所以【写文件很快】这件事本身说明不了任何问题。**
**它可能只是抄进了页缓存,还没到盘上。**
**要确认真的落盘,只有 fsync 的返回能作数。**
**这也是 tmpfs 危险的地方:它把【抄进缓存】当成了终点,从来没有下一步。**
② tmpfs 与【普通文件放缓存里】的区别
特性 磁盘上的文件 tmpfs 里的文件
-------------------------------------------------------------
掉电后还在吗 在(fsync 过就稳) 全没了
占的是谁的空间 磁盘 内存与 Swap
会不会被回收 不会 会:换出到 Swap
容量上限 卷的大小 默认内存的一半
fsync 有意义吗 有 没意义,无事可做
**tmpfs 与【磁盘文件被缓存】看着像,其实是两件事。**
**磁盘文件在缓存里是【副本】,盘上永远有一份真的;**
**tmpfs 里的东西【只有内存这一份】。**
**所以 `df -h /tmp` 明明显示用了 50%,**
**而 `free` 里那部分其实是【可以被换出的内存】。**
③ 哪些目录本来就是 tmpfs
目录 里面放什么 为什么放内存
-----------------------------------------------------
/tmp 临时文件 下次开机就该没了
/run 进程号、锁文件 重启后必须重新生成
/dev/shm 进程间共享内存 本来就不该落盘
**所以【重启后 /run 里的东西消失】不是 bug,是设计。**
**而【/tmp 里放了重要东西然后重启丢了】是用户自己的锅。**
📋 **Linux 上验证**:`df -h /tmp /run /dev/shm`,
看 `Filesystem` 一列是不是写着 `tmpfs`;
`mount | grep tmpfs` 看各自的大小上限。
三行里有两行在同一个量级,第三行明显慢一截。快的那两行——【写内存】与【不 fsync 的磁盘】——干的是同一件事:一次内存拷贝。慢的那一行(每次 fsync)才真的把数据交给了设备。
所以【写文件很快】说明不了任何问题,它可能只是抄进了页缓存,还没到盘上。
这也是 tmpfs 危险的地方:它把【抄进缓存】当成了终点,从来没有下一步。
注意这里的内存缓冲是【预分配】的——如果改成反复 +=,测出来的就是【拼接的代价】而不是【内存的带宽】(7.10 踩过这个坑)。
📋 Linux 上验证:df -h /tmp /run /dev/shm,Filesystem 一列写着 tmpfs;mount | grep tmpfs 看各自的大小上限。
30 秒实验
df -h /tmp /dev/shm,看这两个各有多大。mount -t tmpfs -o size=100M tmpfs /mnt,然后dd写满它,看什么时候报错。- 想一想:为什么把编译的临时目录放进 tmpfs 能加快编译?
常见误区
- 【tmpfs 就是页缓存】。页缓存是磁盘文件的副本,可以随时丢;tmpfs 只有内存这一份,丢了就没了。
- 【tmpfs 里的东西会被自动保存】。它不会。要保就得自己拷出来。
- 【/tmp 会定期清理所以不重要】。清理策略因发行版而异,有的重启才清;重要东西不该放 /tmp,这是设计前提。
自测三问(点开对答案)
1. 为什么【不 fsync 的磁盘写】和【写内存】差不多快? —— **因为不 fsync 时数据只是抄进了页缓存**,**还没到设备**——**它和写内存干的是同一件事。** 2. tmpfs 与页缓存的两个关键区别是什么? —— **① 页缓存是副本,盘上有真的;tmpfs 只有一份。****② 页缓存可以被直接丢弃,tmpfs 只能换出到 Swap。** 3. 为什么把编译临时目录放进 tmpfs 能加快编译? —— **因为编译器产生大量小的中间文件**,**省掉的是每次 `open`/`write` 的固定开销与回写**(7.12 那条台阶)。- ☐ 7.14 网络文件系统
相关笔记
- 各种各样的文件系统 — 放在盘上的那些
- 内存相关的统计信息 — MemAvailable 与缓存的关系
- 同步写入 — fsync 到底保证了什么
- 🐧 Linux是怎样工作的 · 索引