跳转至

05 - 文件系统

文件系统结构与索引示意图

建议学习时间:2.5 小时 🎯 难度等级:⭐⭐⭐ 📋 前置知识:内存管理基础概念、磁盘 I/O 基础


📋 本章目录


一、文件概念与属性

1.1 文件的定义

文件(File) 是操作系统对存储设备上数据的逻辑抽象——一个命名的、按字节序列组织的持久化数据集合。对用户而言,文件是信息存储和组织的基本单元。

1.2 inode 详解

在 Unix/Linux 文件系统中,inode(Index Node,索引节点) 是文件系统的核心数据结构,存储文件的所有元数据(不含文件名)。

Text Only
inode 结构
┌────────────────────────────────┐
│  文件类型(普通文件/目录/链接…) │
│  权限(rwxr-xr-x)             │
│  拥有者 UID / 组 GID           │
│  文件大小(字节)               │
│  时间戳:                      │
│    - atime(最后访问时间)       │
│    - mtime(最后修改时间)       │
│    - ctime(元数据变更时间)     │
│  硬链接计数                     │
│  数据块指针:                   │
│    - 12个直接指针               │
│    - 1个一级间接指针             │
│    - 1个二级间接指针             │
│    - 1个三级间接指针             │
└────────────────────────────────┘

💡 文件名存在目录文件中,而非 inode 中。目录是一张"文件名 → inode 号"的映射表。

1.3 inode 与数据块指针

以 ext4 为例(块大小 4KB,指针 4 字节):

指针类型 指向数据块数 寻址范围
12 个直接指针 12 块 48 KB
一级间接 1024 块 4 MB
二级间接 1024 × 1024 块 4 GB
三级间接 1024^3 块 4 TB
Text Only
inode
┌──────────┐
│直接指针0  │──→ [数据块]
│直接指针1  │──→ [数据块]
│ ...      │
│直接指针11 │──→ [数据块]
├──────────┤
│一级间接   │──→ [指针块] ──→ [数据块],[数据块],[数据块]...
├──────────┤
│二级间接   │──→ [指针块] ──→ [指针块] ──→ [数据块]...
├──────────┤
│三级间接   │──→ [指针块] ──→ [指针块] ──→ [指针块] ──→ [数据块]...
└──────────┘

💡 ext4 使用 Extent(区段) 替代传统的间接指针方式,一个 Extent 记录"起始块号 + 连续块数",大幅减少元数据开销,尤其对大文件效果显著。

1.4 硬链接与软链接

特性 硬链接 软链接(符号链接)
创建命令 ln file hardlink ln -s file softlink
inode 与原文件相同 inode 新建一个独立 inode
跨文件系统 不能 可以
原文件删除后 仍可访问(链接计数 > 0) 失效(悬空链接)
能否链接目录 不能(避免环路) 可以

二、文件物理结构

文件在磁盘上的数据块如何组织?三种经典方案:

2.1 三种分配方式对比

特性 连续分配 链接分配 索引分配
原理 每个文件占据一组连续磁盘块 每个块包含指向下一块的指针 一个索引块存储所有数据块号
顺序访问 ★★★ 极快 ★★☆ 顺序遍历链表 ★★☆ 需查索引
随机访问 ★★★ 直接计算偏移 ★☆☆ 必须遍历链表 ★★★ 索引直接定位
空间利用率 差(外部碎片严重) 好(无外部碎片)
文件增长 困难(可能需要搬迁) 简单(追加新块) 中等(索引块可能溢出)
可靠性 低(一个指针损坏则后续丢失)
典型应用 CD-ROM、磁带 早期 FAT 文件系统 Unix/Linux(inode)

2.2 连续分配

Text Only
磁盘:
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ A0 │ A1 │ A2 │    │ B0 │ B1 │    │    │
└────┴────┴────┴────┴────┴────┴────┴────┘
文件 A:起始块=0,长度=3
文件 B:起始块=4,长度=2

2.3 链接分配

Text Only
文件 A 的块链:
block 2 → block 7 → block 4 → block 12 → NULL
  │          │          │          │
[数据|7]  [数据|4]  [数据|12] [数据|NULL]

FAT(File Allocation Table) 是对链接分配的改进:将所有块的"下一块"指针集中存放在 FAT 表中,加速随机访问。

2.4 索引分配

Text Only
索引块(索引节点):
┌─────────────┐
│ 块号: 4      │
│ 块号: 7      │
│ 块号: 2      │
│ 块号: 12     │
│ ...          │
└─────────────┘

三、目录实现

目录本身也是一种特殊文件,存储"文件名 → 文件属性"的映射。

3.1 线性列表

最简单的实现:按顺序存储所有的 <文件名, inode号> 对。

Text Only
目录文件内容(线性列表):
┌──────────────┬────────┐
│ 文件名        │ inode号 │
├──────────────┼────────┤
│ hello.c      │ 1024   │
│ readme.txt   │ 2048   │
│ Makefile     │ 512    │
│ ...          │ ...    │
└──────────────┴────────┘
  • 查找:O(n) 线性扫描
  • 创建/删除:需检查重名,O(n)

3.2 哈希表

为目录文件名建立哈希表,加速查找。

  • 查找:近似 O(1)
  • 缺点:需处理哈希冲突,表大小固定时扩展麻烦

💡 ext4 使用 HTree(Hash Tree)——基于 B 树的哈希索引结构,在大目录(数万文件)中保持高效查找。


四、磁盘空间管理

如何跟踪磁盘上哪些块空闲、哪些已分配?

4.1 位图法(Bitmap)

每个磁盘块对应一个 bit:0 表示空闲,1 表示已占用。

Text Only
位图:1 1 0 1 1 0 0 1 0 0 ...
块号:0 1 2 3 4 5 6 7 8 9
→ 块 2, 5, 6, 8, 9 空闲
  • 优点:简单高效,适合查找连续空闲块
  • 缺点:位图本身占用空间(1TB 磁盘 / 4KB 块 = 32MB 位图)

4.2 空闲链表

将所有空闲块链成一个链表,表头指针指向第一个空闲块。

  • 优点:不浪费额外空间
  • 缺点:分配连续块效率低,遍历开销大

4.3 成组链接法(Unix 经典方法)

将空闲块分组管理。超级块中存放第一组空闲块号,第一组的第一个块存放下一组的块号,依此类推。

Text Only
超级块                         组1首块                组2首块
┌──────────────┐          ┌──────────────┐       ┌──────────┐
│ 空闲块数: 100 │          │ 空闲块数: 100│       │ 空闲块数:│
│ 块号列表:     │          │ 块号列表:    │       │ 块号列表 │
│ [201]→       │──tail──→ │ [301]→       │──→    │ ...      │
│ [202]        │          │ [302]        │       └──────────┘
│ [203]        │          │ [303]        │
│ ...          │          │ ...          │
└──────────────┘          └──────────────┘
  • 优点:兼顾位图法和链表法的优势,批量分配/释放高效

五、VFS 虚拟文件系统层

VFS(Virtual File System) 是 Linux 内核中的抽象层,为用户空间提供统一的文件操作接口,屏蔽底层不同文件系统的差异。

Text Only
  用户空间
  ─────────────────────────────────────
  系统调用接口:open(), read(), write(), close()
  ─────────────────────────────────────
  ┌────────────────────────────────┐
  │          VFS 层                │  ← 统一抽象
  │  super_block / inode /         │
  │  dentry / file 四大对象         │
  └────────────────────────────────┘
     │          │          │
     ▼          ▼          ▼
  ┌──────┐ ┌──────┐ ┌──────┐
  │ ext4 │ │ XFS  │ │ NFS  │  ← 具体文件系统实现
  └──────┘ └──────┘ └──────┘
     │          │          │
     ▼          ▼          ▼
  [本地磁盘]  [本地磁盘]  [网络]

VFS 四大核心对象

对象 说明
super_block 描述一个已挂载的文件系统(块大小、inode 总数、魔数等)
inode VFS 层的 inode 抽象,代表一个文件的元数据
dentry 目录项缓存(路径组件 → inode 的映射),加速路径查找
file 已打开的文件对象,包含文件偏移量、访问模式等

六、日志文件系统

6.1 问题背景

如果在文件写入过程中突然断电或系统崩溃,文件系统可能处于不一致状态。传统方法(fsck)在系统重启后全盘扫描修复——在大磁盘上可能耗费数小时。

6.2 WAL(Write-Ahead Logging)原理

日志文件系统借鉴数据库的 WAL 思想:

Text Only
写操作流程:
  ① 将修改操作写入日志区(Journal) ← Write-Ahead
  ② 日志写入完成后,提交事务(Commit)
  ③ 将数据实际写入文件系统对应位置
  ④ 数据写入成功后,标记日志条目完成(删除/回收)

崩溃恢复: - 系统重启后只需检查日志区 - 已提交但未写入实际位置的操作 → 重做(Redo) - 未提交的操作 → 丢弃 - 恢复速度:秒级(vs 传统 fsck 的小时级)

6.3 日志模式

以 ext4 为例:

模式 说明 性能 安全性
journal 元数据 + 数据都写日志 最慢 最高
ordered(默认) 仅元数据写日志,但保证数据先写 中等 较高
writeback 仅元数据写日志,数据无序写入 最快 较低

七、常见文件系统对比

特性 ext4 XFS NTFS ZFS Btrfs
操作系统 Linux Linux Windows FreeBSD/Linux Linux
最大文件大小 16 TB 8 EB 16 TB 16 EB 16 EB
最大卷大小 1 EB 8 EB 256 TB 256 ZB 16 EB
日志支持 ✅(ZIL)
快照 ✅(VSS) ✅(COW) ✅(COW)
数据校验 元数据 元数据 ✅(端到端)
压缩
去重 ✅(离线)
RAID 支持 需 mdadm 需 mdadm 需外部 原生(RAID-Z) 原生
在线扩容
典型场景 通用 Linux 大文件/数据库 Windows 桌面 NAS/存储服务器 高级 Linux 存储

八、Buffer Cache 与 Page Cache

8.1 历史与统一

  • Buffer Cache:缓存磁盘(block),以块设备为单位
  • Page Cache:缓存文件(page),以文件页面为单位

💡 Linux 2.4 之后,两者已统一为 Page Cache。在 /proc/meminfo 中看到的 Buffers 实际上是 Page Cache 中用于追踪块设备元数据的部分。

8.2 读写流程

Text Only
读操作:
  应用 read() → 内核检查 Page Cache
    ├── 命中 → 直接返回(零磁盘 I/O)
    └── 未命中 → 从磁盘读入 Page Cache → 返回给应用

写操作(writeback 模式):
  应用 write() → 写入 Page Cache(标记为 Dirty)→ 立即返回
                  └── 后台 pdflush/writeback 线程定期将脏页写回磁盘

脏页回写时机: - 脏页超过阈值(dirty_ratio,默认 20%) - 脏页存在时间超过阈值(dirty_expire_centisecs,默认 30 秒) - 用户显式调用 sync / fsync - 内存紧张时

Bash
# 查看脏页回写参数
cat /proc/sys/vm/dirty_ratio              # 脏页占可用内存比例上限
cat /proc/sys/vm/dirty_background_ratio   # 后台开始回写的阈值
cat /proc/sys/vm/dirty_expire_centisecs   # 脏页过期时间(厘秒)

九、Linux 文件系统实战

9.1 inode 操作

Bash
# 查看文件的 inode 信息
stat myfile.txt
# 输出示例:
#   File: myfile.txt
#   Size: 1024       Blocks: 8          IO Block: 4096   regular file
#   Device: 801h/2049d    Inode: 1234567    Links: 1
#   Access: (0644/-rw-r--r--)  Uid: (1000/user)   Gid: (1000/user)
#   Access: 2025-01-15 10:30:00
#   Modify: 2025-01-15 09:00:00
#   Change: 2025-01-15 09:00:00

# 查看文件系统 inode 使用情况
df -i

# 通过 inode 号查找文件
find / -inum 1234567

# 列出文件的 inode 号
ls -i myfile.txt

9.2 df —— 磁盘空间使用情况

Bash
df -hT
# 输出:
# Filesystem     Type   Size  Used Avail Use% Mounted on
# /dev/sda1      ext4   50G   25G   23G  53% /
# tmpfs          tmpfs  7.8G  1.2G  6.6G  16% /dev/shm
# /dev/sdb1      xfs    200G  120G   80G  60% /data
参数 说明
-h 人性化显示(KB/MB/GB)
-T 显示文件系统类型
-i 显示 inode 使用情况

9.3 du —— 目录/文件空间占用

Bash
# 查看当前目录各子目录大小
du -sh *

# 查看某目录总大小
du -sh /var/log

# 找出占空间最多的前10个目录
du -h --max-depth=1 / 2>/dev/null | sort -rh | head -10

9.4 mount —— 挂载文件系统

Bash
# 挂载磁盘分区
mount /dev/sdb1 /mnt/data

# 以只读方式挂载
mount -o ro /dev/sdb1 /mnt/data

# 查看所有挂载点
mount | column -t

# 挂载 ISO 文件
mount -o loop image.iso /mnt/iso

# 卸载
umount /mnt/data

9.5 lsof —— 列出打开的文件

Bash
# 查看某个文件被哪些进程打开
lsof /var/log/syslog

# 查看某个进程打开的所有文件
lsof -p 1234

# 查看某个端口关联的进程
lsof -i :8080

# 查看已删除但仍被占用的文件(磁盘不释放的元凶)
lsof +L1

9.6 stat —— 详细文件信息

Bash
# 查看文件详细元数据
stat /etc/passwd

# 自定义输出格式
stat --format='Name: %n, Size: %s, Inode: %i, Links: %h' myfile.txt

9.7 实用排查场景

Bash
# 场景1:磁盘满了但找不到大文件——检查已删除但未释放的文件
lsof +L1 | grep deleted

# 场景2:inode 耗尽(df 显示有空间但无法创建文件)
df -i  # 检查 IUse%

# 场景3:检查文件系统类型
lsblk -f

十、面试高频题 📋

题目 1:什么是 inode?它与文件名的关系?

:inode 是文件系统中存储文件元数据的数据结构,包含文件大小、权限、时间戳、数据块指针等一切信息——除了文件名。文件名存储在目录文件中,目录是一张"文件名 → inode 号"的映射表。一个 inode 可对应多个文件名(硬链接),但一个文件名只对应一个 inode。

题目 2:硬链接和软链接的区别?

硬链接与原文件共享同一个 inode,删除原文件后硬链接仍可访问,不能跨文件系统、不能链接目录。软链接(符号链接)有独立的 inode,存储的是目标文件的路径字符串,可跨文件系统、可链接目录,但原文件删除后软链接失效(悬空链接)。

题目 3:连续分配、链接分配、索引分配的优缺点?

连续分配——顺序和随机访问都快,但有外部碎片且文件难以增长。链接分配——无外部碎片、文件可任意增长,但随机访问需遍历链表、可靠性差。索引分配——支持高效随机访问、无外部碎片,但索引块有额外空间开销。现代文件系统(ext4)多采用索引分配的变种。

题目 4:什么是 VFS?为什么需要它?

:VFS(Virtual File System)是 Linux 内核中的抽象层,为所有文件系统(ext4、XFS、NFS 等)提供统一的接口。用户程序调用 open()/read()/write() 等系统调用时,VFS 将请求分发到具体文件系统的实现。这样应用程序无需关心底层文件系统类型,实现了"一切皆文件"的理念。

题目 5:什么是日志文件系统?为什么比传统文件系统更安全?

:日志文件系统在实际修改文件系统元数据之前,先将修改操作写入日志区(Write-Ahead Logging)。如果系统崩溃,重启后只需重放日志即可恢复一致性(秒级),无需像传统 fsck 那样全盘扫描修复(可能数小时)。ext4 默认使用 ordered 日志模式。

题目 6:解释 Page Cache 的作用和脏页回写机制

:Page Cache 是内核在内存中缓存文件数据的机制,将最近访问的文件页保留在内存中。读操作优先从 Page Cache 获取(命中则零磁盘 I/O);写操作先写入 Page Cache 并标记为脏页,后台线程定期或在特定条件下将脏页写回磁盘。这极大提高了 I/O 性能,但异常断电可能丢失未回写的数据。

题目 7:磁盘空间满了但 du 显示占用不多,可能是什么原因?

:最常见的原因是已删除但仍被进程打开的文件。文件已在目录中移除(du 看不到),但由于进程仍持有文件描述符,磁盘空间未释放。用 lsof +L1 | grep deleted 查找此类文件。解决方法:重启相关进程或让其关闭文件描述符。另一种可能是 inode 耗尽df -i 检查)。

题目 8:ext4 与 XFS 如何选择?

ext4 成熟稳定、兼容性好、适合通用场景,是大多数 Linux 发行版的默认文件系统。XFS 在大文件、高并发 I/O 场景表现更好(如数据库、视频存储),支持在线碎片整理和并行 I/O。一般桌面和中小服务器使用 ext4,大规模存储和高 I/O 需求选 XFS。

题目 9:什么是 ZFS 的 COW(Copy-On-Write)?有什么好处?

:COW 是 ZFS/Btrfs 的核心写入策略:修改数据时不覆盖原有块,而是写入新位置,然后更新指针。好处:① 天然的快照支持——快照只需保留旧指针;② 数据一致性——崩溃后旧数据完整;③ 端到端校验——每个块有校验和,读取时自动校验。缺点是随机写性能可能下降、碎片化。

题目 10:解释 "一切皆文件" 的含义

:这是 Unix/Linux 的核心设计哲学。不仅普通文件和目录,设备(/dev/sda)、进程信息(/proc)、网络套接字、管道等都被抽象为文件,通过统一的 open/read/write/close 接口操作。这简化了系统设计和编程——程序只需掌握一套 I/O 接口即可与各种资源交互。


十一、练习题 ✏️

练习 1:inode 计算

一个 ext4 文件系统块大小为 4KB,指针大小为 4 字节,每个 inode 有 12 个直接指针、1 个一级间接指针、1 个二级间接指针、1 个三级间接指针。 1. 单个文件最大可以有多少个数据块? 2. 单个文件最大大小约为多少?

练习 2:空间管理

假设一个磁盘有 100,000 个块,使用位图法管理空闲块: 1. 位图需要多少存储空间? 2. 如果要分配连续的 10 个空闲块,描述查找过程。

练习 3:文件操作实战

在 Linux 系统上完成以下操作: 1. 创建一个文件 test.txt 并写入内容 2. 创建一个硬链接和一个软链接 3. 用 stat 命令分别查看三者的 inode、链接数 4. 删除原始文件后,分别测试硬链接和软链接是否可用

练习 4:日志模式对比

分别用 journalorderedwriteback 三种日志模式挂载一个 ext4 文件系统,用 dd 写入 1GB 文件,比较写入时间。思考为什么 journal 模式最慢。

练习 5:排查磁盘问题

编写一个 Shell 脚本,实现以下功能:

Bash
#!/bin/bash
# 磁盘健康检查脚本

# 1. 显示所有文件系统使用率
echo "=== 磁盘空间 ==="
df -hT

# 2. 显示 inode 使用率
echo "=== inode 使用 ==="
df -i

# 3. 找出占空间最大的前10个目录
echo "=== 最大目录 ==="
du -h --max-depth=1 / 2>/dev/null | sort -rh | head -10  # |管道:将前一命令的输出作为后一命令的输入

# 4. 检查已删除但未释放的文件
echo "=== 已删除未释放 ==="
lsof +L1 2>/dev/null | grep deleted  # grep文本搜索:按模式匹配行

📚 延伸阅读

  • 《操作系统概念》(恐龙书)第 11-13 章 —— 文件系统
  • 《深入理解 Linux 内核》第 12 章 —— 虚拟文件系统
  • 《Linux 内核设计与实现》第 13 章 —— 虚拟文件系统
  • ext4 官方文档
  • Linux VFS 内核文档
  • ZFS on Linux