Linux 存储与文件系统深度剖析系列(一):总览与架构

Linux 存储与文件系统深度剖析系列(一):总览与架构

系列介绍

欢迎来到 Linux 存储与文件系统深度剖析系列!本系列将从代码级别深入探讨 Linux 内核中的存储子系统和文件系统实现,基于 Linux 6.4-rc1 内核源码进行分析。

系列文章目录

  1. 总览与架构(本文)
  2. VFS 虚拟文件系统层深度剖析
  3. 块设备层(Block Layer)详解
  4. 页缓存(Page Cache)与缓冲区缓存机制
  5. Ext4 文件系统源码分析
  6. Btrfs 文件系统核心原理
  7. XFS 文件系统实现细节
  8. IO 调度器深度剖析
  9. 直接 IO 与异步 IO 实现
  10. 文件系统性能优化与调试

Linux 存储栈整体架构

Linux 存储栈是一个复杂的分层架构,从用户空间到物理硬件,主要包含以下几层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌─────────────────────────────────────────────┐
│ 用户空间应用 │
│ (read, write, open, close, mmap...) │
└─────────────────┬───────────────────────────┘
│ System Call Interface
┌─────────────────▼───────────────────────────┐
│ VFS (Virtual Filesystem Switch) │
│ - struct inode, dentry, file, super │
│ - 通用文件操作接口 │
└─────────────────┬───────────────────────────┘

┌───────────┼───────────┐
│ │ │
┌─────▼────┐ ┌───▼────┐ ┌───▼────┐
│ Ext4 │ │ Btrfs │ │ XFS │ ... (具体文件系统)
└─────┬────┘ └───┬────┘ └───┬────┘
│ │ │
└──────────┼──────────┘

┌────────────────▼────────────────────────────┐
│ Page Cache / Buffer Cache │
│ (struct address_space, struct page) │
└────────────────┬────────────────────────────┘

┌────────────────▼────────────────────────────┐
│ Block Layer (块设备层) │
│ - struct bio, struct request │
│ - 块设备通用操作 │
└────────────────┬────────────────────────────┘

┌────────────────▼────────────────────────────┐
│ IO Scheduler (IO调度器) │
│ - mq-deadline, BFQ, Kyber │
└────────────────┬────────────────────────────┘

┌────────────────▼────────────────────────────┐
│ Block Device Drivers (块设备驱动) │
│ - SCSI, NVMe, virtio-blk, ... │
└────────────────┬────────────────────────────┘

┌────────────────▼────────────────────────────┐
│ Physical Storage (物理存储) │
│ - HDD, SSD, NVMe, RAID, ... │
└─────────────────────────────────────────────┘

核心数据结构概览

1. VFS 层核心结构

在 Linux 内核中,VFS 使用以下核心数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// include/linux/fs.h

// inode: 代表文件系统中的一个对象(文件、目录等)
struct inode {
umode_t i_mode; // 文件类型和权限
unsigned short i_opflags;
kuid_t i_uid; // 所有者 UID
kgid_t i_gid; // 所有者 GID
unsigned int i_flags;

const struct inode_operations *i_op; // inode 操作方法
struct super_block *i_sb; // 所属超级块
struct address_space *i_mapping; // 页缓存映射

loff_t i_size; // 文件大小(字节)
struct timespec64 i_atime; // 访问时间
struct timespec64 i_mtime; // 修改时间
struct timespec64 i_ctime; // 状态改变时间

unsigned long i_ino; // inode 编号
dev_t i_rdev; // 设备号(设备文件)

// ... 更多字段
};

// dentry: 目录项,连接路径名和 inode
struct dentry {
unsigned int d_flags;
seqcount_spinlock_t d_seq;
struct hlist_bl_node d_hash;
struct dentry *d_parent; // 父目录项
struct qstr d_name; // 文件名
struct inode *d_inode; // 关联的 inode

const struct dentry_operations *d_op; // dentry 操作方法
struct super_block *d_sb; // 所属超级块

// ... 更多字段
};

// file: 代表打开的文件
struct file {
struct path f_path; // 包含 dentry 和 vfsmount
struct inode *f_inode; // 缓存的 inode 指针
const struct file_operations *f_op; // 文件操作方法

spinlock_t f_lock;
atomic_long_t f_count; // 引用计数
unsigned int f_flags; // 打开标志
fmode_t f_mode; // 文件模式
loff_t f_pos; // 文件位置指针

struct address_space *f_mapping; // 页缓存映射

// ... 更多字段
};

// super_block: 超级块,代表已挂载的文件系统
struct super_block {
struct list_head s_list;
dev_t s_dev; // 设备号
unsigned char s_blocksize_bits;
unsigned long s_blocksize; // 块大小
loff_t s_maxbytes; // 最大文件大小
struct file_system_type *s_type; // 文件系统类型
const struct super_operations *s_op; // 超级块操作方法

unsigned long s_flags;
unsigned long s_magic; // 魔数
struct dentry *s_root; // 根目录项

// ... 更多字段
};

2. Block Layer 核心结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// include/linux/blk_types.h

// bio: Block I/O,描述一次块 I/O 操作
struct bio {
struct bio *bi_next; // 请求队列链表
struct block_device *bi_bdev; // 目标块设备
unsigned int bi_opf; // 操作标志(读/写等)

unsigned short bi_vcnt; // bio_vec 数组大小
unsigned short bi_max_vecs; // bio_vec 最大数量

atomic_t __bi_cnt; // 引用计数
struct bio_vec *bi_io_vec; // 指向页向量数组

bio_end_io_t *bi_end_io; // I/O 完成回调
void *bi_private; // 私有数据

// ... 更多字段
};

// request: 代表一个或多个 bio 组成的请求
struct request {
struct request_queue *q; // 所属请求队列
struct blk_mq_ctx *mq_ctx; // 多队列上下文
struct blk_mq_hw_ctx *mq_hctx; // 硬件队列上下文

unsigned int cmd_flags; // 命令标志
enum rq_qos_id rq_qos_id; // QoS ID

sector_t __sector; // 起始扇区
unsigned int __data_len; // 数据长度

struct bio *bio; // bio 链表头
struct bio *biotail; // bio 链表尾

// ... 更多字段
};

关键源码文件位置

基于 Linux 6.4-rc1 内核,以下是主要的源码文件位置:

VFS 层

  • fs/ - 文件系统核心代码
    • fs/namei.c - 路径查找和解析
    • fs/open.c - 文件打开操作
    • fs/read_write.c - 读写操作
    • fs/inode.c - inode 管理
    • fs/dcache.c - dentry 缓存
    • fs/super.c - 超级块管理

Block Layer

  • block/ - 块设备层代码
    • block/blk-core.c - 核心功能
    • block/blk-mq.c - 多队列实现
    • block/bio.c - bio 处理
    • block/blk-merge.c - 请求合并
    • block/blk-settings.c - 块设备设置

IO Schedulers

  • block/mq-deadline.c - Deadline 调度器
  • block/bfq-iosched.c - BFQ (Budget Fair Queueing) 调度器
  • block/kyber-iosched.c - Kyber 调度器

具体文件系统

  • fs/ext4/ - Ext4 文件系统
  • fs/btrfs/ - Btrfs 文件系统
  • fs/xfs/ - XFS 文件系统

Page Cache

  • mm/filemap.c - 文件映射和页缓存
  • mm/readahead.c - 预读机制
  • fs/buffer.c - 缓冲区缓存

一个文件读取的完整流程

让我们通过一个简单的 read() 系统调用,看看数据是如何从磁盘流向用户空间的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
用户程序: read(fd, buffer, size)
|
v
系统调用入口: sys_read() (fs/read_write.c)
|
v
VFS 层: vfs_read()
| - 检查权限和文件状态
| - 调用 file->f_op->read_iter()
v
文件系统层: ext4_file_read_iter() (以 Ext4 为例)
| - 调用 generic_file_read_iter()
v
Page Cache: filemap_read() (mm/filemap.c)
| - 在页缓存中查找数据
| - 如果缓存命中,直接返回
| - 如果缓存未命中,继续...
v
文件系统 readpage: ext4_readpage()
| - 准备从磁盘读取数据
| - 创建 bio 结构
v
Block Layer: submit_bio() (block/bio.c)
| - 将 bio 提交到块设备层
| - 可能进行请求合并
v
IO Scheduler: 调度器处理请求
| - mq-deadline / BFQ / Kyber
| - 优化 I/O 顺序
v
Block Driver: SCSI/NVMe/... 驱动程序
| - 向硬件发送命令
v
Hardware: 物理存储设备执行读取
|
v (中断返回)
Block Driver: 中断处理程序
|
v
Block Layer: bio_endio()
| - 调用 bio->bi_end_io() 回调
v
Page Cache: 将数据标记为最新
|
v
VFS: 将数据复制到用户空间缓冲区
|
v
用户程序: read() 返回

核心概念解析

1. 页缓存(Page Cache)

页缓存是 Linux 内存管理的核心组件之一,它缓存文件内容在内存中,避免重复的磁盘访问。

关键特性:

  • 以页(通常 4KB)为单位缓存文件数据
  • 使用 struct address_space 管理文件到物理页的映射
  • 支持预读(readahead)机制优化顺序读取
  • 支持回写(writeback)机制延迟写入

代码位置: mm/filemap.c

2. 块 I/O 层(Block Layer)

块设备层是连接文件系统和设备驱动的中间层。

核心功能:

  • 将文件系统的 I/O 请求转换为块设备请求
  • I/O 请求的合并和重排序
  • 支持多队列(blk-mq)架构
  • 提供 I/O 统计和 QoS 控制

代码位置: block/

3. VFS(Virtual Filesystem Switch)

VFS 是所有文件系统的抽象层,提供统一的接口。

设计理念:

  • 对上层提供统一的系统调用接口
  • 对下层提供标准的文件系统操作接口
  • 使用面向对象的设计思想(通过函数指针实现多态)

代码位置: fs/

代码示例:创建一个简单的文件系统

为了更好地理解 VFS,让我们看一个极简的文件系统示例(基于 ramfs):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 超级块操作
static const struct super_operations simple_super_ops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
};

// inode 操作
static const struct inode_operations simple_dir_inode_operations = {
.create = simple_create,
.lookup = simple_lookup,
.link = simple_link,
.unlink = simple_unlink,
.mkdir = simple_mkdir,
.rmdir = simple_rmdir,
.mknod = simple_mknod,
.rename = simple_rename,
};

// 文件操作
static const struct file_operations simple_file_operations = {
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.mmap = generic_file_mmap,
.fsync = noop_fsync,
.llseek = generic_file_llseek,
};

// 填充超级块
static int simple_fill_super(struct super_block *sb, void *data, int silent)
{
struct inode *inode;

sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = 0x12345678;
sb->s_op = &simple_super_ops;

// 创建根 inode
inode = new_inode(sb);
if (!inode)
return -ENOMEM;

inode->i_ino = 1;
inode->i_mode = S_IFDIR | 0755;
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = &simple_file_operations;

// 创建根 dentry
sb->s_root = d_make_root(inode);
if (!sb->s_root)
return -ENOMEM;

return 0;
}

性能关键路径

在 Linux 存储栈中,以下是几个性能关键路径:

1. 快速路径(Fast Path)

  • 页缓存命中
  • 零拷贝操作(sendfile, splice)
  • 直接 I/O(绕过页缓存)

2. 慢速路径(Slow Path)

  • 页缓存未命中导致的磁盘 I/O
  • 同步写入和 fsync 操作
  • 元数据操作(创建、删除文件)

调试工具

在学习和调试存储系统时,以下工具非常有用:

系统工具

1
2
3
4
5
6
7
8
9
10
11
12
# 查看块设备 I/O 统计
iostat -x 1

# 追踪块 I/O
blktrace -d /dev/sda -o - | blkparse -i -

# 查看文件系统缓存统计
cat /proc/meminfo | grep -i cache

# 查看 VFS 统计
cat /proc/sys/fs/dentry-state
cat /proc/sys/fs/inode-state

内核工具

1
2
3
4
5
6
# 动态追踪(需要 CONFIG_DYNAMIC_FTRACE)
trace-cmd record -e block:* -e vfs:* command

# BPF 工具
biolatency # I/O 延迟分布
biosnoop # 追踪块 I/O 事件

下一篇预告

在下一篇文章中,我们将深入探讨 VFS 虚拟文件系统层,包括:

  • inode、dentry、file 的生命周期管理
  • 路径查找算法的实现细节
  • dcache(dentry cache)的设计与优化
  • VFS 如何处理各种文件操作的源码分析

参考资料

  1. Linux 内核源码(v6.4-rc1):https://git.kernel.org/
  2. Linux 内核文档:Documentation/filesystems/
  3. 《深入 Linux 内核架构》
  4. 《Linux 内核设计与实现》

作者注: 本系列所有源码分析基于 Linux 6.4-rc1 内核版本。随着内核的演进,部分实现细节可能会有所变化,但核心设计理念保持相对稳定。