Ext 系列文件系统核心:块、分区、inode 与块组结构详解


🔥草莓熊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,存储在多个位置进行备份

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),并将文件属性(如权限、时间戳、所有者等)记录在其中。
  • 存储数据
    文件需要存储在三个磁盘块中。内核找到空闲块:300500800,并按顺序将缓冲区数据复制到这些块中。
  • 记录分配情况
    文件数据块按顺序 300500800 存放。内核在 i 节点的磁盘分布区记录这个块列表。
  • 添加文件名到目录
    文件名是 abc。内核在当前目录的目录文件中添加一个条目(入口):
    (i节点号: 263466, 文件名: abc)
    这个条目建立了文件名与文件内容和属性之间的连接。

补充几个核心注意事项:

  • 块和 inode 的数量在格式化时确定,若 inode 耗尽(如大量小文件),即使有空闲块也无法创建新文件;
  • 超级块和 GDT 的冗余备份是文件系统的 "救命稻草",若主超级块损坏,可通过备份恢复;
  • 目录也是文件,其内容是 "文件名→inode 号" 的映射,删除文件本质是删除目录中的该映射,并将 inode 和块标记为空闲(关于这个问题,我们下篇博客还会再继续讲的)。

结尾:

html 复制代码
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:文件系统的核心价值在于 "组织与管理"------ 通过块、分区、inode 将零散的扇区转化为有序的存储体系,而 Ext 系列文件系统的块组结构则进一步优化了这种管理的效率和可靠性。理解了块组的 "超级块→GDT→位图→inode 表→数据块" 链路,就等于掌握了 Ext2/3/4 文件系统的底层逻辑。下一篇博客,我们将深入探讨 inode 与数据块的映射机制(直接块、间接块)、路径解析、挂载等高级内容,带你彻底吃透 Ext 系列文件系统。

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど

相关推荐
有点心急10211 小时前
SQL 执行 MCP 工具开发(一)
人工智能·python·aigc
清风与日月2 小时前
OpenCV 图像显示高级技巧和常见问题
人工智能·opencv·计算机视觉
摘星编程2 小时前
突破界限!多模态AI如何重塑人机交互的未来?
人工智能·人机交互
勇气要爆发2 小时前
2026年想学AI,面对 Dify、Coze、n8n、LangChain 该学哪个?
人工智能·langchain·dify·coze·n8n
love530love2 小时前
ZeroClaw Reflex UI完整搭建流程——ZeroClaw Gateway + LM Studio + Reflex 本地 AI 管理面板
人工智能·windows·gateway·lm studio·reflex·openclaw·zeroclaw
CelestialYuxin2 小时前
FAMOSE:ReAct智能体驱动的自动化特征工程新框架
人工智能
qq_454245032 小时前
开源GraphMindStudio工作流引擎:自动化与AI智能体的理想核心
运维·人工智能·开源·c#·自动化
七夜zippoe2 小时前
微服务链路追踪实战:SkyWalking vs Zipkin 架构深度解析与性能优化指南
java·开发语言·微服务·springcloud·sleuth·zipkin
桂花很香,旭很美2 小时前
ADB 安卓实战手册
android·adb