文件系统
概念
文件系统(File System)是操作系统中负责管理持久化存储的子系统。它定义了数据在磁盘上的组织方式,并向上层应用提供统一的文件读写接口。
核心职责:
- 磁盘空间管理:跟踪哪些块已使用、哪些空闲(位图或空闲链表)
- 文件组织:将逻辑文件映射到物理磁盘块
- 权限控制:记录每个文件的所有者、读写执行权限
- 命名与目录:维护目录树,支持按路径查找文件
核心原理
1. 文件系统基础
常见文件系统对比:
| 文件系统 | 平台 | 特点 |
|---|---|---|
| ext4 | Linux | 最广泛使用,支持日志,最大文件 16 TB |
| XFS | Linux | 高性能,擅长大文件和高并发写 |
| Btrfs | Linux | 写时复制(CoW),支持快照和校验和 |
| ZFS | FreeBSD/Linux | 内置 RAID、压缩、去重,稳定性强 |
| NTFS | Windows | 支持 ACL、加密、稀疏文件 |
| APFS | macOS/iOS | 写时复制,针对 SSD 优化 |
VFS(虚拟文件系统):
Linux 内核在具体文件系统之上抽象了一层 VFS(Virtual File System)。所有文件系统(磁盘、网络、proc、设备)都通过实现统一的 VFS 接口接入内核,这正是 Linux "一切皆文件"的实现基础。应用程序调用 open()、read()、write() 等系统调用时,由 VFS 路由到实际文件系统的驱动。
用户程序
│ open() / read() / write()
▼
VFS 层(统一接口)
│
├── ext4 驱动
├── XFS 驱动
├── proc 文件系统
└── 网络文件系统(NFS)2. inode 与数据块
每个文件在文件系统中对应一个 inode(索引节点),inode 与数据块是分开存储的。
inode 存储的内容(元数据):
- 文件类型(普通文件、目录、符号链接等)
- 权限(rwxrwxrwx)
- 硬链接数
- 所有者 UID / GID
- 文件大小(字节数)
- 时间戳(atime 访问、mtime 修改、ctime 元数据变更)
- 数据块指针
inode 不存储的内容:
- 文件名 — 文件名保存在目录项(directory entry)中,目录本身是一个将文件名映射到 inode 号的特殊文件。
数据块寻址结构(以 ext 系文件系统为例):
inode
├── 直接指针 × 12 → 直接指向数据块(小文件)
├── 一级间接指针 × 1 → 指向一个存放块地址的块
├── 二级间接指针 × 1 → 指向块 → 块地址块 → 数据块
└── 三级间接指针 × 1 → 指向块 → 块地址块 → 块地址块 → 数据块
假设块大小 4 KB,地址 4 字节(32 位),每块可存 1024 个指针:
- 直接:12 × 4 KB = 48 KB
- 一级间接:1024 × 4 KB = 4 MB
- 二级间接:1024² × 4 KB = 4 GB
- 三级间接:1024³ × 4 KB = 4 TBinode 耗尽问题:
inode 数量在格式化时固定(mkfs 时确定)。如果磁盘空间剩余但 inode 用尽(例如存储了海量小文件),将无法创建新文件。排查命令:
df -i # 查看 inode 使用情况
ls -i file # 查看文件的 inode 号
stat file # 查看 inode 详细信息3. 硬链接 vs 软链接
硬链接(Hard Link):
- 在目录中新增一条「文件名 → inode 号」的映射
- 与原文件共享同一个 inode,inode 引用计数 +1
- 删除任意一个名字,只要引用计数 > 0,文件数据不会被删除
- 限制:不能跨文件系统;不能链接目录(防止循环)
软链接 / 符号链接(Symbolic Link):
- 独立的 inode,文件内容是目标路径的字符串
- 类似 Windows 的快捷方式
- 目标被删除后,软链接变为"悬空链接"(dangling link)
- 可跨文件系统,可链接目录
对比表:
| 特性 | 硬链接 | 软链接 |
|---|---|---|
| 独立 inode | 否(共享) | 是 |
| 跨文件系统 | 不支持 | 支持 |
| 链接目录 | 不支持 | 支持 |
| 原文件删除后 | 仍可访问 | 链接失效 |
| 占用额外空间 | 极少(目录项) | 少量(路径字符串) |
命令示例:
ln source.txt hard_link.txt # 创建硬链接
ln -s source.txt soft_link.txt # 创建软链接
ls -li # 查看 inode 号,硬链接 inode 相同
readlink soft_link.txt # 查看软链接指向的路径4. 文件描述符(fd)
三级结构:
文件描述符(file descriptor,fd)是一个非负整数,是进程访问文件的句柄。内核维护三张表:
进程 A 的 fd 表 系统级打开文件表 inode 表
┌──────────────┐ ┌──────────────────┐ ┌──────────┐
│ fd 0 (stdin) │───────▶│ 偏移量、访问模式 │─────▶│ inode 1 │
│ fd 1 (stdout)│───────▶│ 偏移量、访问模式 │─────▶│ inode 2 │
│ fd 2 (stderr)│───────▶│ 偏移量、访问模式 │─────▶│ inode 2 │◀─── 进程 B fd 3
│ fd 3 │───────▶│ 偏移量、访问模式 │─────▶│ inode 5 │
└──────────────┘ └──────────────────┘ └──────────┘- 进程级 fd 表:每个进程独立,fd 是这张表的下标
- 系统级打开文件表:记录当前偏移量和访问模式(读/写),
fork()后父子进程共享同一表项(因此偏移量共享) - inode 表:全局唯一,多个打开文件表项可指向同一 inode
标准文件描述符:
| fd | 名称 | 默认指向 |
|---|---|---|
| 0 | stdin | 键盘输入 |
| 1 | stdout | 终端输出 |
| 2 | stderr | 终端错误输出 |
fd 泄漏与 ulimit:
每个进程可打开的 fd 数量有上限(默认通常 1024),可通过 ulimit -n 查看和调整。fd 泄漏指打开文件后未调用 close() 导致 fd 耗尽。
ulimit -n # 查看当前进程 fd 上限
cat /proc/<pid>/limits # 查看指定进程的限制
ls /proc/<pid>/fd | wc -l # 统计进程当前打开的 fd 数量
lsof -p <pid> # 列出进程打开的所有文件5. 磁盘调度算法
磁盘 I/O 的主要延迟来自磁头寻道(seek time)。调度算法决定以什么顺序处理 I/O 请求队列。
常见算法:
| 算法 | 全称 | 策略 | 特点 |
|---|---|---|---|
| FCFS | First Come First Served | 按请求到达顺序 | 公平,但寻道距离可能很长 |
| SSTF | Shortest Seek Time First | 优先处理离当前磁头最近的请求 | 平均寻道短,可能饥饿远端请求 |
| SCAN | — | 磁头来回扫描,遇到请求就处理 | 类似电梯,无饥饿 |
| C-SCAN | Circular SCAN | 单向扫描,到头后直接回起点 | 等待时间更均匀 |
| LOOK | — | SCAN 的优化,到最后一个请求就折返 | 减少不必要的移动 |
现代 SSD 无机械寻道,调度算法意义减弱;Linux 对 SSD 默认使用 none 或 mq-deadline。
cat /sys/block/sda/queue/scheduler # 查看当前磁盘调度算法6. 页缓存(Page Cache)
读写流程:
Linux 内核在文件系统和磁盘之间维护一个内存缓存层——Page Cache(页缓存)。
应用程序 read()/write()
│
▼
Page Cache(内存)
│ ← 缓存命中:直接返回,不访问磁盘
▼(缓存未命中)
磁盘 I/O- 读:先查 Page Cache,命中则直接返回;未命中则从磁盘读入并缓存。
- 写:数据先写入 Page Cache,标记为"脏页"(dirty page),由内核异步回写到磁盘(write-back 策略)。
脏页回写机制:
- 内核后台线程(
pdflush/flusher)定期将脏页写回磁盘 - 脏页比例超过阈值(
/proc/sys/vm/dirty_ratio)时触发强制回写 fsync(fd)/fdatasync(fd)可强制将指定文件的脏页立即刷盘
O_DIRECT — 绕过 Page Cache:
打开文件时传入 O_DIRECT 标志,读写直接在用户缓冲区与磁盘之间进行,跳过 Page Cache。适用于数据库等自行管理缓存的应用(如 MySQL InnoDB buffer pool)。代价是失去内核缓存带来的加速,且有内存对齐要求。
int fd = open("data.bin", O_RDWR | O_DIRECT);面试常问 & 怎么答
Q1:硬链接和软链接有什么区别?
硬链接是在目录中新增一条指向同一 inode 的映射,与原文件共享 inode 和数据块,引用计数 +1,删除其中任何一个名字只要引用计数不为 0 文件就不会消失。硬链接不能跨文件系统,也不能链接目录。
软链接是独立的文件,有自己的 inode,内容是目标文件的路径字符串。可以跨文件系统,可以链接目录,但如果目标被删除,软链接就会失效变成悬空链接。
一句话区分:硬链接是"别名",软链接是"快捷方式"。
Q2:什么是 inode?一个文件最大能多大?
inode 是文件系统中描述文件元数据的数据结构,存储权限、大小、时间戳和指向数据块的指针,但不存储文件名(文件名在目录项中)。
文件最大大小取决于文件系统的寻址能力。以 ext4 为例,块大小 4 KB 时:
- 直接指针覆盖 48 KB
- 加上三级间接指针理论上可达约 4 TB
- ext4 实际限制单文件最大 16 TB(受地址位数和文件系统配置限制)
此外,inode 数量在格式化时固定,若 inode 耗尽(大量小文件场景),即使磁盘有剩余空间也无法创建新文件,可用
df -i排查。
Q3:文件描述符是什么?fd 泄漏怎么排查?
文件描述符(fd)是进程访问打开文件的整数句柄。内核维护三级结构:进程级 fd 表 → 系统级打开文件表(记录偏移量和访问模式)→ inode 表。fd 0/1/2 分别对应 stdin/stdout/stderr。
fd 泄漏是指程序打开文件后未调用
close(),导致 fd 数量持续增长直到达到进程上限(默认 1024)而报错。排查方法:
lsof -p <pid>查看进程当前打开的所有文件,重点看是否有大量重复的文件名ls /proc/<pid>/fd | wc -l统计 fd 数量,持续增长说明有泄漏- 结合代码 review,检查异常路径(如抛出异常时)是否跳过了
close()- 使用 RAII(C++)或
try-with-resources(Java)、with语句(Python)等机制自动关闭文件
看到什么就先想到这类
| 关键词 | 联想到 |
|---|---|
| 文件名、路径 | 目录项 → inode 号映射,不是 inode 本身存文件名 |
| 文件删不掉 / 空间没释放 | 硬链接引用计数 > 0,或进程仍持有 fd |
| 磁盘满但 df 显示有空间 | inode 耗尽,df -i 检查 |
| 数据库自管缓存 | O_DIRECT,绕过 Page Cache |
| 进程 fork 后文件偏移共享 | 父子进程共享系统级打开文件表项 |
| 写入后断电数据丢失 | 脏页未回写,需 fsync() 保证持久化 |
| 磁盘 I/O 吞吐优化 | 磁盘调度算法(SSD 用 none,HDD 用 SCAN/deadline) |
| /proc、/dev、/sys | VFS 抽象,非磁盘文件系统通过 VFS 统一接口暴露 |