在之前的文章中,我曾从源码角度分析过 linux 文件系统,但这种方式对于新手来说可能略显枯燥。所以,这次我将通过图文结合的方式,详细讲解 linux 文件系统的原理,而不会深入到源代码的细节。
一、硬盘简介
在介绍文件系统之前,我们先来了解一下硬盘。
众所周知,内存中的数据会在断电后丢失,因此现代计算机使用硬盘来存储数据。硬盘中的数据在断电后依然能够保存。
目前,常见的硬盘类型包括机械硬盘(HDD)和固态硬盘(SSD)。由于本文的重点是文件系统,所以不会详细介绍硬盘的原理。以下是机械硬盘和固态硬盘的对比图:

我们可以将硬盘想象成一个巨大的数组,每个元素代表一个数据块,如下图所示:

在 Linux 内核中,每个数据块的大小定义为 4KB。因此,一个 128GB 的硬盘可以被划分为 33554432 个数据块,内核通过数据块的编号来进行读写操作。
二、什么是文件系统
前面提到,内核通过数据块来读写硬盘,但这种方式对人类来说并不直观,因为我们无法记住每个数据块存储的内容。
为了让用户使用起来更方便和直观,Linux 内核抽象出两个概念来管理硬盘中的数据:文件(File)和目录(Directory)。
文件用于存储数据,目录用于存储文件列表,当然,目录也可以包含其他目录。由于数据存储在硬盘的数据块中,文件只需要记录哪些数据块属于它即可。如下图所示:

从图中可以看出,目录可以包含文件和子目录,而文件则记录了属于它的数据块编号。因此,读取或写入文件时,只需找到对应的文件数据块即可。
三、MINIX 文件系统实现
现在,我们以 MINIX 文件系统为例,详细介绍文件系统的设计原理。由于 MINIX 文件系统结构简单,非常适合用于教学。
在 MINIX 文件系统中,一个文件由 minix2_inode 对象描述。我们来看一下 minix2_inode 的定义:
struct minix2_inode {
__u16 i_mode; // 模式
__u16 i_nlinks; // 链接数
__u16 i_uid; // 所属用户UID
__u16 i_gid; // 所属组ID
__u32 i_size; // 文件大小
__u32 i_atime; // 访问时间
__u32 i_mtime; // 修改时间
__u32 i_ctime; // 创建时间
__u32 i_zone[10]; // 文件对应的数据块编号
};我们需要特别关注 minix2_inode 对象中的 i_zone 字段,它记录了属于当前文件的数据块编号。从定义来看,i_zone 是一个包含 10 个元素的整数数组。那么,是否意味着 MINIX 文件只能存储 40 KB 的数据呢?
答案是否定的。MINIX 文件系统将 i_zone 数组分为四个部分:前 7 个元素直接指向存储数据的数据块编号,第 8 个元素是一级间接指向,第 9 个元素是二级间接指向,第 10 个元素是三级间接指向。我们通过下图来解释这种关系:

通过这种多级指向的方式,一个 MINIX 文件可以存储超过 40KB 的数据。
既然有描述文件的对象,那么也应该有描述目录的对象。在 MINIX 文件系统中,目录也使用 minix2_inode 对象来描述。那么,如何区分文件和目录呢?
在 minix2_inode 对象中,有一个名为 i_mode 的字段,它保存了 minix2_inode 对应的类型。普通文件使用 S_IFREG 标志表示,而目录使用 S_IFDIR 标志表示。因此,从本质上来说,目录也是一种特殊的文件。
普通文件的数据块存储的是文件的数据,那么目录的数据块存储的是什么呢?答案是文件列表,每个表项使用 minix_dir_entry 对象表示,定义如下:
struct minix_dir_entry {
__u16 inode; // 当前文件对应的 minix2_inode 对象在 inode 数组中的索引
char name[0]; // 用于记录当前文件的文件名,由于文件名长度不固定,这里使用柔性数组表示
};我们通过下图来展示文件与目录在数据内容上的区别:

上图展示了文件与目录的两个明显区别:
i_mode 字段设置为 S_IFREG,而目录的 i_mode 字段设置为 S_IFDIR。i_zone 字段指向的数据块存储的是文件的数据,而目录的 i_zone 字段指向的数据块存储的是文件列表。现在,我们基本了解了 MINIX 文件系统对文件与目录的存储方式,接下来介绍 MINIX 文件系统如何管理硬盘中的文件和目录,也就是我们常说的格式化。
我们可以将硬盘视为一个由数据块组成的巨大数组,MINIX 文件系统将硬盘划分为以下几个部分,如下图所示:

下面我们对这几个部分进行解释:
minix_super_block 对象来保存文件系统的信息,如 inode位图 占用几个数据块、数据块位图 占用几个数据块等。inode表 中哪些成员已经被使用,每个位表示一个 inode 的使用情况。数据块列表 中哪些成员已经被使用,每个位表示一个数据块的使用情况。minix2_inode 对象组成,每个 minix2_inode 对象表示一个文件或目录。上图展示了 MINIX 文件系统在硬盘中的格式化结构。我们先来看一下 超级块 记录的信息有哪些,超级块由 minix_super_block 对象表示,其定义如下:
struct minix_super_block {
__u16 s_ninodes; // inode表的元素个数
__u16 s_nzones; // 数据块列表的元素个数(v1版本)
__u16 s_imap_blocks; // inode位图占用的数据块数量
__u16 s_zmap_blocks; // 数据块位图占用的数据块数量
__u16 s_firstdatazone; // 第一个数据块起始号
__u16 s_log_zone_size;
__u32 s_max_size; // 文件最大尺寸
__u16 s_magic; // 魔数(用于识别MINIX文件系统)
__u16 s_state; // 文件系统状态
__u32 s_zones; // 数据块列表的元素个数(v2版本)
};minix_super_block 每个字段的作用都在注释中进行了说明,通过 minix_super_block 对象我们可以了解到 MINIX 文件系统的信息。
了解了 MINIX 文件系统的结构组织,现在我们介绍一下 MINIX 文件系统读取文件的过程。
例如,我们要读取 /home/file.txt 文件的内容,MINIX 文件系统是如何准确地找到文件并读取其中的内容呢?下面我们分步描述这个过程。
第一步:读取根目录
要读取 /home/file.txt 文件,首先要从根目录 / 开始。MINIX 文件系统约定根目录使用 inode表 的第一个元素进行存储。如下图:

如上图所示,根目录使用 inode表 的第一个元素进行存储,然后从根目录的文件列表中查找目录 home。从图中可以看出,home 目录的 inode索引 为 5,表示 home 目录存储在 inode表 的第 5 个元素中。
第二步:读取 home 目录
知道 home 目录的 inode索引 为 5 后,再读取 inode表 的第 5 个元素,然后从 home 目录的文件列表中查找文件 file.txt,过程如下图:

如上图所示,从 home 目录的文件列表中找到 file.txt 文件的 inode索引 为 9,所以现在可以通过读取 inode表 的第 9 个元素来获得 file.txt 文件对应的 inode 节点。
第三步:读取 file.txt 文件的内容
现在我们已经知道了 file.txt 文件对应的 inode索引,所以从 inode表 中读取第 9 个元素即可获得 file.txt 文件的 inode节点,然后就可以通过 inode节点 的 i_zone 字段所指向的数据块来读取文件的内容,如下图所示:

如上图所示,通过读取 inode表 的第 9 个元素获得 file.txt 文件的 inode节点 后,可以通过 inode节点 的 i_zone 字段所指向的数据块读取文件的内容。
四、总结
本文通过 MINIX 这种简单的文件系统来介绍如何设计一个文件系统。虽然 Linux 系统有多种文件系统,但其基本思想都是如何有效地管理硬盘的数据。因此,掌握 MINIX 文件系统的设计对理解其他不同的文件系统有非常大的帮助。
以上就是图解 Linux 文件系统的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号