Ext系列文件系统
一、预备知识引入
1、文件系统基础定义
文件系统是操作系统的核心机制,主要用于抽象、管理磁盘、固态硬盘等存储设备上的数据。而Ext文件系统是Linux系统中生态覆盖最广、功能支持最完善的文件系统家族,也是Linux系统的主流文件系统。
2、磁盘结构与寻址方式
大众认知中的磁盘单圈存储结构并不完整,完整的物理磁盘是由多片盘面叠加组成的立体存储结构。我们可以通过拆分理解磁盘核心结构:将单个盘面的环形存储轨道定义为磁道;所有盘面同一位置的磁道组合形成柱面;单条磁道会被均匀划分为多个扇形存储单元,每个单元即为扇区,扇区是磁盘最小的物理存储单元。
为了方便理解磁盘寻址逻辑,我们可以用数组类比:将单个盘面的所有磁道展开,可看作一维数组;多个盘面叠加形成的柱面,可看作二维数组;海量柱面组合成的完整磁盘,就形成了三维数组。
磁盘传统寻址方式为CHS寻址,寻址顺序为:先定位目标数据所在的柱面,再确定柱面内对应的磁道(对应磁头位置),最后精准定位到对应扇区。但计算机底层不会使用复杂的多维数组寻址,而是将磁盘所有扇区统一线性编号,形成LBA逻辑块地址,也就是一维线性下标,以此简化数据寻址流程,提升读写效率。
3、“块”核心概念
硬盘属于典型的块设备,操作系统不会以单个扇区为单位读写数据,而是采用批量读写的方式,一次性读取连续多个扇区的数据,这一组连续扇区就构成了一个数据块(Block)。
数据块的大小在磁盘格式化时确定,一经设定无法修改,业界最常用的块大小为4KB。在文件系统中,块是文件数据存取的最小逻辑单位,所有文件的内容数据都以块为单位存储。
4、“分区”核心概念
物理磁盘可以划分为多个独立分区,Windows系统中常见的C盘、D盘、E盘就是磁盘分区的可视化体现。分区本质是对物理磁盘的逻辑格式化与空间划分,目的是实现数据分类存储、系统隔离,提升磁盘数据管理的安全性与规范性。
Linux系统的分区规则有明确限制,柱面是Linux分区的最小单位。Linux分区的核心逻辑是通过设定分区的起始柱面编号和结束柱面编号,划定专属的磁盘存储空间,完成分区创建。
5、inode核心概念
计算机中所有文件都由两部分组成:文件内容和文件属性。我们日常使用ls -l命令查看文件信息时,只会展示文件的部分核心属性。
[root@localhost linux]# ls -l
总⽤量 12
-rwxr-xr-x. 1 root root 7438 "9⽉ 13 14:56" a.out
-rw-r--r--. 1 root root 654 "9⽉ 13 14:56" test.c
上述输出内容共7列,分别对应:文件模式权限、硬链接数、文件所有者、所属用户组、文件大小、最后修改时间、文件名。这些信息只是文件属性的一部分,使用stat命令可以查看文件完整属性信息。
[root@localhost linux]# stat test.c
File: "test.c"
Size: 654 Blocks: 8 IO Block: 4096 普通⽂件
Device: 802h/2050d Inode: 263715 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-09-13 14:56:57.059012947 +0800
Modify: 2017-09-13 14:56:40.067012944 +0800
Change: 2017-09-13 14:56:40.069012948 +0800
stat命令输出的信息中,新增了各类时间戳、inode编号、数据块占用数等详细参数。其中inode(索引节点)是存储文件所有属性信息的核心载体,前文提到的文件内容存储在磁盘数据块中,而文件的权限、所有者、大小、时间戳等所有属性,全部存储在inode中。
每个Linux文件都拥有唯一对应的inode,ext2文件系统的inode结构体源码如下,完整定义了文件的所有属性字段:
/*
* Structure of an inode on the disk
*/
struct ext2_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks; /* Blocks count */
__le32 i_flags; /* File flags */
union {
struct {
__le32 l_i_reserved1;
} linux1;
struct {
__le32 h_i_translator;
} hurd1;
struct {
__le32 m_i_reserved1;
} masix1;
} osd1; /* OS dependent 1 */
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
__le32 i_generation; /* File version (for NFS) */
__le32 i_file_acl; /* File ACL */
__le32 i_dir_acl; /* Directory ACL */
__le32 i_faddr; /* Fragment address */
union {
struct {
__u8 l_i_frag; /* Fragment number */
__u8 l_i_fsize; /* Fragment size */
__u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; /* Fragment number */
__u8 h_i_fsize; /* Fragment size */
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; /* Fragment number */
__u8 m_i_fsize; /* Fragment size */
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; /* OS dependent 2 */
};
/*
* Constants relative to the data blocks
*/
#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)
备注:代码中定义的 EXT2_N_BLOCKS 数值为15。结合inode结构体,我们可以明确三个核心知识点:
1. 文件名不属于inode的存储范畴,inode只存储文件属性,不记录文件名;
2. inode的大小固定,主流规格为128字节或256字节;
3. 不同文件的内容大小、占用空间可以各不相同,但所有文件的inode属性数据大小完全一致。
二、ext2文件系统
1、宏观认知
磁盘无法直接存储文件,必须先格式化为指定类型的文件系统,才能实现文件的存储、组织与管理,这也是文件系统的核心作用。ext2作为经典的Linux文件系统,采用模块化管理思想,会将整个磁盘分区均匀划分为多个大小一致的块组。所有块组的结构、管理规则完全相同,只要掌握单个块组的管理逻辑,就能理解整个分区乃至整块磁盘的文件管理机制。
2、块组(Block Group)核心结构
每个块组由六大核心模块组成,各司其职、相互配合,完成文件数据与属性的存储管理,具体结构如下:
2-1、超级块(Super Block)
超级块是文件系统的核心“总台账”,专门存储文件系统的全局结构信息,完整描述整个分区的文件系统状态。其主要记录内容包括:分区内block总数量、inode总数量、空闲未使用的block与inode数量、单个block和inode的大小、文件系统最近挂载时间、最近数据写入时间、磁盘校验时间等关键全局参数。
超级块直接决定文件系统的完整性,一旦超级块数据损坏,整个分区的文件系统结构会彻底失效,无法正常读写数据。为了规避该风险,每个块组的开头都会备份一份超级块数据,所有备份的超级块信息完全一致,当某一块扇区损坏时,系统可通过其他块组的备份数据恢复文件系统,保障稳定性。
2-2、GDT块组描述符表(Group Descriptor Table)
块组描述符表用于精准描述每个块组的专属属性信息。分区被划分为多少个块组,就对应存在多少个块组描述符。单个块组描述符会记录对应块组的核心参数:inode表的起始位置、数据块的起始位置、当前块组空闲inode数量、空闲数据块数量等。和超级块一致,每个块组开头均会备份一份GDT数据,防止数据丢失。
2-3、块位图(Block Bitmap)
块位图是数据块的状态记录表,以二进制bit位为最小单位。每一个bit位对应块组内的一个数据块,通过0、1两种状态标记数据块的使用情况:0代表对应数据块空闲可用,1代表对应数据块已被占用,系统可通过块位图快速检索空闲数据块。
2-4、inode位图(Inode Bitmap)
inode位图的逻辑与块位图完全一致,同样以bit位为单位。每一个bit位对应一个inode节点,用于标记inode的空闲状态,快速筛选出可用于存储新文件属性的空闲inode节点。
2-5、inode节点表(Inode Table)
inode节点表是当前块组内所有inode节点的集合,专门用于存放所有文件的属性信息,包括文件大小、所有者、权限、各类时间戳、数据块指针等。需要注意的是,inode编号以单个分区为单位统一编排,无法跨分区使用,不同分区的inode编号允许重复。
2-6、DataBlock数据块区
数据块区是文件内容的唯一存储区域,由大量统一规格的Block组成,不同类型的文件,数据块的存储内容有所区别:
1. 普通文件:文件的实际内容、文本数据、程序代码等全部存储在对应的数据块中;
2. 目录文件:目录本身也是特殊文件,其数据块中仅存储子文件名与对应inode号的映射关系。日常ls -l看到的目录权限、大小、所有者等属性,均存储在目录文件的inode中,而非数据块中。
同时,数据块的编号也以单个分区为单位划分,数据块无法跨分区调用和存储数据。
3、Linux新建文件完整流程
在ext2文件系统中创建新文件,是内核依次完成属性存储、数据存储、状态记录、目录挂载的完整流程,具体步骤如下:
1. 分配inode、存储文件属性:内核通过inode位图检索空闲inode节点,分配一个空闲inode后,将文件的权限、所有者、大小、修改时间等所有属性信息写入该inode节点中;
2. 分配数据块、存储文件内容:根据文件大小确定需要的磁盘块数量,通过块位图检索对应数量的空闲数据块(如示例中的300、500、800号块),将内核缓冲区中的文件内容,分批复制到对应的数据块中;
3. 记录存储映射关系:内核在该文件的inode数据块指针区域,记录文件内容对应的所有数据块编号,建立“inode-数据块”的映射,确保后续可快速读取文件内容;
4. 挂载文件名到目录:将新文件的文件名与inode号的映射关系,写入当前所在目录的数据块中。至此,系统可通过目录匹配文件名,再通过文件名匹配inode,最终读取文件属性与内容,完成文件创建。
4、目录与文件名的本质
Linux磁盘中不存在独立的“目录结构”,磁盘底层仅有一种存储单元——文件,所有数据均以「属性+内容」的统一形式存储。目录只是系统定义的特殊文件,其核心作用是管理下属文件。
目录文件的内容十分单一,仅保存「子文件名-inode号」的映射表。用户访问任意文件时,必须先读取文件所在目录的内容,通过文件名匹配到对应的inode号,再通过inode读取文件属性和数据块内容,最终完成文件访问。
5、文件路径解析逻辑
当我们访问绝对路径/home/whb/code/test/test.c时,系统采用逐层递归解析的逻辑查找文件:从根目录出发,先找到home目录,再通过home目录匹配whb目录,依次递归匹配code、test目录,最后在test目录中通过文件名匹配test.c的inode号,读取文件数据。
为了保证Linux系统目录结构统一、功能规范,系统内核预设了固定的根目录及基础系统路径,各路径承担专属功能,是系统运行的基础:
/bin:存储基础系统命令,存放ls、cat等系统启动和日常运维必备工具;
/etc:存储系统与服务的配置文件,系统、软件的启动参数和配置均从该目录读取;
/home:用户家目录,为不同用户提供独立的存储空间,实现多用户隔离;
/var:存储系统可变数据,包括运行日志、程序缓存、动态生成的运行数据等;
/tmp:全局临时文件目录,所有进程、用户均可读写,用于存放临时数据,重启后数据清空;
/dev:设备文件目录,用于映射硬件设备,实现操作系统对硬件的访问控制;
/proc:虚拟文件系统目录,不占用磁盘空间,实时反映内核进程、系统硬件运行状态。
系统以预设基础路径为框架,用户和软件可在此基础上新建目录、文件,最终生长为完整的系统路径树。
6、路径缓存机制
针对文件路径解析,我们先解答三个核心问题,帮助理解缓存的意义:
问题1:Linux磁盘中存在真实的目录实体吗? 答案:不存在。磁盘底层只有文件,仅存储文件的属性和内容,目录是系统上层抽象出的概念。
问题2:每次访问文件都需要从根目录逐层解析路径吗? 答案:原则上初始访问需要逐层解析,但该方式效率极低,因此Linux内核引入了路径缓存机制,优化访问速度。
问题3:Linux的目录树形结构如何产生? 答案:系统将已打开的文件、目录信息,通过内核结构体在内存中动态维护,形成虚拟的树形目录结构。
Linux内核通过 dentry(目录项)结构体 维护内存中的路径树,也是路径缓存的核心载体,结构体源码如下:
struct dentry {
atomic_t d_count;
unsigned int d_flags; /* protected by d_lock */
spinlock_t d_lock; /* per dentry lock */
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
/*
* The next three fields are touched by __d_lookup. Place them here
* so they all fit in a cache line.
*/
struct hlist_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct list_head d_lru; /* LRU list */
/*
* d_child and d_rcu can share memory
*/
union {
struct list_head d_child; /* child of parent list */
struct rcu_head d_rcu;
} d_u;
struct list_head d_subdirs; /* our children */
struct list_head d_alias; /* inode alias list */
unsigned long d_time; /* used by d_revalidate */
struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
void *d_fsdata; /* fs-specific data */
#ifdef CONFIG_PROFILING
struct dcookie_struct *d_cookie; /* cookie, if any */
#endif
int d_mounted;
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};
dentry结构体的核心工作逻辑:
1. 系统中所有被打开的普通文件、目录文件,都会对应一个独立的dentry结构体,所有dentry在内存中拼接成完整的树形路径结构;
2. 内存空间有限,dentry不会永久缓存所有路径,系统通过LRU最近最少使用算法,自动淘汰长期未访问的路径节点,释放内存空间;
3. 所有dentry节点同时挂载在哈希表中,可通过哈希匹配快速检索路径,大幅提升文件查找效率;
4. 树形dentry集合构成Linux的路径缓存,访问文件时优先查询缓存:路径已缓存则直接读取inode与文件数据;未缓存则从磁盘逐层解析路径,生成新的dentry节点加入缓存。
三、软硬链接
1、硬链接
Linux系统支持多个不同文件名,绑定同一个inode节点,这种多文件名共享同一inode的关系,就是硬链接。
[root@localhost linux]# touch abc
[root@localhost linux]# ln abc def
[root@localhost linux]# ls -li abc def
263466 abc
263466 def
上述示例中,abc和def两个文件名对应同一个inode(263466),互为硬链接。内核会为该inode维护一个硬链接计数,此时该inode的链接数为2。
硬链接的文件删除逻辑:删除硬链接文件时,系统会执行两个操作,一是删除目录中该文件名与inode的映射记录,二是将inode的硬链接计数减1。仅当链接计数减为0时,系统才会释放该inode对应的磁盘数据块,真正删除文件数据。
2、软链接
软链接也叫符号链接,和硬链接的实现逻辑完全不同。硬链接是文件名与inode的映射关系,而软链接是一个独立的特殊文件,其数据块中不存储普通文件数据,仅存储目标文件的绝对/相对路径字符串,功能类似于Windows系统的快捷方式。
3、软硬链接核心对比
1. 软链接:属于独立文件,拥有自己专属的inode和数据块,依赖目标文件路径生效;
2. 硬链接:不属于独立文件,只是文件名与目标inode的映射关系,不生成新inode。
4、软硬链接核心用途
硬链接用途
1. 系统默认的`.`(当前目录)、`..`(上级目录)本质就是硬链接,用于支撑目录层级跳转;
2. 可实现简单的文件备份,多个硬链接可保障文件数据不被误删,任意一个链接文件可正常读取数据。
软链接用途
主要用于创建文件快捷方式,适配程序版本切换、路径迁移、目录便捷访问等场景,操作灵活、使用广泛。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)