11. 文件系统与 I/O

理解文件系统的组织方式:inode、目录、硬链接与软链接,掌握 Linux VFS、磁盘调度算法与 I/O 模型。

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-LOOKSCAN 的优化,不到头就折返更高效略复杂
假设磁头起始位置 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           # 使用某目录的进程

参考文章

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「计算机基础」更多文章

  1. 16. 数据链路层
  2. 15. 网络层与路由
  3. 14. 网络模型与协议