LinuxNotes

第 16 章:虚拟文件系统

zjc 于 2026-01-16 发布

这是《Linux 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 VFS 是 Linux 给不同文件系统提供的统一接口。应用调用 open/read/write/close,底层可能是 ext4、XFS、NFS、tmpfs、overlay 或 procfs。理解 VFS 有助于分析缓存、写放大、容器镜像、虚拟文件和 IO 延迟。

16.1 VFS 位置

Application
  -> System Calls
     -> VFS
        |-- ext4
        |-- xfs
        |-- tmpfs
        |-- procfs / sysfs
        |-- overlayfs
        +-- NFS
           -> Page Cache / Block Layer

统一接口:

操作 示例
open 打开文件
read 读文件
write 写文件
fsync 刷盘
mmap 映射到地址空间
stat 读取元数据
unlink 删除目录项

16.2 页缓存

读路径:

read()
  -> page cache hit -> copy to user
  -> miss -> block IO -> cache -> copy to user

写路径:

write()
  -> page cache dirty
     -> writeback by kernel
        -> block device

观察:

free -h
cat /proc/meminfo | grep -E 'Cached|Dirty|Writeback'
vmstat 1

关键指标:

指标 含义
Cached 页缓存
Dirty 待回写页
Writeback 正在回写页
bi / bo 块读写

大量脏页集中回写可能造成延迟抖动。数据库和消息队列通常会显式控制刷盘策略。

16.3 procfs 与 sysfs

proc 示例:

cat /proc/meminfo
cat /proc/loadavg
cat /proc/self/status
ls -l /proc/<pid>/fd
cat /proc/<pid>/io

sys 示例:

ls /sys/class/net
cat /sys/class/net/eth0/mtu
cat /sys/fs/cgroup/cpu.max

这些文件不占普通磁盘数据块,读取时由内核动态生成。

16.4 tmpfs

查看:

df -hT | grep tmpfs
mount | grep tmpfs

创建:

sudo mount -t tmpfs -o size=2G tmpfs /mnt/cache

特点:

  1. 数据在内存和 swap 中;
  2. 重启后丢失;
  3. 写入会消耗内存;
  4. 适合临时缓存和低延迟文件。

不适合存储不可恢复数据。使用前应限制大小。

16.5 overlayfs

容器镜像常见分层:

lowerdir  只读镜像层
upperdir  可写层
merged    容器看到的统一视图

查看:

mount | grep overlay
docker inspect <container> --format '{{json .GraphDriver.Data}}' | jq .

写行为:

操作 行为
读 lower 直接读
写 lower copy-up 到 upper
删除 whiteout 标记
写新文件 写 upper

容器内大量写入会放大可写层增长。持久数据应使用 volume 或 bind mount。

16.6 文件与描述符

查看:

lsof -nP -p <pid>
ls -l /proc/<pid>/fd
cat /proc/<pid>/limits | grep 'open files'

常见 fd:

0 stdin
1 stdout
2 stderr
3+ files, sockets, pipes, epoll...

进程持续打开文件不释放,会导致 Too many open files。处理要回到代码资源关闭逻辑,而不是只调大 limit。

16.7 fsync 与持久化

示例:

sync
sync -f /data

应用写文件不等于落盘。崩溃一致性需要结合 fsync、目录 fsync、原子 rename 和文件系统语义。

安全写入模式:

1. 写临时文件
2. fsync 临时文件
3. rename 到目标
4. fsync 目录

数据库、消息队列和配置发布都依赖类似的原子替换语义。

16.8 文件锁

查看:

fuser -v /data/file
lsof /data/file

C 示例:

flock /tmp/app.lock -c '/opt/app/bin/job.sh'

常见问题:

  1. 锁文件残留;
  2. NFS 锁语义差异;
  3. 多实例没有共享锁;
  4. 锁释放和进程退出异常;
  5. 只锁文件不锁业务状态。

分布式多节点任务应使用数据库锁、分布式锁或编排系统的 Leader Election。

16.9 NFS 与网络文件系统

查看:

df -hT
mount | grep nfs
nfsstat -s
rpcinfo -p nfs-server

常见问题:

现象 常见原因
ls 卡住 服务不可用或硬挂载
权限不一致 root_squash 和 UID 映射
写入延迟高 网络或服务端压力
文件锁异常 锁服务或实现差异

生产数据库通常不直接使用 NFS,应选择数据库兼容的存储和网络方案。

16.10 常见故障

删除文件但空间不释放

lsof +L1
du -sh /data
df -hT /data

原因:目录项已删除,进程仍持有 fd。

Too many open files

cat /proc/<pid>/limits
ls /proc/<pid>/fd | wc -l
lsof -p <pid> | awk '{print $5}' | sort | uniq -c | sort -nr

overlay 可写层过大

docker system df
du -sh /var/lib/docker/overlay2

处理:

  1. 修改应用日志输出;
  2. 使用 volume;
  3. 清理临时文件;
  4. 控制镜像层数和大小。

本章小结

VFS 让应用用统一接口访问不同文件系统,同时带来页缓存、写回、overlay copy-up、网络文件系统等行为差异。可靠写文件要考虑原子替换和 fsync;容器持久数据不应写在镜像可写层。

思考题

  1. 页缓存对读和写分别有什么影响?
  2. /proc 为什么能动态生成内容?
  3. overlayfs 的 copy-up 会带来什么成本?
  4. 为什么配置文件写入常用临时文件加 rename?
  5. 多节点任务为什么不能只依赖本地文件锁?