1. 文件系统基础
1.1 文件系统的作用
文件系统负责将磁盘上的裸数据组织成逻辑上的文件和目录,提供统一的读写接口。
文件系统层次:
用户接口层(open/read/write)
↓
虚拟文件系统 VFS(统一抽象)
↓
具体文件系统(ext4/XFS/Btrfs)
↓
块设备驱动(磁盘 I/O)
↓
物理磁盘(扇区、柱面)
1.2 磁盘物理结构
磁盘 → 柱面(Cylinder)→ 磁道(Track)→ 扇区(Sector,通常 512B 或 4KB)
寻道时间:磁头移动到目标磁道(最耗时)
旋转延迟:等待扇区旋转到磁头下方(半圈平均)
传输时间:读取数据到内存
2. inode 与文件存储
2.1 inode 结构
**inode(索引节点)**是文件系统管理文件的核心数据结构,每个文件/目录对应一个 inode。
inode 包含:
┌─────────────────────┐
│ 文件类型与权限 │
│ 文件大小 │
│ 时间戳(创建/修改/访问)│
│ 链接计数 │
│ 数据块指针(最重要!) │
└─────────────────────┘
注意:inode 不包含文件名!文件名存储在目录中。
2.2 数据块寻址
直接指针(12个)→ 直接指向数据块
间接指针(1个) → 指向一个块,该块存储更多指针
双重间接(1个)→ 两次间接,支持大文件
三重间接(1个)→ 三次间接,超大文件
12 × 4KB + 1024 × 4KB + 1024² × 4KB + 1024³ × 4KB
≈ 48KB + 4MB + 4GB + 4TB
2.3 硬链接 vs 软链接
ln file hardlink # 硬链接:与源文件共用同一个 inode
ln -s file symlink # 软链接:独立的 inode,存储目标路径
| 特性 | 硬链接 | 软链接(符号链接) |
|---|---|---|
| inode | 相同 | 不同 |
| 跨文件系统 | ❌ | ✅ |
| 链接目录 | ❌ | ✅ |
| 源文件删除 | 内容保留 | 悬空(dangling) |
| 大小 | 与源文件相同 | 路径字符串长度 |
硬链接示意图:
文件名 "a.txt" ──→ inode #12345 ──→ 数据块 [Hello World]
文件名 "b.txt" ──↗
软链接示意图:
文件名 "link" ──→ inode #67890 ──→ "a.txt"(路径字符串)
↓
文件名 "a.txt" ──→ inode #12345
3. 目录实现
3.1 目录项结构
目录本质也是文件,内容为:<文件名, inode 号> 的列表
目录文件内容示例:
┌──────────────┬──────────┐
│ 文件名 │ inode 号 │
├──────────────┼──────────┤
│ . │ 12345 │ ← 当前目录
│ .. │ 12344 │ ← 父目录
│ file1.txt │ 12346 │
│ dir1 │ 12347 │
└──────────────┴──────────┘
3.2 路径解析
"/home/user/docs/file.txt"
↓
根目录 inode → 读取内容找到 "home" → 获取 home 的 inode
↓
home 的 inode → 读取内容找到 "user" → 获取 user 的 inode
↓
...递归直到找到目标文件
4. 虚拟文件系统(VFS)
4.1 VFS 的四个核心对象
| VFS 对象 | 对应 | 作用 |
|---|---|---|
| superblock | 文件系统超级块 | 描述整个文件系统的元信息 |
| inode | 索引节点 | 描述单个文件的元信息 |
| dentry | 目录项 | 描述文件路径的一个分量 |
| file | 打开文件对象 | 描述进程打开文件的上下文 |
4.2 VFS 系统调用映射
用户调用: open("/tmp/foo.txt", O_RDONLY)
↓
VFS: sys_open() → lookup dentry → find inode → create file object
↓
ext4: ext4_open() → 分配 ext4 inode operations
↓
块层: 读取磁盘块缓存/发起 I/O 请求
5. 磁盘调度算法
5.1 算法对比
| 算法 | 策略 | 优点 | 缺点 |
|---|---|---|---|
| FCFS | 先来先服务 | 简单公平 | 寻道时间长 |
| SSTF | 最短寻道优先 | 总寻道短 | 饥饿问题 |
| SCAN(电梯) | 向一端扫描,到头折返 | 无饥饿 | 两端等待不均 |
| C-SCAN | 单向循环扫描 | 更均匀 | 返回时无服务 |
| LOOK/C-LOOK | SCAN 的优化,不到头就折返 | 更高效 | 略复杂 |
假设磁头起始位置 50,请求队列:[98, 183, 37, 122, 14, 124, 65, 67]
FCFS: 50 → 98 → 183 → 37 → 122 → 14 → 124 → 65 → 67
总寻道 = 640
SSTF: 50 → 65 → 67 → 37 → 14 → 98 → 122 → 124 → 183
总寻道 = 236
SCAN (向大): 50 → 65 → 67 → 98 → 122 → 124 → 183 → 0 → 14 → 37
总寻道 ≈ 200 + 183 + 37 = 420
C-SCAN: 50 → 65 → 67 → 98 → 122 → 124 → 183 → 跳回到 0 → 14 → 37
总寻道更均匀
6. Linux I/O 模型
6.1 五种 I/O 模型
| 模型 | 阻塞阶段 | 特点 |
|---|---|---|
| 阻塞 I/O | 数据准备 + 数据拷贝 | 最简单,效率低 |
| 非阻塞 I/O | 不阻塞,但轮询浪费 CPU | 需不断 check |
| I/O 多路复用 | select/poll/epoll 监控多个 fd | 单线程处理多连接 |
| 信号驱动 I/O | 数据准备好发信号,再拷贝 | Linux 少用 |
| 异步 I/O | 完全不阻塞,内核通知完成 | 最复杂,效率最高 |
6.2 epoll 详解
#include <sys/epoll.h>
// 1. 创建 epoll 实例
int epoll_fd = epoll_create1(0);
// 2. 注册感兴趣的 fd
struct epoll_event ev;
ev.events = EPOLLIN; // 监控可读事件
ev.data.fd = listen_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);
// 3. 等待事件发生
struct epoll_event events[MAX_EVENTS];
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == listen_fd) {
// accept 新连接
} else {
// 处理已有连接的数据
}
}
epoll vs select/poll:
- select:fd 数量受限(1024),每次需拷贝 fd_set 到内核
- poll:无数量限制,但仍需遍历所有 fd
- epoll:事件驱动,只返回有事件的 fd,O(1) 注册,O(活跃事件数) 等待
7. Linux 文件操作命令速查
# 文件类型查看
file /path/to/file
stat /path/to/file # 查看 inode 信息
ls -i # 查看 inode 号
# 链接操作
ln target link # 硬链接
ln -s target link # 软链接
readlink link # 查看软链接指向
# 文件系统信息
df -h # 磁盘使用情况
du -sh dir # 目录大小
dumpe2fs /dev/sda1 # ext 文件系统详情
# 挂载
mount /dev/sdb1 /mnt # 挂载
mount -t ext4 /dev/sdb1 /mnt # 指定类型
umount /mnt # 卸载
# 查看打开的文件
lsof -p [pid] # 进程打开的文件
lsof +D /path # 打开某目录下文件的进程
fuser -v /path # 使用某目录的进程
参考文章
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。