
🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!
🎬 博主简介:

文章目录
- 前言:
- [一. 文件系统的核心铺垫:块、分区、inode](#一. 文件系统的核心铺垫:块、分区、inode)
-
- [1.1 块(Block):文件存取的最小单位](#1.1 块(Block):文件存取的最小单位)
- [1.2 分区(Partition):磁盘的 "逻辑切片"](#1.2 分区(Partition):磁盘的 “逻辑切片”)
- [1.3 inode:文件属性的 "身份证"](#1.3 inode:文件属性的 “身份证”)
- [二. Ext 系列文件系统核心:块组(Block Group)结构](#二. Ext 系列文件系统核心:块组(Block Group)结构)
-
- [2.1 块组的整体结构](#2.1 块组的整体结构)
- [2.2 块组内部核心构成(从管理到数据的完整链路)](#2.2 块组内部核心构成(从管理到数据的完整链路))
-
- [2.2.1 超级块(Super Block):文件系统的 "总配置文件"](#2.2.1 超级块(Super Block):文件系统的 “总配置文件”)
- [2.2.2 块组描述符表(GDT:Group Descriptor Table)](#2.2.2 块组描述符表(GDT:Group Descriptor Table))
- [2.2.3 块位图(Block Bitmap):块的 "占用状态记录表"](#2.2.3 块位图(Block Bitmap):块的 “占用状态记录表”)
- [2.2.4 inode 位图(Inode Bitmap):inode 的 "占用状态记录表"](#2.2.4 inode 位图(Inode Bitmap):inode 的 “占用状态记录表”)
- [2.2.5 inode 表(Inode Table):inode 的 "存储仓库"](#2.2.5 inode 表(Inode Table):inode 的 “存储仓库”)
- [2.2.6 数据块(Data Blocks):文件内容的 "存储区域"](#2.2.6 数据块(Data Blocks):文件内容的 “存储区域”)
- [三. 文件系统的核心工作流程:创建一个文件的底层逻辑(其它的就不展示了)](#三. 文件系统的核心工作流程:创建一个文件的底层逻辑(其它的就不展示了))
- 结尾:
前言:
在上一篇博客中,我们搞懂了磁盘的物理结构、逻辑抽象和寻址方式(CHS/LBA),知道了磁盘的最小存储单位是扇区。但扇区仅 512 字节,直接操作效率极低,且杂乱无章的扇区无法高效管理文件。这就需要
"文件系统"登场 ------ 它就像磁盘的 "管理员",通过划分块、分区、inode 等结构,将零散的扇区组织成有序的存储体系,让文件的创建、读取、修改、删除变得高效可控。本文从 "块""分区""inode" 等基础概念入手,深入剖析 Ext2/3/4 文件系统的核心设计 ------ 块组结构,帮你彻底搞懂文件系统是如何管理磁盘存储的。
一. 文件系统的核心铺垫:块、分区、inode
在认识 Ext 系列文件系统之前,必须先掌握三个核心前置概念,它们是文件系统设计的基石。
1.1 块(Block):文件存取的最小单位
块的引入原因:
扇区是磁盘的最小物理存储单位(512 字节),但频繁操作单个扇区会导致系统调用过多(每次 IO 都要切换内核态),效率极低。因此文件系统引入 "块" 的概念,将连续的多个扇区打包成一个块 ,作为文件存取的最小单位。
块的关键特性:
- 块大小由格式化时指定(常见 4KB=8 个扇区),一旦确定不可修改;
- 块是文件系统层面的逻辑概念,屏蔽了扇区的物理细节;
- 文件的内容数据会被分割成若干个块存储(不足一块则占用一块)。
验证块大小 :
通过stat命令可查看文件的块相关信息:

输出关键信息解读:
Blocks: 8:文件占用 8 个 "磁盘块"(不同系统块计数规则可能不同);IO Block: 4096:文件系统的块大小为 4KB(即 8 个扇区)。
注意:
- 磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇区
- 每个扇区都有LBA,那么8个扇区⼀个块,每⼀个块的地址我们也能算出来。
- 知道LBA:
块号 = LBA/8 - 知道块号:
LAB=块号*8 + n(n是块内第几个扇区)

1.2 分区(Partition):磁盘的 "逻辑切片"
分区的核心作用:
一块物理磁盘容量可能很大(如 1TB),若全盘只装一个文件系统,一旦损坏会导致所有数据丢失。分区就是将磁盘按柱面(磁盘物理结构的核心)分割成多个独立的 "逻辑磁盘",每个分区可单独格式化、挂载使用,实现数据隔离和灵活管理。
分区的本质:
- 分区的最小单位是柱面,每个分区有独立的起始和结束柱面;
- 从逻辑上看,分区就是磁盘的 "切片",每个切片都是一个独立的存储区域;
- Linux 中分区设备文件以
/dev/sda1、/dev/vda1等形式存在(如/dev/vda1表示第一块虚拟磁盘的第一个分区)。
实战查看分区信息:
通过fdisk命令查看系统分区:

Start/End:分区的起始 / 结束扇区号;Blocks:分区总块数(1 块 = 1KB,此处 41936658+ KB≈40GB);System:分区的文件系统类型(83 对应 Linux 系统)。
柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。 此时我们可以将硬盘上的柱面(分区)进行平铺,将其想象成一个大的平面,如下图所示:


其实分区还可以继续分为一个个组,这个我们后面还会再讲的。
1.3 inode:文件属性的 "身份证"
inode 的核心作用:
文件 = 属性(元数据)+ 内容。文件的内容存储在块中,而文件的属性(所有者、权限、大小、创建时间、块映射关系等)需要单独存储 ------ 这就是 inode(索引节点)的职责。
inode 的关键特性:
- 每个文件对应唯一的 inode,包含唯一的 inode 号;
- inode 大小固定(常见 128 字节或 256 字节),与文件内容大小无关;
- 文件名不存储在 inode 中,而是存储在目录的块数据中(目录本质也是文件,我们之后再来详细了解);
- inode 中最核心的字段是
i_block[EXT2_N_BLOCKS](15 个指针),记录文件内容所在的块编号。
inode 结构核心字段(Ext2 示例):
c
struct ext2_inode {
__le16 i_mode; // 文件权限(如0644)
__le16 i_uid; // 所有者ID
__le32 i_size; // 文件大小(字节)
__le32 i_atime; // 最后访问时间
__le32 i_mtime; // 最后修改时间(内容)
__le32 i_ctime; // 最后变更时间(属性)
__le16 i_gid; // 所属组ID
__le16 i_links_count; // 硬链接数
__le32 i_blocks; // 占用块数
__le32 i_block[15]; // 块映射指针(12直接+1一级间接+1二级间接+1三级间接)
};
实战查看 inode 信息:
通过ls -li命令可查看文件的 inode 号:

开头的号码就是该文件的 inode 号,通过 inode 号可唯一定位文件。
二. Ext 系列文件系统核心:块组(Block Group)结构
Ext2/3/4 文件系统的核心设计是 "块组"------ 将一个分区进一步划分为多个大小相等的块组(类似 "小区划分"),每个块组包含完整的管理结构和数据存储区域。这种设计的优势是:分散风险(单个块组损坏不影响全局)、提升效率(IO 操作集中在单个块组,减少磁头移动)。
2.1 块组的整体结构
一个 Ext2 分区的结构如下(从磁盘起始位置到末尾):

- Boot Block:固定 1KB,存储分区表和启动信息,文件系统不可修改;
- 每个 Block Group 结构完全相同,包含:超级块、块组描述符表、块位图、inode 位图、inode 表、数据块。
2.2 块组内部核心构成(从管理到数据的完整链路)
每个块组就像一个 "独立的小文件系统",内部结构层层递进,各司其职:
- 我们先来看看整体图示解析(很重要),再继续往下看


- 关键问题解答 (也可以先看下面的再返回过来理解):

2.2.1 超级块(Super Block):文件系统的 "总配置文件"
- 核心作用:存储整个分区的文件系统全局信息,是文件系统的 "大脑";
- 关键存储内容 :
- 块大小、inode 大小;
- 块总数、inode 总数、空闲块数、空闲 inode 数;
- 最近挂载时间、最近写入时间、最近磁盘检查时间;
- 魔数(
s_magic,Ext2 魔数为 0xEF53,用于识别文件系统类型);
- 冗余设计:超级块会在多个块组中备份(Block Group 0 必存),防止单点损坏导致整个文件系统崩溃。
- 部分源码(选择性的看):
cpp
/*
* 超级块结构
*/
struct ext2_super_block {
__le32 s_inodes_count; /* Inodes总数 */
__le32 s_blocks_count; /* 块总数 */
__le32 s_r_blocks_count; /* 保留块数 */
__le32 s_free_blocks_count; /* 空闲块数 */
__le32 s_free_inodes_count; /* 空闲Inodes数 */
__le32 s_first_data_block; /* 第一个数据块 */
__le32 s_log_block_size; /* 块大小(对数形式) */
__le32 s_log_frag_size; /* 片段大小(对数形式) */
__le32 s_blocks_per_group; /* 每组的块数 */
__le32 s_frags_per_group; /* 每组的片段数 */
__le32 s_inodes_per_group; /* 每组的Inodes数 */
__le32 s_mtime; /* 挂载时间 */
__le32 s_wtime; /* 写入时间 */
__le16 s_mnt_count; /* 挂载次数 */
__le16 s_max_mnt_count; /* 最大挂载次数 */
__le16 s_magic; /* 魔数签名(0xEF53) */
__le16 s_state; /* 文件系统状态 */
__le16 s_errors; /* 错误检测时的行为 */
__le16 s_minor_rev_level; /* 次修订级别 */
__le32 s_lastcheck; /* 上次检查时间 */
__le32 s_checkinterval; /* 检查最大间隔时间 */
__le32 s_creator_os; /* 创建操作系统 */
__le32 s_rev_level; /* 修订级别 */
__le16 s_def_resuid; /* 保留块的默认用户ID */
__le16 s_def_resgid; /* 保留块的默认组ID */
/*
* 以下字段仅用于EXT2_DYNAMIC_REV版本的超级块
*
* 注意:兼容特性集与不兼容特性集的区别在于,
* 如果不兼容特性集中有内核不认识的位,内核应拒绝挂载该文件系统。
*
* e2fsck的要求更严格:如果它不知道兼容或不兼容特性集中的某个特性,
* 它必须中止,并且不尝试修改它不理解的内容...
*/
__le32 s_first_ino; /* 第一个非保留inode编号 */
__le16 s_inode_size; /* inode结构大小 */
__le16 s_block_group_nr; /* 当前超级块所在的块组号 */
__le32 s_feature_compat; /* 兼容特性集 */
__le32 s_feature_incompat; /* 不兼容特性集 */
__le32 s_feature_ro_compat; /* 只读兼容特性集 */
__u8 s_uuid[16]; /* 128位卷UUID */
char s_volume_name[16]; /* 卷名称 */
char s_last_mounted[64]; /* 上次挂载的目录 */
__le32 s_algorithm_usage_bitmap; /* 压缩算法使用位图 */
/*
* 性能提示。仅当EXT2_COMPAT_PREALLOC标志开启时,
* 目录预分配才会生效。
*/
__u8 s_prealloc_blocks; /* 尝试预分配的块数 */
__u8 s_prealloc_dir_blocks; /* 为目录预分配的块数 */
__u16 s_padding1; /* 填充 */
/*
* 日志支持(当EXT3_FEATURE_COMPAT_HAS_JOURNAL设置时有效)
*/
__u8 s_journal_uuid[16]; /* 日志超级块UUID */
__u32 s_journal_inum; /* 日志文件的inode编号 */
__u32 s_journal_dev; /* 日志文件的设备编号 */
__u32 s_last_orphan; /* 待删除inode列表的起始 */
__u32 s_hash_seed[4]; /* HTREE哈希种子 */
__u8 s_def_hash_version; /* 默认使用的哈希版本 */
__u8 s_reserved_char_pad; /* 保留字符填充 */
__u16 s_reserved_word_pad; /* 保留字填充 */
__le32 s_default_mount_opts; /* 默认挂载选项 */
__le32 s_first_meta_bg; /* 第一个元数据块组 */
__u32 s_reserved[190]; /* 填充到块末尾 */
};
2.2.2 块组描述符表(GDT:Group Descriptor Table)
- 核心作用:每个块组对应其中一个描述符,记录该块组的详细属性信息;
- 关键存储内容 :
- 块位图的块编号(
bg_block_bitmap); - inode 位图的块编号(
bg_inode_bitmap); - inode 表的起始块编号(
bg_inode_table); - 该块组的空闲块数、空闲 inode 数、目录数;
- 块位图的块编号(
- 冗余设计:与超级块类似,GDT 也会在多个块组中备份
html
块组0: | 超级块 | GDT | 块位图 | inode位图 | inode表 | 数据块 |
块组1: | 超级块备份 | GDT备份 | 块位图 | inode位图 | inode表 | 数据块 |
块组3: | 超级块备份 | GDT备份 | 块位图 | inode位图 | inode表 | 数据块 |
块组5: | 超级块备份 | GDT备份 | 块位图 | inode位图 | inode表 | 数据块 |
块组7: | 超级块备份 | GDT备份 | 块位图 | inode位图 | inode表 | 数据块 |
...
- 部分源码(选择性的看):
c
/*
* 块组描述符结构(磁盘级)
*/
struct ext2_group_desc {
__le32 bg_block_bitmap; /* 块位图所在块号 */
__le32 bg_inode_bitmap; /* Inode位图所在块号 */
__le32 bg_inode_table; /* Inode表起始块号 */
__le16 bg_free_blocks_count; /* 空闲块数量 */
__le16 bg_free_inodes_count; /* 空闲Inode数量 */
__le16 bg_used_dirs_count; /* 目录数量(已使用的目录项) */
__le16 bg_pad; /* 填充对齐 */
__le32 bg_reserved[3]; /* 保留字段 */
};
- 补充说明 :
- 每个块组在GDT(组描述符表)中都有一个对应的
ext2_group_desc结构 - 这个结构描述的是特定块组的元数据位置和统计信息
- 所有块组的描述符按顺序组成GDT,存储在多个位置进行备份
- 每个块组在GDT(组描述符表)中都有一个对应的
2.2.3 块位图(Block Bitmap):块的 "占用状态记录表"
- 核心作用:用一个 bit 位标记一个块的状态(0 = 空闲,1 = 已占用);
- 高效性:查找空闲块时,只需遍历位图中的 bit 位,无需扫描所有块;
- 示例:若块位图的第 3 个 bit 为 1,表示该块组的第 3 个块已被占用。
html
字节0: [bit7][bit6][bit5][bit4][bit3][bit2][bit1][bit0]
第8块 第7块 第6块 第5块 第4块 第3块 第2块 第1块
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
最高位 ←-------- 字节内比特位顺序 --------→ 最低位
2.2.4 inode 位图(Inode Bitmap):inode 的 "占用状态记录表"
- 核心作用:与块位图逻辑一致,用一个 bit 位标记一个 inode 的状态(0 = 空闲,1 = 已占用);
- 关键特性:inode 编号是分区级全局唯一的,由块组号 + 块组内 inode 偏移量组成。
2.2.5 inode 表(Inode Table):inode 的 "存储仓库"
- 核心作用:连续的块组成 inode 表,每个块存储多个 inode(如 4KB 块可存储 32 个 128 字节的 inode);
- 关键特性 :inode 表的起始位置由 GDT 中的
bg_inode_table指定,通过 inode 号可快速计算出其在 inode 表中的位置。
2.2.6 数据块(Data Blocks):文件内容的 "存储区域"
- 核心作用:存储文件的内容数据,是块组中最大的区域;
- 存储类型 :
- 普通文件:直接存储文件内容;
- 目录文件:存储该目录下的 "文件名→inode 号" 映射关系;
- 链接文件:硬链接仅增加 inode 的链接数,软链接存储目标文件路径。
三. 文件系统的核心工作流程:创建一个文件的底层逻辑(其它的就不展示了)
结合上述结构,我们以创建一个普通文件(touch test.txt)为例,看看文件系统的底层操作流程:
- 查找空闲 inode:遍历 inode 位图,找到第一个值为 0 的 bit 位,标记为 1(占用),分配对应的 inode;
- 写入 inode 属性:将文件的属性(所有者、权限、创建时间等)写入 inode 表中对应的 inode,i_block数组初始化为 0;
- 查找空闲块:遍历块位图,找到空闲块,标记为 1(占用),若文件有内容则将内容写入该块,并更新 inode 的i_block数组(记录块编号);
- 更新目录数据:在当前目录的数据块中,添加一条 "文件名(test.txt)→inode 号" 的映射记录。
整个过程中,文件系统通过块位图、inode 位图快速定位空闲资源,通过 inode 关联文件属性与内容,通过目录维护文件名与 inode 的映射,高效完成文件创建。
实际示例 :

4 个步骤:
- 存储属性
内核找到一个空闲的 i 节点(这里是263466),并将文件属性(如权限、时间戳、所有者等)记录在其中。 - 存储数据
文件需要存储在三个磁盘块中。内核找到空闲块:300、500、800,并按顺序将缓冲区数据复制到这些块中。 - 记录分配情况
文件数据块按顺序300、500、800存放。内核在 i 节点的磁盘分布区记录这个块列表。 - 添加文件名到目录
文件名是abc。内核在当前目录的目录文件中添加一个条目(入口):
(i节点号:263466, 文件名:abc)
这个条目建立了文件名与文件内容和属性之间的连接。
补充几个核心注意事项:
- 块和 inode 的数量在格式化时确定,若 inode 耗尽(如大量小文件),即使有空闲块也无法创建新文件;
- 超级块和 GDT 的冗余备份是文件系统的 "救命稻草",若主超级块损坏,可通过备份恢复;
- 目录也是文件,其内容是 "文件名→inode 号" 的映射,删除文件本质是删除目录中的该映射,并将 inode 和块标记为空闲(关于这个问题,我们下篇博客还会再继续讲的)。

结尾:
html
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
结语:文件系统的核心价值在于 "组织与管理"------ 通过块、分区、inode 将零散的扇区转化为有序的存储体系,而 Ext 系列文件系统的块组结构则进一步优化了这种管理的效率和可靠性。理解了块组的 "超级块→GDT→位图→inode 表→数据块" 链路,就等于掌握了 Ext2/3/4 文件系统的底层逻辑。下一篇博客,我们将深入探讨 inode 与数据块的映射机制(直接块、间接块)、路径解析、挂载等高级内容,带你彻底吃透 Ext 系列文件系统。
✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど
