深入理解 Ext 系列文件系统:从磁盘物理到文件系统原理
一、开篇:走进文件系统的世界
在计算机的存储体系里,文件系统扮演着 “大管家” 的角色,精心打理着磁盘上的数据,让我们能高效地存储、访问和管理文件。Ext 系列文件系统作为 Linux 系统中常用的文件系统,背后有着一套精妙的设计逻辑。接下来,我们就从磁盘物理结构讲起,一步步揭开它的神秘面纱,理解分区、格式化、路径解析、挂载以及软硬链接等关键概念。
二、理解硬件基础:磁盘的奥秘
(一)磁盘的 “身份” 与特性
机械磁盘是计算机中唯一的机械设备,属于外设。它虽然 “慢”,但凭借大容量、价格便宜的优势,成为数据长期存储的重要载体。从机房角度看,它是服务器、机柜组成的复杂环境中,负责数据持久化的关键部件;从原理上讲,磁盘和磁铁也有着奇妙联系,涉及磁记录等技术 。
(二)磁盘物理结构:精准定位数据的基础
磁盘由盘片、磁头、传动臂等组成。
要定位一个扇区(磁盘存储数据的基本单位,512 字节,属于块设备 ),需经过三步:先定位磁头(header),确定要访问的柱面(磁道,cylinder ),再定位扇区(sector),这就是 CHS 地址定位方式。
CHS寻址:
对早期的磁盘非常有效,知道用哪个磁头,读取哪个柱面上的第几扇区就可以读到数据了。 但是CHS模式支持的硬盘容量有限,因为系统用8bit来存储磁头地址,用10bit来存储柱面地 址,用6bit来存储扇区地址,而⼀个扇区共有512Byte,这样使用CHS寻址⼀块硬盘最大容量 为
256*1024*63*512B=8064MB(1MB=1048576B)
(若按1MB=1000000B来算就是 8.4GB)
- 磁头(head):每个盘片一般有上下两面,对应 2 个磁头,负责读写盘片上的数据。
- 磁道(track):从盘片外圈往内圈编号,靠近主轴的同心圆用于停靠磁头,不存储数据。
- 柱面(cylinder):磁道构成柱面,数量等同于磁道个数,是磁头移动到相同半径位置时所经过的磁道集合 。
- 扇区(sector):每个磁道被切分成多个扇形区域,每道扇区数量相同,是磁盘读写的最小物理单位。
- 磁盘容量计算:磁盘容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数 。
- 磁头运动特点:传动臂上的磁头是共进退的,这对理解磁盘寻址和数据存储布局很关键。
- 圆盘(platter)数:就是盘片的数量。
下面这张图可以帮你更直观地理解磁盘物理结构:
(三)磁盘逻辑结构:从三维到一维的抽象
磁盘物理结构复杂,但逻辑上可抽象。我们可以把磁盘想象成卷起来的磁带,将其逻辑存储结构线性化,每个扇区就有了线性地址,即 LBA(Logical Block Address)。
-
CHS 与 LBA 的转换
:CHS 寻址对早期磁盘有效,但支持容量有限(因系统存储地址的位数限制,最大容量有限 )。LBA 是线性地址,磁盘内部固件(硬件电路、伺服系统 )负责 CHS 和 LBA 之间的转换。
-
CHS 转 LBA:
LBA = 柱面号 C ×(磁头数 × 每磁道扇区数) + 磁头号 H × 每磁道扇区数 + 扇区号 S - 1(扇区号从 1 开始,LBA 从 0 开始,柱面和磁道从 0 编号 )。
-
LBA 转 CHS:
柱面号 C = LBA //(磁头数 × 每磁道扇区数)[就是单个柱面的扇区总数];
磁头号 H = (LBA %(磁头数 × 每磁道扇区数)) // 每磁道扇区数;
扇区号 S = (LBA % 每磁道扇区数) + 1 。
“//”:表⽰除取整。
这样,对磁盘使用者来说,只需关注 LBA 地址,磁盘内部自动处理转换,磁盘就像一个元素为扇区的一维数组,下标是 LBA 地址。
-
这里用一张图展示磁盘逻辑结构的抽象过程:
三、引入文件系统:组织磁盘数据的关键
(一)“块” 概念:提升读写效率
硬盘是典型 “块” 设备,操作系统读取硬盘数据时,不会逐个扇区读取(效率低 ),而是一次性读取多个扇区,即一个 “块”(block)。
硬盘分区被划分为一个个块,大小由格式化的时候确定的(常见 4KB,由连续八个扇区组成 ),并且不可以更改,块是文件存取的最小单位。
注意:
- 磁盘就是一个三维数组,我们把它看待成为⼀个"一维数组",数组下标就是LBA,每个元素都是扇区 ;
- 每个扇区都有LBA,那么8个扇区一个块,每一个块的地址我们也能算出来。
- 知道 LBA 地址,块号 = LBA / 8;知道块号,LBA = 块号 × 8 + n(n 是块内扇区序号 )。
看看这张块与扇区关系的图,理解更轻松:
(二)“分区” 概念:磁盘空间的划分
磁盘可分成多个分区,如 Windows 的 C、D、E 盘,Linux 中设备以文件形式存在,分区本质是对硬盘格式化。
柱面是分区最小单位,通过设置每个区起始和结束柱面号码分区,知道分区起始、结束柱面号及每柱面扇区数,就能确定分区大小和对应 LBA 范围 。
分区划分的示意图如下:
(三)“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列:
- 模式
- 硬链接数
- 文件所有者
- 组
- 大小
- 最后修改时间
- 文件名
ls -l
读取存储在磁盘上的文件信息,然后显示出来
其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息
[root@localhost linux]# stat test.cFile: "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
到这我们要思考一个问题,文件数据都储存在”块”中,那么很显然,我们还必须找到一个地方储存 文件的元信息(属性信息),比如文件的创建者、文件的创建日期、文件的大小等等。
这种储存文件元信息(属性)的区域就叫做inode,中文译名为”索引节点”。
$ ls -li //列出当前目录下文件和目录的详细信息,其中第一列为inode编号
每一个文件都有对应的inode,里面包含了与该文件有关的⼀些信息。为了能解释清楚inode,我们需 要是深入了解一下文件系统。
注意:
- Linux下文件的存储是属性和内容分离存储的
- Linux下,保存文件属性的集合叫做inode,⼀个文件,一个inode,inode内有一个唯一的标识符,叫做inode号
- 文件名属性并未纳入到inode数据结构内部
- inode 大小一般 128 或 256 字节,后面统一128字节
- 任何文件的内容大小可以不同,但是属性大小⼀定是相同的
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,又是如何放置的呢? 文件系统就是为了组织管理这些的!!
四、Ext2 文件系统:深度剖析
(一)宏观认识:文件系统的 “骨架”
我们想要在硬盘上储文件,必须先把硬盘格式化为某种格式的文件系统,才能存储文件。文件系统的目的就是组织和管理硬盘中的文件。
在 Linux系统中,最常见的是ext2系列的文件系统。其早期版本为ext2,后来又发展出ext3和ext4。 ext3和ext4虽然对ext2进行了增强,但是其核心设计并没有发生变化,我们仍是以较老的ext2作为演示对象。
ext2文件系统将整个分区划分成若干个同样大小的块组(BlockGroup),如下图所示。只要能管理⼀个分区就能管理所有分区,也就能管理所有磁盘文件。
Ext2 文件系统宏观结构的图:
上图中启动块(BootBlock/Sector)的大小是确定的,为1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是ext2文件系统的开始。
(二)块组(Block Group):文件系统的 “管理单元”
Ext2 根据分区大小划分多个块组,每个块组结构相同,如同政府管理各区,方便统一管理和维护。
(三)块组内部构成:各司其职的组件
-
超级块(SuperBlock):小区总台账
存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:bolck和inode的 总量,未使用的block和inode的数量,⼀个block和inode的大小,最近⼀次挂载的时间,最近⼀次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。SuperBlock的信息被破坏,可 以说整个文件系统结构就被破坏了。超级块在每个块组的开头都有一份拷贝(第一个块组必须有,后⾯的块组可以没有)。为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的superblock信 息在这种情况下也能正常访问。所以一个文件系统的superblock会在多个blockgroup中进行备份, 这些superblock区域的数据保持一致。
-
GDT(Group Descriptor Table,块组描述符表 ):楼栋说明书
每个块组有个描述符,记录这个块组里 inode 表在哪、数据块在哪、还剩多少空闲块 /inode 等,在每个块组开头备份 。 -
块位图(Block Bitmap):房间使用图
BlockBitmap中记录着DataBlock中哪个数据块已经被占用,哪个数据块没有被占用。 -
inode 位图(Inode Bitmap):身份证使用图
类似块位图,每个 bit 代表一个 inode 是否在用(0 = 空闲,1 = 已用)。 -
i 节点表(Inode Table):住户身份证集合
- 存放文件属性如文件大小,所有者,最近修改时间等
- 当前分组所有Inode属性的集合
- inode编号以分区为单位,整体划分,不可跨分区
-
Data Block(数据区 ):实际存东西的房间
数据区:存放文件内容,也就是一个一个的Block。根据不同的文件类型有以下几种情况:
- 对于普通文件,文件的数据存储在数据块中。
- 对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文件名外,ls-l命令 看到的其它信息保存在该文件的inode中。
- Block号按照分区划分,不可跨分区
(四)inode 和数据块映射:关联文件属性与内容
inode 内部的 __le32 i_block [EXT2_N_BLOCKS](EXT2_N_BLOCKS = 15 )用于 inode 和 block 映射,这样文件的内容和属性就能通过 inode 关联起来,找到 inode 号,就能确定文件属性和内容存储位置 。
思考:
请解释:知道inode号的情况下,在指定分区,请解释:对文件进行增、删、查、改是在做什么?
结论:
- 分区之后的格式化操作,就是对分区进行分组,在每个分组中写入SB、GDT、Block Bitmap、InodeBitmap等管理信息,这些管理信息统称:文件系统;
- 只要知道文件的inode号,就能在指定分区中确定是哪⼀个分组,进而在哪一个分组确定是哪⼀个inode
- 拿到inode文件属性和内容就全部都有了
已知 inode 号和指定分区时,文件的增删查改对应这些操作:
- 增(新建/追加):在分区里找空闲块存新内容,分配新 inode 记录属性(大小、权限等),把新 inode 号和块关联,更新管理信息(标记块和 inode 为已用 ),就像给新文件办“身份+存内容”。
- 删:把文件对应 inode 标记为空闲(让系统知道这 inode 能复用 ),数据块也标记成可覆盖,类似“注销文件身份,腾空位置”(实际内容没立刻删,只是系统不管了 )。
- 查:用 inode 号定位到分组,找到 inode 后,读它存的属性(比如多大、谁创建的 ),再根据 inode 里的指针找数据块,取出文件内容,就是“凭编号查档案,读信息+内容”。
- 改:改属性(如权限 )就直接改 inode 里的属性记录;改内容的话,小改直接动对应数据块,大改可能要调整数据块分配(删旧块、加新块 ),最后更新 inode 里的指针,像“改档案信息,或给档案换/增存内容的盒子”。
核心就是:inode 是文件的“身份核心”,增删查改全围绕它和关联的数据块折腾,结论里的底层原理(分区管理、inode 定位逻辑 ),就是这些操作能跑通的“规矩”~
下面,通过touch⼀个新文件来看看如何工作:
[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc
为了说明问题,我们将上图简化:
创建⼀个新文件主要有以下4个操作:
1.存储属性:内核先找到⼀个空闲的i节点(这里是263466)。内核把文件信息记录到其中。
2.存储数据:该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。
3.记录分配情况:文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
- 添加文件名到目录:新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
(五)目录与文件名:文件访问的 “桥梁”
目录也是文件,其内容保存文件名和 inode 号的映射关系。访问文件时,我们用文件名,系统实际先打开当前目录文件,根据文件名找到对应 inode 号,进而访问文件。比如访问 test.c 文件,需打开当前工作目录文件,获取 test.c 对应的 inode 才能操作 。
(六)路径解析:从根目录开始的 “探索”
访问文件要从根目录开始路径解析,依次打开路径中的每个目录,根据目录名找到下一级目录或目标文件 inode 。任何文件路径访问,都需解析路径中所有目录,出口是根目录。进程访问文件时提供路径,系统开机后需知道根目录固定文件名和 inode 号,系统和用户共同构建 Linux 路径结构。
(七)路径缓存:提升访问效率的 “技巧”
问题1:Linux磁盘中,存在真正的目录吗?
答案:不存在,只有文件。只保存文件属性+文件内容 。
问题2:访问任何文件,都要从/目录开始进行路径解析?
答案:原则上是,但是这样太慢,所以Linux会缓存历史路径结构 。
问题2:Linux目录的概念,怎么产生的?
答案:打开的文件是目录的话,由OS自己在内存中进行路径维护。
Linux中,在内核中维护树状路径结构的内核结构体叫做: struct dentry 。
注意:
- 每个文件其实都要有对应的dentry结构,包括普通文件。这样所有被打开的文件,就可以在内存中形成整个树形结构 ;
- 整个树形节点也同时会隶属于LRU(LeastRecentlyUsed,最近最少使用)结构中,进行节点淘汰;
- 整个树形节点也同时会隶属于Hash,方便快速查找 ;
- 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都在先在这棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry 结构,缓存新路径。
(八)挂载分区:让分区 “可用” 的操作
分区写入文件系统后,需挂载到指定目录才能使用 —— 就像给仓库的小房间装个门,让我们能进出。
五、软硬连接:文件的 “分身术”
(一)硬链接:复制一把钥匙
创建硬链接 ln abc.txt def.txt
后:
def.txt
和abc.txt
共用一个 inode(同一个 “身份证”)。- 删
abc.txt
,def.txt
还能用(只要硬链接数不为 0,文件就不删)。 - 用途:备份重要文件(改一个,另一个也变,因为是同一个文件)。
我们看到,真正找到磁盘上文件的并不是文件名,而是inode。其实在linux中可以让多个文件名对应于同⼀个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的硬连接数为2。
- 我们在删除⽂件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则 将对应的磁盘释放。
(二)软链接:写一张地址条
创建软链接 ln -s abc.txt abc.s
后:
abc.s
是个新文件,有自己的 inode,内容是 “abc.txt” 的路径。- 删
abc.txt
,abc.s
就失效了(地址条指向的地方没东西了)。 - 用途:当 “快捷方式”,比如把深目录里的文件链接到桌面。
硬链接是通过inode引用另外⼀个文件,软链接是通过名字引用另外一个文件,但实际上,新的文件和 被引用的文件的inode不同,应用常见上可以想象成⼀个快捷方式。
[root@localhost linux]# ln -s abc.txt abc.s
[root@localhost linux]# ls -li
263563 -rw-r--r--. 2 root root 0 9⽉ 15 17:45 abc.s
261678 lrwxrwxrwx. 1 root root 3 9⽉ 15 17:53 abc.txt -> abc.s
软硬连接对比
- 软连接是独立文件 。
- 硬链接只是文件名和目标文件inode的映射关系 。
软硬连接的用途:
硬链接 : .和… 就是硬链接;文件备份 。
软连接:类似快捷方式。
六、总结:Ext 文件系统的全貌
从磁盘物理结构的 CHS、LBA 寻址,到文件系统引入的块、分区、inode 概念,再到 Ext2 文件系统的块组、超级块、inode 与数据块映射,以及目录、路径解析、挂载、软硬链接,这些知识共同构成了 Ext 系列文件系统的完整体系。理解这些,我们就能明白文件在 Linux 系统中是如何被高效组织、存储和访问的,为深入学习 Linux 存储和文件管理打下坚实基础 。