目录
[Super Block(超级块)](#Super Block(超级块))
[GDT(Group Descriptor Table)](#GDT(Group Descriptor Table))
[Block Bitmap(块位图)](#Block Bitmap(块位图))
[Inode Bitmap(inode 位图)](#Inode Bitmap(inode 位图))
[Inode Table(索引节点表)](#Inode Table(索引节点表))
[Data Blocks(数据块)](#Data Blocks(数据块))
磁盘的理解
存储结构

相关概念
拆开一个机械硬盘,你会看到里面有几个关键部件:
-
盘片(Platter):像光盘一样闪闪发光的圆盘。数据就刻在上面。
-
磁头(Head) :悬在盘片上方极近的地方,负责读取和写入数据。重点:每个盘面都有一个磁头,但所有的磁头都连在同一个机械臂上,同进同退!
-
主轴(Spindle):带着盘片飞速旋转的马达。
为了在这么大的盘片上定位数据,工程师发明了三个概念:
-
磁道(Track):把盘片想象成操场跑道,一圈一圈的同心圆就是磁道。
-
扇区(Sector) :把跑道切成一段一段的,每一小段就是一个扇区。这是磁盘存储的最小物理单位,通常是 512 字节。
-
柱面(Cylinder) :因为所有盘片的磁头是同进同退的,所以当你把磁头停在某个位置时,所有盘面上对应位置的磁道在空间上叠在一起,形成了一个圆柱体,这就是柱面。
CHS和LAB
CHS

如果让你去拿数据,你需要告诉机械臂:
-
去第几个柱面(C)?
-
用第几个磁头(H)?
-
读第几个扇区 (S)?
这就叫 CHS 寻址。但这种方法很反人类,因为你要同时管理三个数字,而且外圈磁道和内圈磁道能切的扇区数量还不一样(早期)。
⼀个细节:传动臂上的磁头是共进退的
LAB


操作系统是个"大忙人",它不想管你里面有几个盘片、磁头怎么动。它希望用最简单的方式管理存储。
所以,操作系统把整个磁盘**"拍扁"** ,看成一根长长的**"录音磁带"**。
-
磁带上密密麻麻排满了等大的格子。
-
每个格子就是一个扇区。
-
每个格子都有一个唯一的编号(从 0 开始),这个编号就叫 LBA(Logical Block Address,逻辑块地址)。
操作系统只需要说:"我要读第 1000 号扇区(LBA=1000)",然后把它交给磁盘控制器。
磁盘内部的固件(伺服系统)会自己用数学公式,瞬间把 LBA 1000 转换成对应的 CHS(柱面、磁头、扇区),然后机械臂精准地去拿数据。
公式转换
假设磁盘有 100 个柱面,4 个磁头,每磁道 8 个扇区。
想象你在操场上找自己的位置(LBA),你要先算前面有几个完整的小队。
-
C 算前面有几排 :每个柱面(排)有
4 × 8 = 32个扇区。LBA = C × 32 + H × 8 + (S - 1)(注:S 减 1 是因为物理扇区从 1 开始数,而 LBA 从 0 开始数)
-
反推 :
C = LBA ÷ 32(算排在前面有几排)H = (LBA 剩下的余数) ÷ 8(算这一排里,前面有几层)S = (LBA 剩下的余数) % 8 + 1(算这一层里,第几个座)
所以:OS 只认 LBA,磁盘硬件自己转换成 CHS。
文件系统
初步
"块"的概念
物理磁盘存取的最小单位是扇区(Sector),通常只有 512 字节。如果你有一本 10MB 文件,需要读 2 万次扇区。磁盘机械臂疯狂寻道,效率低。

-
块是操作系统读写磁盘的最小单位。
-
就像图书馆的"书箱",借书还书都以"箱"为单位。
-
这样一来,读取一个 10MB 的文件,只需搬运 2500 次(10MB / 4KB),效率直接提升 8 倍!
"分区"
假设你有一块 1TB 的硬盘,如果把它当成一个大整体来管理,所有的文件、系统备份、用户数据全混在一起。一旦系统崩溃,重装系统可能会把数据全毁掉;而且寻找文件时,面对 1TB 的空间,头都要炸了。
【引入分区】
就像一个大图书馆,如果只有一个大厅,找书太累了。
管理员决定用承重墙把大厅隔开,分成:科技区(C盘) 、历史区(D盘)。
-
分区,就是把一个大硬盘切成几个逻辑上独立的区域。
-
每个分区可以单独格式化、单独装系统、单独管理。
-
操作系统只需要分别管理这几个独立的小区域,复杂度大幅降低。
iNode
在分区里,数据已经被打散成一个个"块"了。假如你写了一个 100KB 的 C 语言代码文件 main.c,它会被切成 25 个 4KB 的块,散落在硬盘的各个角落。
操作系统怎么知道这 25 个块属于 main.c 呢?
引入 inode】
在图书馆里,为了找到一本书,你必须查**"图书检索卡"**。
-
这张卡片上写着:书名、作者、出版时间(文件的属性)。
-
最重要的是,卡片上写着:这本书分别放在了第几排、第几个书架的哪几个箱子里 (指向文件数据块的指针)。
这张"图书检索卡",在 Linux 中就叫 inode(索引节点)。
-
一个文件对应唯一的一个 inode。
-
inode 存储了文件的所有属性(大小、权限、创建时间等),但唯独没有文件名。
-
inode 内部有一个指针数组,指向了存储该文件内容的那些 数据块(Block)。
块组内部

Super Block(超级块)
cpp
struct ext2_super_block {
__le32 s_inodes_count; // 整个分区有多少个 inode
__le32 s_blocks_count; // 整个分区有多少个 4KB 块
__le32 s_free_inodes_count; // 还剩多少空闲 inode
__le32 s_free_blocks_count; // 还剩多少空闲数据块
__le32 s_log_block_size; // 块大小(比如 4KB)
// ...
};
详细解释:
如果你格式化了一个 300GB 的分区,Super Block 里就记着:"本分区总共有 7864 万个数据块,总共有 196 万个 inode,目前全都是空的。"
协作情况 :它只管全局宏观数据。如果有进程想创建一个文件,内核会先看一眼 Super Block,确认"嗯,还有空闲的 inode 和块",才允许创建。
格式化是在磁盘上写入管理信息。
格式化步骤
bash
1. 划分 Block Group
把 300GB 切成一块块的区域(比如每个区 128MB),划分为 Group 0, Group 1, Group 2...
2. 建立"总账本"(写入 Super Block)
在 Group 0 的开头,写入 Super Block。记录:"本分区总共有 7864 万个 4KB 块,有 196 万个 inode。块大小是 4KB。"等信息。
3. 建立"索引目录"(写入 GDT)
写入 Group Descriptor Table,告诉系统:Group 0 的位图在哪个块,Group 1 的位图在哪个块......
4. 清空"状态位图"(初始化 Bitmap)
把 Block Bitmap 和 Inode Bitmap 全部用 0 填满。
全是 0 代表什么?代表所有数据块都是空的,所有 inode 也都是空闲的。
5. 创建"根目录"
在 Inode Table 里分配 1 号 inode 给根目录 /,并在某个数据块里写入根目录的初始内容(通常是 . 和 ..)。
GDT(Group Descriptor Table)
cpp
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?
// ...
};
详细解释:
因为一个大分区被切成了很多个 Block Group,GDT 就是用来管理这些 Block Group 的。它相当于一个"目录",告诉你 Block Group 0 的位图在哪、Block Group 1 的位图在哪。
协作情况:当系统要在某个组找 inode 时,先查 GDT,瞬间定位到那个组的 Inode Table 起始位置。
Block Bitmap(块位图)
内部结构 :
它根本不是一个复杂的结构体,它就是一段连续的字节(byte) 。每一个**比特位(bit)**代表一个数据块(Block)。
-
0代表该数据块是空闲的。 -
1代表该数据块已经被占用了。
详细解释 :
假设本块组的 Block Bitmap 是 0000 0001(从右往左看),说明 0号到6号块是空的,7号块被占了。
协作情况 :当你要往文件里写数据(比如追加 4KB),文件系统扫描这串 0101 的位图,找到第一个为 0 的 bit,把它改成 1,就说明这个块被分配给你了。不仅分配快,还省空间(1个字节就能管8个块)。
Inode Bitmap(inode 位图)
内部结构 :
和块位图一模一样,也是一串连续的字节。每一个**比特位(bit)**代表一个 inode。
-
0代表这个 inode 号码还没人用。 -
1代表这个 inode 已经被分配出去了。
详细解释 :
Inode Table 是一个大数组(比如能容纳 1024 个 inode),Inode Bitmap 就是用来标记这 1024 个 inode 哪些被用掉了。
协作情况 :当你 touch abc 新建文件时,系统查 Inode Bitmap,找到第一个为 0 的 bit,把它标记为 1,并给你分配这个 inode 号(比如 263466)。
Inode Table(索引节点表)
cpp
struct ext2_inode {
__le16 i_mode; // 文件类型和权限 (比如 0644)
__le16 i_uid; // 所有者ID
__le32 i_size; // 文件大小(字节)
__le32 i_atime; // 访问时间
__le32 i_mtime; // 修改时间
__le32 i_links_count; // 硬链接数
__le32 i_block[15]; // 核心!记录文件数据存放在哪些 Block 里
// ...
};
详细解释 :
这就是我们前面反复提的inode 。在 4KB 的数据块里,一个 128 字节的 inode 可以塞下 32 个。所以一个块组里的 Inode Table 可能占据了成百上千个数据块,里面密密麻麻全是 inode。
协作情况 :一旦分配了 inode 号,系统就会在 Inode Table 里找到这个结构体,把文件大小、权限、以及真正的数据存在哪几个块 (i_block 数组),全部记进去。
Data Blocks(数据块)
内部结构 :
没有特定结构,就是一块 4KB 的空白内存。存什么完全取决于你的文件。
-
如果你存的是代码,这里面就是代码字符。
-
如果你存的是目录,这里面存的就是
{文件名, inode号}的列表(这就是前面说的"目录也是文件")。
协作情况 :Inode 里的 i_block[15] 记录着它们的编号。OS 拿着编号,直接去 Data Blocks 里读数据。
疑问
1、为什么super block单独放在Block group内部,而不是单独拿出来?
bash
机械硬盘是极其脆弱的物理设备。如果一块磁道坏了,或者磁盘表面刮花了,那块区域的数据就彻底没了。
如果只放一份:假设你把 Super Block 单独放在分区的开头。一旦硬盘开头那几个扇区出现了物理坏道(这在早期硬盘中很常见),整个分区就直接瘫痪了,操作系统连这个分区有多大、有多少空闲空间都不知道,根本无法挂载。
冗余备份:Ext2 的设计者规定,每一个 Block Group 的开头都要包含一份 Super Block 的拷贝。而且,这些拷贝是保持同步的。
如果 Group 0 的 Super Block 所在的物理磁道坏了,操作系统会尝试去 Group 1 读取备份的 Super Block。
只要有一份备份存活,你的文件系统就还能被抢救回来。
(注:现代文件系统如 ext4 做了优化,虽然每个组都有备份空间,但为了节省空间,通常只在少数几个组里真正存储 Super Block 的完整拷贝,这叫稀疏备份。)
2、inode的编号在文件系统里面是唯一的吗
是的,inode 的编号在整个文件系统(也就是一个独立的分区)内是唯一的。
"inode和数据块,跨组编号的。所以,在同一个分区内部,inode编号和块号都是唯一的!"
-
inode 表是分开的 :
Block Group 0有自己的一段Inode Table,Block Group 1也有自己的一段Inode Table。 -
编号是全局递增的:虽然它们分散在不同的组里,但文件系统给它们统一编了号。
-
比如:
Group 0的 inode 编号可能是1 ~ 1024。 -
Group 1的 inode 编号紧接其后,可能是1025 ~ 2048。
-
-
计算公式 :如果已知一个 inode 号,通过
(inode号 - 1) / 每组inode数,就能算出它在哪个块组;通过(inode号 - 1) % 每组inode数,就能算出它是这个组里的第几个 inode。
怎么知道 inode 在哪一个分组里?
假设你在 Super Block 里查到:每个块组(Block Group)固定包含 8192 个 inode 。
现在你手里拿着一个文件的 inode 编号:12345。
操作系统用脚趾头算一下就知道了:
分组号 = (inode 编号 - 1) ÷ 每组 inode 数
分组号 = (12345 - 1) ÷ 8192 = 12344 ÷ 8192 = 1(取整)
结论 :这个 inode 12345 存在 Block Group 1 里面。
(注:减 1 是因为 inode 编号是从 1 开始的,而数组下标是从 0 开始的)
问题二:怎么知道在分组中位图的哪一个位置?
既然知道了它在 Group 1,那它在 Group 1 的 Inode Bitmap 里,具体是第几个比特位(bit)呢?
接着用刚才的余数算:
组内偏移量 = (inode 编号 - 1) % 每组 inode 数
组内偏移量 = 12344 % 8192 = 4152
结论 :它的确切位置是 Group 1 的 Inode Bitmap 里的第 4152 个 bit。
进一步定位到具体结构体:
-
Inode Bitmap 里 :第 4152 个 bit 肯定被标记为了
1(表示已分配)。 -
Inode Table 里:系统直接在 Group 1 的 Inode Table 中,往后跳过 4151 个 inode 结构体(每个 128 字节),找到第 4152 个。这个结构体,就是你要找的文件的属性档案!