这是《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
特点:
- 数据在内存和 swap 中;
- 重启后丢失;
- 写入会消耗内存;
- 适合临时缓存和低延迟文件。
不适合存储不可恢复数据。使用前应限制大小。
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'
常见问题:
- 锁文件残留;
- NFS 锁语义差异;
- 多实例没有共享锁;
- 锁释放和进程退出异常;
- 只锁文件不锁业务状态。
分布式多节点任务应使用数据库锁、分布式锁或编排系统的 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
处理:
- 修改应用日志输出;
- 使用 volume;
- 清理临时文件;
- 控制镜像层数和大小。
本章小结
VFS 让应用用统一接口访问不同文件系统,同时带来页缓存、写回、overlay copy-up、网络文件系统等行为差异。可靠写文件要考虑原子替换和 fsync;容器持久数据不应写在镜像可写层。
思考题
- 页缓存对读和写分别有什么影响?
/proc为什么能动态生成内容?- overlayfs 的 copy-up 会带来什么成本?
- 为什么配置文件写入常用临时文件加 rename?
- 多节点任务为什么不能只依赖本地文件锁?