Ext文件系统详解

1.理解硬件

上一篇文章我们已经学习了被进程打开的文件是被加载到在内核的内存中中由OS进行以"先组织,后描述"的方法进行管理的,那么没有被加载到内存的文件是在哪里呢?由谁管理呢?不错,是在磁盘上,由Ext文件系统进行管理,而Ext文件系统本质上是被建立在磁盘分区上的一套管理数据的结构与规则,包含超级块、inode 表、数据块等,全部存储在磁盘上,用于组织、索引、存放文件。所以我们先从磁盘开始学习吧!

1.1磁盘

① 磁盘特点

机械磁盘是计算机中唯一的机械设备,磁盘也属于外设,运行速度满,但是容量大,价格便宜;

② 磁盘的物理结构

③磁盘的存储结构

④ 如何定位一个扇区

先定位磁头(也就是我们看到的哪一面,注意正方面都有磁面,也就都有磁头),现在三维空间变成二维了,然后我们再定位要访问哪一个磁道(柱面,不是横着的柱面,竖着的)一个面的哪一圈,最后定位是这一圈上的哪一段,也就是扇区;我们把这样的地址定位方法叫做CHS地址定位;

关于CHS寻址 对早期的磁盘⾮常有效,知道⽤哪个磁头,读取哪个柱⾯上的第⼏扇区就可以读到数据了。但是CHS模式⽀持的硬盘容量有限,因为系统⽤8bit来存储磁头地址,⽤10bit来存储柱⾯地址,⽤6bit来存储扇区地址,⽽⼀个扇区共有512Byte,这样使⽤CHS寻址⼀块硬盘最⼤容量为256 * 1024 * 63 * 512B = 8064 MB(1MB = 1048576B)(若按1MB=1000000B来算就8.4GB)

总结

扇区是磁盘读出和写入信息的最小物理单位,通常大小为 512 字节(现代磁盘也常见 4096 字节的高级格式化扇区)。

磁头(head)数每个盘片有上下两个盘面,每个盘面对应 1 个独立磁头,因此 1 个盘片对应 2 个磁头,多盘片则磁头数按盘面数累加。

磁道(track)数磁道是盘片上以主轴为中心的同心圆,从外圈向内圈依次编号为 0 磁道、1 磁道......;靠近主轴的最内侧同心圆为 "着陆区"(停靠区),仅用于磁头停靠,不存储数据。

柱面(cylinder)数所有盘片上同一编号的磁道垂直对齐形成柱面,柱面数量与单个盘片的磁道数量完全相等。

扇区(sector)数每个磁道被等分成若干扇形区域(扇区),同一磁盘中每道的扇区数量固定且相同。

盘片(platter)数即磁盘中物理盘片的总数量。

磁盘容量计算公式:磁盘容量 = 磁头数 × 柱面数 × 每道扇区数 × 每扇区字节数

核心细节:所有磁头固定在同一传动臂上,移动时同步进退(即所有磁头同时定位到对应盘片的同一编号磁道)。

⑤磁盘的逻辑结构

磁带上⾯可以存储数据,我们可以把磁带"拉直",形成线性结构

这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做 LBA

真实结构:

磁道

柱面

整体

整个磁盘不就是多张⼆维的扇区数组表(三维数组?)

所有,寻址⼀个扇区:先找到哪⼀个柱⾯(Cylinder) ,在确定柱⾯内哪⼀个磁道(其实就是磁头位置,Head),在确定扇区(Sector),所以就有了 CHS 。

现在我们再看在,其实全部都是⼀维数组

所以,每⼀个扇区都有⼀个下标,我们叫做 LBA(Logical Block Address) 地址,其实就是线性

地址。所以怎么计算得到这个LBA地址呢?OS只需要使用LBA就可以了!!LBA地址转成CHS地址,CHS如何转换成为LBA地址。谁做啊??磁盘自己来做!

⑥CHS 与 LBA 地址转换核心总结

CHS(柱面 - 磁头 - 扇区)是早期磁盘的物理寻址方式,LBA(逻辑块寻址)是将磁盘抽象为一维扇区数组的逻辑寻址方式,二者可互相转换,且现代系统仅使用 LBA 寻址,磁盘内部自动完成与 CHS 的转换:

扇区号(S):CHS 中从 1 开始编号,LBA 中从 0 开始编号;

柱面号(C)、磁头号(H):均从 0 开始编号磁盘会自动维护总柱面数、磁头数、每磁道扇区数等参数,系统开机时可读取。

CHS 转 LBA(物理地址→逻辑地址)

先计算单个柱面的扇区总数:单个柱面扇区数 = 磁头数 × 每磁道扇区数;

转换公式:LBA = 柱面号 C × 单个柱面扇区数 + 磁头号 H × 每磁道扇区数 + 扇区号 S - 1(减 1 是因为 CHS 扇区从 1 开始,LBA 从 0 开始,完成编号对齐)。

LBA 转 CHS(逻辑地址→物理地址)

柱面号 C = LBA // (磁头数 × 每磁道扇区数) (// 表示整除取整);

磁头号 H = (LBA % (磁头数 × 每磁道扇区数)) // 每磁道扇区数;

扇区号 S = (LBA % 每磁道扇区数) + 1 (加 1 还原 CHS 扇区从 1 开始的编号规则)。

现代操作系统将磁盘抽象为 "以扇区为元素的一维数组",数组下标即为扇区的 LBA 地址,只需通过一个数字即可访问对应扇区;CHS 仅作为磁盘底层的物理寻址方式,由磁盘硬件自行完成与 LBA 的转换,使用者无需关注。

2.ExT文件系统

一块磁盘 → 分成分区 → 每个分区放 Ext4 文件系统

2.1什么是块

硬盘是典型的块设备 。操作系统读取硬盘数据时,不会以单个扇区为单位读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个块(Block) 。硬盘的每个分区都会被划分为一个个块,块的大小在格式化时确定,之后不能更改,最常见的大小是 4KB ,也就是由连续的8 个 512 字节的扇区组成一个块。块是文件系统存取数据的最小单位

2.2什么是分区

磁盘可以被划分成多个分区(partition) 。以 Windows 为例,一块磁盘可以分成 C、D、E 盘,C、D、E 就是分区 。分区并不是格式化,而是在磁盘上划分出一段连续的物理范围;格式化是在分好的分区上建立文件系统。Linux 下所有设备都以文件形式存在,分区同样对应设备文件(如 /dev/sda1、/dev/sda2)。

柱面是传统磁盘分区的最小单位,分区的本质就是记录每个分区的起始柱面号结束柱面号,以此圈定一段独立的物理空间。我们可以把磁盘上所有柱面按顺序平铺展开,看作一个线性的地址空间,分区就是在这个线性空间里划定一段连续的范围。

2.3什么是inode

文件由 数据 + 属性(元信息)组成**,** 使用 ls -l 时,除文件名外看到的都是文件元数据。ls -l 会读取磁盘上的文件信息并显示出来。文件数据存储在块(block)中,而存储文件元信息(如文件创建者、时间、大小、权限等)的区域称为 inode(索引节点)。每个文件都对应一个 inode,里面存放该文件的相关属性。

由此可知:Linux 中文件的属性与内容是分开存储的。存放文件属性的集合称为 inode,一个文件对应一个 inode,每个 inode 有唯一的 inode 号文件名并不存储在 inode 结构中,inode 大小通常固定为 128 字节或 256 字节,后续统一按 128 字节理解。不同文件的数据长度可以不同,但属性(inode)的大小是固定的

3.正式理解Ext文件系统

所有准备工作完成后,我们来认识文件系统。想要在硬盘上存储文件,必须先将硬盘格式化为某种文件系统 ,才能正常存放和管理文件,文件系统的作用就是组织和管理硬盘中的文件。在 Linux 中最常见的是 Ext 系列文件系统,早期为 Ext2,后续发展出 Ext3 和 Ext4;Ext3、Ext4 在 Ext2 基础上做了功能增强,但核心设计基本保持不变。

Ext2 文件系统会把整个分区 划分为若干个大小相同的 块组(Block Group)。只要理解了一个块组的管理方式,就能理解整个分区的管理逻辑,进而实现对磁盘上所有文件的组织与管理。

每一块在一个磁道上,整个分区是柱面的一部分;

3.1每一块的作用

①启动块

启动块(Boot Block/Sector)的大小是确定的,为1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是ext2文件系统的开始,和文件系统管理无关。

②超级块

SuperBlock整个文件系统的 "总说明书":多大、有多少 inode、有多少数据块、块大小是多少;超级块(Super Block)用于存放文件系统自身的结构信息,描述整个分区的文件系统状况。它记录的主要信息包括:block 与 inode 的总量、未使用的 block 和 inode 数量、block 和 inode 的大小、最近一次挂载时间、最近一次数据写入时间、最近一次磁盘检验时间等其他文件系统相关信息。超级块的信息一旦被破坏,整个文件系统结构就会损坏。

超级块在每个块组的开头都可能存在一份拷贝:第一个块组必须包含超级块,后面的块组可以选择性备份。为了保证磁盘部分扇区出现物理故障时,文件系统仍能正常工作,必须确保超级块信息依然可以访问。因此,一个文件系统的超级块会在多个块组中进行备份,且所有备份的超级块数据保持一致

③组描述符

管理每个块组的信息,相当于 "片区管理员"。块组描述符表(Group Descriptor Table)用于描述块组 的属性信息。整个分区划分为多少个块组,就对应有多少个块组描述符。每个块组描述符存储一个块组的管理信息,例如:该块组中 inode 表从哪里开始、数据块从哪里开始、空闲的 inode 和数据块还有多少个等。为了保证文件系统的可靠性,块组描述符也会像超级块一样,在每个块组的开头都保存一份拷贝

④块位图

& inode 位图

⑤块位图

记录哪个数据块是空的、哪个被用了,每一个bit表示一个inode是否空闲可用;

⑥inode 位图

记录哪个 inode 是空的、哪个被用了,每一个bit表示一个ionde是够空闲可用;

⑦inode 表(核心!)

存放文件属性,如文件大小、文件所有者、最近修改时间、权限等。每个块组中,当前组所有 inode 的集合,称为 inode 表(inode Table)。inode 编号以分区为单位统一分配,不能跨分区使用。

⑧ 数据块

真正存文件内容的地方。也就是一个一个的block,对于普通文件,文件的数据存储在数据块中,对于目录,该目录下的所有文件存储在所在目录的数据块中,除了文件名外;

分区后的格式化操作,本质是对分区进行块组划分,并在每个块组中写入超级块(SB)、块组描述符表(GDT)、块位图(Block Bitmap)、inode 位图(Inode Bitmap)等管理信息,这些管理信息的集合,就构成了文件系统。只要知道文件的 inode 号,就能在指定分区中确定它所属的块组,进而在该块组中定位到对应的 inode。找到 inode 后,就能获取到文件的全部属性,同时通过 inode 中记录的信息,找到文件数据所在的数据块,从而获取文件的完整内容。

3.2 inode和datablock文件映射

3.3 思考(重点)

请解释:知道inode号的情况下,在指定分区,请解释:对⽂件进⾏增、删、查、改是在
做什么?

①查(读取文件)

根据 inode 号,找到对应的 inode 结构。从 inode 中读出:文件属性(权限、大小、时间等)

文件数据所在的 数据块编号,把对应的数据块把内容读出来。

一句话:查 = 用 inode 找到数据块,读取内容 + 属性。
②改(修改文件内容)

通过 inode 号定位到 inode。根据 inode 里的指针找到原有数据块。把新内容写入这些数据块(覆盖或追加)。如果文件大小变了,更新 inode 里的文件大小。

一句话:改 = 找到数据块改写内容,并更新 inode。
③增(给文件追加内容 / 增加数据)

定位到文件的 inode。看当前数据块是否够用:够用 → 直接写。不够用 → 通过块位图申请新的空闲数据块。把新块编号记入 inode。更新 inode 中的文件大小。

一句话:增 = 申请新数据块,写入数据,更新 inode。
④删(删除文件)

删除文件根本不删数据!只做 3 件事:根据 inode 号找到 inode。把 inode 对应的 inode 位图 标记为未使用。把文件占用的所有 数据块 通过块位图标记为空闲。数据本身还在磁盘上,只是被标记为 "可覆盖"。

一句话:删 = 释放 inode + 释放数据块,标记为可用

3.4 在实际中分析

通过touch 创建一个新文件,看磁盘文件的具体操作情况;

bash 复制代码
[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc

①存储属性

内核先找到一个空闲的 inode(索引节点,此处为 263466),并将文件的属性信息(如大小、所有者、创建时间等)记录到该 inode 中。
②存储数据

该文件需要占用三个磁盘块来存储内容,内核从磁盘中找到三个空闲的数据块(编号为 300、500、800),随后将内核缓冲区中的数据按顺序复制到这些块中:第一部分数据写入 300 块,第二部分写入 500 块,第三部分写入 800 块。
③记录分配情况

文件内容按 300→500→800 的顺序存放,内核会在该文件对应的 inode 中,专门记录数据块分布的区域(指针区)记录这三个块的编号列表,以此关联文件属性与数据存储位置。
④添加文件名到目录

新建文件名为 abc,Linux 会在当前目录中完成如下记录:内核将映射关系(inode 号 263466,文件名 abc)作为一条条目添加到目录文件的数据块中。文件名与 inode 号的对应关系,是连接文件名、文件内容和文件属性的核心纽带。

注意:目录也是普通文件,存储在数据块的块中;目录的内容中保存的是文件名和inode的映射关系;所以访问文件必须打开当前目录,根据文件名获得相应的inode号,然后进行文件的访问,所以访问文件必须知道当前的工作目录,本质是必须打开当前工作目录的文件,查看目录文件的内容;

4.路径解析

我们先理清核心问题:当前工作目录本质是目录文件,访问它看似只知道文件名,实则需要先找到它的 inode;但要获取当前工作目录的 inode,又得先访问它的上级目录 ------ 这就陷入了 "先有鸡还是先有蛋" 的循环。

①表层疑问:要访问当前工作目录(目录文件),确实需要先打开它的上级目录,再通过 "上级目录文件中的「当前目录名→inode 号」映射关系" 找到当前目录的 inode;但上级目录本身也是目录文件,同样需要解析它的上级目录。

②核心逻辑 :这个过程类似 "递归解析",而递归的 "出口" 是根目录「/」 ------ 根目录的 inode 号是 Linux 内核固定约定的(通常为 2),无需通过其他目录解析,是路径解析的起点。

③最终结论(Linux 路径解析):

访问任何文件 / 目录(比如 /home/whb/code/test/test/test.c),系统都会从根目录「/」开始,按路径层级依次解析每一个目录?

①打开根目录「/」(已知 inode=2),找到其中「home→inode 号」的映射,获取 home 目录的 inode;

②打开 home 目录,找到其中「whb→inode 号」的映射,获取 whb 目录的 inode;

③依次解析 code、test(第一层)、test(第二层)目录;最后:在最终的 test 目录中,找到··test.c→inode 号」的映射,完成文件定位。整个逐层解析目录、从根到目标的过程,就是 Linux 的路径解析。

可是路径是谁提供的呢?

我们访问文件时,看似是手动输入指令 / 使用工具操作,本质是进程在访问文件:每个进程都有自己的当前工作目录(CWD),进程会基于 CWD 或用户提供的路径来定位文件(比如调用open函数时传入的路径)。但核心疑问是:最初的路径从何而来? 这需要从 Linux 文件系统的设计逻辑和使用逻辑两层解答:

①根目录的核心意义 :Linux 必须有根目录「/」------ 它是所有路径的起点(inode 号固定为 2),没有根目录就无法完成路径解析的 "递归出口",所有文件 / 目录都失去了定位的基础;而根目录下的默认目录(如/bin/etc/home/usr等)是系统预设的标准化结构,目的是按功能分类存储文件(比如/bin存系统指令、/etc存配置、/home存用户数据),让系统和用户能统一、规范地管理文件。

②家目录与自定义目录的意义 :每个用户都有专属家目录(如/home/whb),是系统为用户分配的默认操作空间;用户也可以自行新建目录 ------ 这些行为的本质,都是在磁盘的文件系统中新建目录文件 。而我们新建的任何文件,都会被创建在系统或用户指定的目录下,这就天然赋予了文件 "可被解析的路径"(比如/home/whb/test.txt)。

综上,Linux 的路径结构并非凭空存在:由系统预设根目录和标准化默认目录作为基础框架,再由用户根据需求新建目录 / 文件填充,最终形成完整、可解析的路径体系

5.路径缓存(struct dentry)

问题 1:Linux 磁盘中,存在真正的 "目录" 吗?

答案:不存在。磁盘中只有 "文件" 这一种存储形态 ------ 无论是普通文件还是我们认知中的 "目录",本质都是文件,仅存储两部分内容:文件属性(inode)+ 文件内容(数据块);所谓 "目录",只是内容为「文件名→inode 号」映射表的特殊文件。

问题 2:访问任何文件,都要从「/」目录开始进行路径解析吗?

答案:原则上是 (根目录是路径解析的唯一起点),但纯磁盘级的逐层解析效率极低。因此 Linux 会在内存中缓存历史路径结构,优先从缓存查找,大幅提升访问速度。

问题 3:Linux "目录" 的概念,是怎么产生的?

答案:磁盘中仅存在 "目录文件",而我们感知到的 "目录树""路径层级" 等 "目录" 概念,是操作系统在内存中构建的逻辑结构当打开的文件是目录文件时,Linux 内核会在内存中通过dentry 结构体(目录项结构体,struct dentry) 维护路径关系。

  • 所有被打开的文件(包括普通文件、目录文件)都有对应的 dentry 结构,这些 dentry 节点在内存中串联成树形路径结构,这就是我们感知到的 "目录树";
  • 该树形节点同时隶属于 LRU(Least Recently Used,最近最少使用) 淘汰机制:长时间未访问的 dentry 节点会被释放,节省内存;
  • 节点还被纳入哈希表(Hash):通过路径快速哈希查找,避免逐层遍历,提升查找效率;
  • 核心作用:这棵内存中的 dentry 树构成了 Linux 的路径缓存------ 访问文件时,先在缓存中按路径查找 dentry 节点:
  • ✅ 找到:直接通过 dentry 获取 inode(属性)和数据块(内容),无需访问磁盘;
  • ❌ 没找到:从磁盘加载路径、创建对应的 dentry 节点并加入缓存,再返回文件信息。

6.挂载分区

我们已经掌握:在指定分区内,可通过 inode 号找到文件,也能通过目录文件的「文件名→inode 号」映射定位 inode,看似能自由操作文件,但核心疑问随之而来:inode 号仅在所属分区内唯一,无法跨分区识别;Linux 系统支持多个分区(如/dev/sda1、/dev/sda2),如何判断目标文件所在的分区?

先补充关键背景:/dev/loop0是 Linux 的第一个循环设备(loop device,也叫回环 /loopback 设备),属于伪设备(pseudo-device)。它的核心作用是允许将普通文件(如 ISO 镜像)当作块设备使用,可像物理硬盘分区一样挂载为文件系统。

而解决 "跨分区识别" 问题的核心机制是挂载(mount):磁盘分区被写入文件系统后,无法直接访问,必须将分区与系统中一个指定的目录(挂载点)建立关联(即 "挂载"),才能通过该目录访问分区内的文件。由此得出核心结论:通过目标文件的「路径前缀」,就能准确判断其所在的分区(理解到这一层即可)。比如:
若文件路径是/home/test.txt,且/home是/dev/sda2的挂载点,则该文件在/dev/sda2分区;
若文件路径是/mnt/iso/file.iso,且/mnt/iso是/dev/loop0的挂载点,则该文件在/dev/loop0对应的循环设备(文件)分区中。

总结

  • inode 号仅在分区内唯一,跨分区无意义,需通过 "挂载" 解决跨分区访问问题;
  • 分区需挂载到指定目录(挂载点)才能使用,目标文件的路径前缀对应其挂载点,由此可判断所在分区;
  • /dev/loop0是循环伪设备,可将文件当作块设备挂载,本质仍遵循 "路径前缀→分区" 的判断逻辑。

7.软硬连接

7.1软链接

①本质

软连接 = 快捷方式,自己是独立的小文件,有自己的 inode。内容只有一行:指向原来的路径

源文件删了,软连接就失效(变红),可以跨分区、可以给目录创建;

②特点

相当于 Windows 快捷方式

可以跨分区

可以链接目录

源文件没了,软连接就断了

③实现

bash 复制代码
ln -s 源文件 软连接文件

7.2硬链接

①本质

硬链接 = 同一个 inode,多个文件名;文件真正的身份是 inode 号;所有硬链接,共用同一个 inode;删除其中一个文件名,文件本体还在;只有当最后一个文件名被删掉,文件才真正删除

② 特点

不能跨分区

不能给目录创建硬链接(系统限制)

不占用额外 inode

删哪个名字都不影响文件本体

③用途

A.防止误删文件(最核心)

你给文件建几个硬链接,删掉任何一个文件名,文件本体都不会丢。

只有最后一个名字被删,文件才真正删除。

→ 适合重要数据、备份、不想被误删的文件。
B.同一个文件,在多个地方能用

比如:

/a/file.txt

你在 /b/ 也想用它

建硬链接:/b/file.txt

两个路径访问的是完全同一个文件,不占双倍空间。
C. 节省磁盘空间

硬链接不复制数据,只多一个目录项。→ 大文件想在多处使用,又不想复制,用硬链接。

④实现

cpp 复制代码
ln 源文件 硬链接文件

8.pid和inode的区别

① inode(索引节点)

管的是:磁盘上的文件

在哪:磁盘、文件系统里

作用:标识一个文件(属性 + 数据位置)

谁用:文件系统、open、read、write

一句话:inode = 文件的身份证号(磁盘层面)

②PID(进程 ID)

管的是:内存里跑的进程

在哪:内存、操作系统内核里

作用:标识一个正在运行的程序

谁用:ps、kill、fork、调度

一句话:PID = 进程的身份证号(内存 / 运行层面)
③ 最直观对比(一秒分清)

你创建一个 test.txt → 系统给它分配 inode

你运行一个 ./a.out → 系统给它分配 PID

一个管硬盘文件,一个管运行程序,完全不搭边。

相关推荐
圥忈&&丅佽&&扗虖2 小时前
linux 安装 Ollama
linux·服务器
kabu_Charlie2 小时前
使用Docker运行python程序
运维·docker·容器
cyber_两只龙宝2 小时前
【Keepalived】抢占模式、延迟抢占模式与非抢占模式详解
linux·运维·服务器·keepalived
Flash.kkl2 小时前
TCP套接字
服务器·网络·tcp/ip
REDcker2 小时前
CentOS 与主流 Linux 发行版历史与版本综述
linux·centos·numpy
劳埃德福杰2 小时前
【Kylin银河麒麟】文件系统磁盘空间满导致无法进入操作系统
运维·服务器·电脑·笔记本电脑·kylin
逻辑峰2 小时前
ReadStat在Linux的安装和使用
linux·运维·服务器
Lsir10110_3 小时前
【Linux】序列化与反序列化——网络计算器的实现
linux·运维·网络
脆皮的饭桶3 小时前
给负载均衡做高可用的工具Keepalived
运维·服务器·负载均衡