1. 概述
我们将进入下一个重要模块——文件系统和磁盘的 I/O 性能。相比CPU和内存,这应该是在计算机的资源环节里最慢的,也是比较出现性能瓶颈的模块。再次搬出这张图回顾一下。
与 CPU 和内存一样,磁盘和文件系统的管理也是操作系统的核心功能。其中:
- 磁盘:为系统提供最基本的持久化存储。
- 文件系统:基于磁盘,提供文件管理的树状结构。
本节我们将重点了解 Linux 文件系统的工作原理,而磁盘的工作原理将在下一节中详细学习。
首先了解文件系统的基本结构:索引节点和目录项
- 文件系统的作用
-
- 文件系统用于组织和管理存储设备上的文件。
- 在 Linux 中,一切皆文件(普通文件、目录、块设备、套接字、管道等),都通过统一的文件系统管理。
- 索引节点(inode)
-
- 作用:记录文件的元数据(如 inode 编号、文件大小、权限、修改日期、数据位置等)。
- 特点:
-
-
- 每个文件对应一个唯一的 inode。
- inode 持久化存储在磁盘中,占用磁盘空间。
-
- 目录项(dentry)
-
- 作用:记录文件名、inode 指针,以及与其他目录项的关系。
- 特点:
-
-
- 由内核维护,存储在内存中(称为目录项缓存)。
- 通过多个目录项,可以为同一文件创建别名(如硬链接),这些目录项指向相同的 inode。
-
- inode 与 dentry 的关系
-
- inode 是文件的唯一标志,dentry 则维护文件系统的树状结构。
- 关系是多对一:一个文件可以有多个 dentry(别名),但它们指向同一个 inode。
文件数据的存储方式
- 磁盘的读写单位
-
- 磁盘的最小读写单位是扇区(512B),但扇区大小过小,频繁读写会导致效率低下。
- 文件系统的优化
-
- 文件系统将连续的扇区组成逻辑块(如 4KB,由 8 个扇区组成)。
- 数据以逻辑块为最小单位进行读写,提升了磁盘 I/O 的效率。
总结:
- inode 记录文件元数据,并存储在磁盘中。
- dentry 记录文件名和目录结构,由内核维护的内存数据结构。
- 文件数据通过逻辑块管理,优化了磁盘的读写效率。
注意事项:
- 目录项与索引节点的缓存
-
- 目录项(dentry) :本质是一个内存缓存,由内核维护,用于加速文件名到 inode 的解析。
- 索引节点(inode) :虽然存储在磁盘中,但为了加速文件访问,也会缓存到内存中(类似页缓存 Cache)。
- 文件系统格式化后的存储区域
磁盘在格式化文件系统时,会划分为以下三个存储区域:
-
- 超级块(Superblock) :存储整个文件系统的状态信息。
- 索引节点区(inode table) :存储所有文件的索引节点。
- 数据块区(data block) :存储文件的实际数据内容。
其次需要了解:虚拟文件系统(VFS)
- VFS 的作用
-
- VFS(Virtual File System)是用户进程与底层文件系统之间的抽象层。
- VFS 定义了一组统一的数据结构和标准接口,使用户进程和内核只需与 VFS 交互,而无需关心底层文件系统的具体实现。
- 文件系统的四大基本要素
-
- 目录项(dentry) :记录文件名与 inode 的关联关系。
- 索引节点(inode) :记录文件元数据(如大小、权限等)。
- 逻辑块:磁盘上文件数据的基本存储单元。
- 超级块(superblock) :记录整个文件系统的状态信息。
文件系统的分类
按照存储位置,文件系统分为以下三类:
- 基于磁盘的文件系统
-
- 特点:将数据直接存储在本地挂载的磁盘中。
- 常见文件系统:Ext4、XFS、OverlayFS 等。
- 应用场景:用于持久化存储,如操作系统安装、磁盘分区等。
- 基于内存的文件系统
-
- 特点:不占用磁盘空间,数据存储在内存中。
- 常见文件系统:
-
-
/proc文件系统:提供内核数据的动态视图。/sys文件系统:导出内核对象到用户空间。
-
-
- 应用场景:用于系统信息查看和内核交互。
- 网络文件系统
-
- 特点:通过网络访问其他计算机上的数据。
- 常见文件系统:NFS、SMB、iSCSI 等。
- 应用场景:分布式存储、远程数据访问。
文件系统挂载
- 根目录挂载:
-
- 在安装系统时,先挂载根目录(/)。
- 子文件系统挂载:
-
- 在根目录下,将其他文件系统(如磁盘分区、/proc、/sys、NFS 等)挂载到 VFS 的目录树中某个子目录(挂载点)。
系统调用、VFS、缓存、文件系统以及块存储之间的关系
文件系统 I/O 涉及的主要内容如下:
- 文件系统挂载与访问
文件系统挂载到挂载点后,应用程序通过挂载点访问文件系统。VFS(虚拟文件系统)提供了一组标准的文件访问接口,以系统调用方式供应用程序使用。常见的文件访问操作包括open()、read()和write():
int open(const char *pathname, int flags, mode_t mode);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
2. 文件 I/O 分类
根据不同的特性,文件 I/O 可以被分类为以下几种:
-
- 缓冲与非缓冲 I/O
-
-
- 缓冲 I/O:通过标准库缓存加速文件访问,标准库内部通过系统调用访问文件。常见的场景是换行时才输出,输出内容暂时缓存。
- 非缓冲 I/O:直接通过系统调用访问文件,不经过标准库缓存。
-
-
- 直接与非直接 I/O
-
-
- 直接 I/O:跳过操作系统的页缓存,直接与文件系统交互,访问文件。这种方式通过设置
O_DIRECT标志实现。 - 非直接 I/O:通过操作系统的页缓存进行读写,最终通过内核或额外的系统调用将数据写入磁盘。
- 直接 I/O:跳过操作系统的页缓存,直接与文件系统交互,访问文件。这种方式通过设置
-
-
- 阻塞与非阻塞 I/O
-
-
- 阻塞 I/O:应用程序在进行 I/O 操作时,如果没有及时响应,会阻塞当前线程,不能执行其他任务。
- 非阻塞 I/O:I/O 操作后,应用程序不会阻塞当前线程,可以继续执行其他任务,最终通过轮询或事件通知获取结果。设置
O_NONBLOCK标志实现非阻塞访问。
-
-
- 同步与异步 I/O
-
-
- 同步 I/O:应用程序执行 I/O 操作后,必须等待整个 I/O 完成,才能继续其他操作。设置
O_SYNC或O_DSYNC标志,确保数据写入磁盘后才返回。 - 异步 I/O:应用程序发起 I/O 操作后,不需等待其完成,而是继续执行,完成后通过事件通知(如
SIGIO)告知应用程序。设置O_ASYNC选项时为异步 I/O。
- 同步 I/O:应用程序执行 I/O 操作后,必须等待整个 I/O 完成,才能继续其他操作。设置
-
通过这些分类,应用程序可以根据需求选择不同的 I/O 操作方式,以优化性能或满足特定的应用场景需求。
2. 性能观测
对于文件系统来说,空间不足是个非常严重的问题,所以一般会先监控使用量。常见命令就是df
$ df -h /dev/sda1
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 29G 3.1G 26G 11% /
除了文件数据,还要监控索引文件,也就是我们常说的inode。
$ df -i /dev/sda1
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 3870720 157460 3713260 5% /
缓存使用量
$ cat /proc/meminfo | grep -E "SReclaimable|Cached"
Cached: 748316 kB
SwapCached: 0 kB
SReclaimable: 179508 kB
如何观察文件系统中的目录项和索引节点缓存?实际上,内核使用 Slab 机制,管理目录项和索引节点的缓存。/proc/meminfo 只给出了 Slab 的整体大小,具体到每一种 Slab 缓存,还要查看 /proc/slabinfo 这个文件。
$ cat /proc/slabinfo | grep -E '^#|dentry|inode'
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
xfs_inode 0 0 960 17 4 : tunables 0 0 0 : slabdata 0 0 0
...
ext4_inode_cache 32104 34590 1088 15 4 : tunables 0 0 0 : slabdata 2306 2306 0hugetlbfs_inode_cache 13 13 624 13 2 : tunables 0 0 0 : slabdata 1 1 0
sock_inode_cache 1190 1242 704 23 4 : tunables 0 0 0 : slabdata 54 54 0
shmem_inode_cache 1622 2139 712 23 4 : tunables 0 0 0 : slabdata 93 93 0
proc_inode_cache 3560 4080 680 12 2 : tunables 0 0 0 : slabdata 340 340 0
inode_cache 25172 25818 608 13 2 : tunables 0 0 0 : slabdata 1986 1986 0
dentry 76050 121296 192 21 1 : tunables 0 0 0 : slabdata 5776 5776 0
3. 小结
- 文件系统概念
文件系统是对存储设备上的文件进行组织和管理的一种机制,确保文件的有效存储、访问和操作。文件系统不仅要处理文件存储的逻辑结构,还要保证数据的安全和一致性。 - 虚拟文件系统(VFS)
Linux 提供了一层虚拟文件系统(VFS)来支持不同类型的文件系统。VFS 定义了一组所有文件系统都支持的数据结构和标准接口,这些接口为用户进程和内核中的其他子系统提供了统一的交互方式。通过 VFS,应用程序与实际文件系统的实现解耦,可以更加方便地支持多种不同的文件系统(如 ext4、NTFS、FAT 等)。 - 性能优化:缓存机制
为了缓解慢速磁盘访问对性能的影响,Linux 文件系统使用了多种缓存机制:
-
- 页缓存:缓存磁盘上的数据页,减少磁盘访问,提高性能。
- 目录项缓存(dentry cache):缓存文件路径信息,避免每次访问都进行路径查找。
- 索引节点缓存(inode cache):缓存文件的元数据(如文件权限、大小、位置等),加速文件的读取。
- 性能观测:容量与缓存
在文件系统的性能观测中,容量和缓存是两个重要指标:
-
- 容量:指磁盘的存储能力,衡量系统能够处理的数据量。
- 缓存:指内存中存储的数据副本,通过缓存来减少对慢速磁盘的访问,提高系统的响应速度。
Linux 文件系统通过 VFS 提供统一的文件访问接口,同时采用多级缓存机制来优化磁盘访问性能。通过对容量和缓存的合理管理,可以有效地降低磁盘延迟对性能的影响。
参考文献:《Linux 性能优化实战》