当前位置: 首页 > news >正文

10【认识文件系统】

1 认识硬件——磁盘

1.1 物理构成

  磁盘是计算机中唯一的机械设备,同时也是一种外部存储设备(外设)。早期的计算机通常配备的是机械硬盘(HDD),依靠磁头和盘片的机械运动来进行数据的读写。但随着用户对计算机速度要求的不断提高,如今大多数个人电脑已普遍采用SSD(固态硬盘)。

  相比机械硬盘,SSD没有机械运动部件,具有读写速度快、体积小、低功耗、耐用性好、无噪音等优点,且仍有广阔的技术发展空间。因此,在桌面电脑领域,SSD几乎已经全面取代了机械硬盘。

  但在企业级存储领域,机械硬盘仍被大量使用,主要因为其低成本、大容量的特点,非常适合海量数据的长期存储,短期内仍难以被完全淘汰。

1.2 磁盘的工作原理及特点

  磁盘的核心工作原理是:二进制序列通过磁头的充放电过程写入盘片。任何硬件本质上只能识别二进制信号(高低电平),磁头通过充电或放电,将电信号转化为磁信号,改变盘片表面磁性区域的排列,进而完成数据的写入。

主要特点:

  1. 计算机内部信息流动通常以电子或光电信号形式高速传输,而磁盘依靠机械运动,速度相对较慢。
  2. 磁盘工作时,盘片高速旋转,磁头沿盘面半径方向微调位置,但始终悬浮于盘面之上,不直接接触。
  3. 磁盘制造必须在无尘密封环境中进行,灰尘若进入磁盘,会划伤盘面,造成数据丢失。
  4. 内存(RAM)属于易失性存储,掉电后数据丢失,而磁盘则是持久性存储,即使断电,数据仍可保存。

补充:磁盘是有寿命的,在企业级存储中,磁盘接近报废时不会随意丢弃,因为盘内存有大量敏感数据。磁盘密封性强,不易物理销毁,因此大公司通常会通过专业安全部门,采用严格流程销毁磁盘,以防数据泄露风险。

1.3 存储构成

在这里插入图片描述

1.3.1 磁盘的基本工作方式

  1. 磁头的左右摆动:磁头臂在磁盘表面径向移动,定位到特定的磁道(柱面)
  2. 盘片的旋转:当磁头静止在某个磁道上时,高速旋转的盘片使目标扇区经过磁头下方

1.3.2 存储的最小单位:扇区

  磁盘被访问的最基本单位是扇区,传统大小为512字节,现代高级格式磁盘通常采用4KB扇区。基于这一特性,我们可以将磁盘视为由无数个扇区构成的线性存储介质。

1.3.3 三维定位:如何访问一个扇区

  1. 磁头选择:确定使用哪个盘面(对于多碟片磁盘)
  2. 磁道定位:在选定盘面上找到具体的磁道(柱面)
  3. 扇区识别:在旋转的磁道上等待目标扇区经过磁头

1.4 逻辑抽象

在这里插入图片描述

1.4.1 磁带的线性存储特性

  从物理结构来看,磁带是一种顺序访问的存储介质,数据以线性方式排列在磁带上。如果我们将磁带逻辑上摊开,可以将其抽象为一个连续的线性存储空间。这种抽象允许我们使用逻辑块寻址(LBA, Logical Block Addressing)的方式来组织和管理磁带上的数据。

1.4.2 逻辑扇区与LBA地址

  • 每个逻辑扇区(通常为固定大小,如512字节或4KB)对应磁带上的一个存储单元
  • 通过LBA地址(即逻辑扇区号,类似于数组下标)唯一标识每个扇区
  • 整个磁带可视为一个由LBA地址索引的大数组,支持随机访问的逻辑模型(尽管物理上仍需顺序访问)

问题1:如何通过下标找到我们要写入的扇区?

假设有以下磁盘参数:

  • 每个盘面有2w个扇区
  • 每个盘面有50个磁道(50个圈)
  • 每个磁道有400个扇区(每一圈分了400份)

以LBA地址28888为例:

  • 28888 / 20000 = 1
  • 28888 % 20000 = 8888
  • 8888 / 400 = 22
  • 8888 % 400 = 88
  • 第一个磁盘(下标从0开始)的第22个磁道的第88个扇区。
  • Cylinder Header Sector ==> CHS寻址方式
  • C = 22 ;H = 1;S = 88

问题2:为什么扇区大小不均匀,但 LBA 地址是均匀的?
  因为磁盘的磁道是从中心向外辐射分布的,靠近中心的磁道周长小,外圈磁道周长大。在早期磁盘设计中,为了简化控制逻辑,通常会采用固定扇区数设计,即每个磁道分配相同数量的扇区。但这样导致内圈扇区物理尺寸小,外圈扇区物理尺寸大,扇区大小在物理空间上是不均匀的。

  通过调整数据编码密度(比如让内圈磁道的数据编码更稠密,外圈更稀疏),理论上可以让每个扇区实际存储的数据量更均衡。现在的磁盘也普遍采用了这种区段分区(Zoned Recording)技术,让外圈磁道能存储更多数据,提升存储效率。

  至于LBA(Logical Block Addressing)地址,它是逻辑地址,按“块”递增编号,与物理扇区大小无关,映射层可以屏蔽底层磁道结构差异,保证逻辑地址空间连续、均匀,方便操作系统统一管理磁盘数据

  当然,如果对物理扇区大小进行调整,底层算法和控制逻辑也需要相应适配,现有磁盘固件已能很好支持这种变化。

1.4.3 回归硬件

问题:我们究竟是如何和硬件交互的?
  虽然大多数人只接触操作系统层面的文件操作(如 read / write),但在底层,其实我们与硬件的交互是通过“寄存器”完成的 —— 不止 CPU 有寄存器,所有外设也都有自己的寄存器集,磁盘也不例外。

当 CPU 向磁盘发起一次 I/O 请求时,交互过程大致如下:

  1. 控制寄存器(Control Register):CPU 通过它告诉磁盘“我要做什么”——是要读数据,还是要写数据。也可能指定其他控制位,如启动位、复位位等。(告诉磁盘是打算读还是打算写)
  2. 数据寄存器(Data Register):用于传输数据。如果是写操作,CPU 会将要写入的数据写入磁盘的数据寄存器;如果是读操作,磁盘会将读出的数据放入此寄存器,由 CPU 读取。((告诉磁盘要写入哪些数据,读那些数据)
  3. 地址寄存器(Address Register):用于告诉磁盘“要读/写哪一块数据”,一般是使用逻辑块地址(LBA)表示。磁盘控制器会把 LBA 转换为实际的 CHS(柱面-磁头-扇区)地址,定位到具体的物理位置。
  4. 状态/结果寄存器(Status Register):用于反馈 I/O 操作是否成功,或当前磁盘状态(如是否就绪、是否发生错误、空间是否不足等)。CPU 通过轮询、或中断机制读取此寄存器判断操作结果。

2 文件系统

  通过逻辑抽象,虽然我们可以把磁盘扇区的地址抽象成线性的 LBA 地址,但实际操作系统要处理很多关键问题,比如:

  • 磁盘有多大?
  • 已用了多少扇区?
  • 哪些扇区还没用?
  • 哪些扇区存放的是元数据(如文件属性)?
  • 哪些扇区存的是实际内容?
  • 新写入的数据该写到哪个扇区?

这些问题决定了操作系统必须设计一套机制,把磁盘空间合理地组织和管理起来!

2.1 磁盘分区

  首先,物理磁盘通常会被划分成多个分区,每个分区就像一个逻辑磁盘,独立管理。分区的划分可以灵活配置(如图所示)。

在这里插入图片描述

2.2 文件系统结构

  每个分区内部,通常会被组织成若干 Block Group,而每个 Block Group 里又由多个区域组成,比如:

Boot Block:文件系统中的特殊块,位于起始位置,存放 Boot Loader 引导信息,作用是启动操作系统。

Block Group:分区内部会被划分为一个个 Block,Block 大小在格式化时确定,通常不可修改。

每个 Block Group 典型结构包括:Super Block、Group Descriptor、Block Bitmap、Inode Bitmap、Inode Table、Data Blocks

2.3 inode机制

  • inode 存储文件的全部属性信息(比如权限、所有者、大小、修改时间),但不存储文件名!
  • 每个文件都有唯一的 inode 号,inode 号是文件的唯一标识。
  • 文件的实际内容存放在 Data Block 区域,inode 里会记录指向这些块的地址。
  • 可以通过 ls -li 命令查看文件的 inode 编号。

2.4 Data Block

Data Block:是文件系统中用于存放文件实际内容的区域。数据以块(Block)为单位存储,常见的 Block 大小是 4KB,通常 一个块只存放自己的数据,不会混合多个文件的数据。

问题:一个文件对应多少个 Data Block?

  • 每个文件在文件系统中只有一个 inode,inode 记录了文件的属性信息,同时也记录了 该文件的内容存放在哪些数据块里。
  • 如果文件很大,可能需要占用很多个 Data Block,因此 inode 里需要有“指向数据块”的信息。
  • 具体来说,inode 结构体里有一个 block 数组,用于存储数据块的编号(Block Number)。

问题:如果文件非常大,那 block 数组是不是要非常大?
不是的,block 数组通常设计得很有限,里面分为两种类型的指针:

  • 一部分是直接索引(Direct Pointer),直接指向实际的数据块。
  • 还有一部分是间接索引(Indirect Pointer):
    • 一级间接索引:指向一个“块号列表”块,该块里存储更多数据块的块号;
    • 二级间接索引、三级间接索引也是同理,逐层扩展。
  • 通过这种直接+间接索引的设计,文件系统可以用很小的 inode 结构,支持非常大的文件,同时又不浪费空间。

  block 数组并不需要无限增大,而是通过“多级间接索引”机制来高效管理大文件的数据块映射,兼顾了小文件的访问效率和大文件的存储能力!

2.5 Bitmap

  操作系统如何知道磁盘里哪些块/哪些 inode 被用过、哪些还空闲?这就靠 位图(Bitmap) 来管理!

  • Block Bitmap 记录着 Data Block 的使用情况:
    • 每一个 bit 对应一个 Data Block
    • bit=1 表示该块已被占用
    • bit=0 表示该块空闲,可用
  • 通过 Block Bitmap,文件系统可以快速找到空闲的数据块,分配给新文件或追加的文件内容

  • inode Bitmap 记录着 inode 的使用情况:
    • 每一个 bit 对应一个 inode
    • bit=1 表示该 inode 已被占用(已有文件或目录)
    • bit=0 表示该 inode 空闲,可用
  • 文件系统创建新文件时,先查 inode Bitmap,找到一个空闲 inode,分配给新文件,建立文件属性信息。

问题1:为什么下载一个文件很久,删除一个文件却很快?
  因为 删除文件时并不会真正“擦除”文件内容,操作系统只是在 Block Bitmap 和 inode Bitmap 里把对应 bit 标记为“空闲”,表示可以被新文件覆盖即可,整个过程非常快。相反,下载/写入文件需要实际分配 inode 和数据块,还要写入磁盘数据,过程自然更慢。

问题2:误删了文件,还能恢复吗?
  其实误删后,文件内容本身还在磁盘上,只是对应的 Bitmap 标记为“可用”了。如果没有新数据覆盖,理论上是可以恢复的!

  恢复过程一般是先找到文件的 inode 编号,读取 inode 里记录的 block 位置,重新拼接数据。通常需要依靠专业工具或人员,因为一旦新文件写入,会覆盖旧数据,恢复难度就变大。

  Block Bitmap 和 inode Bitmap 是文件系统中非常关键的机制,通过简单高效的 bit 标记,帮助操作系统快速管理和分配磁盘空间,也是文件删除“秒删”背后的原因!

2.6 GDT (Group Descriptor Table)和超级块(Super Block)

  在文件系统中,除了我们常说的 inode 和 Data Block,其实还有一些 “元信息” 结构,来描述整个文件系统的组织和状态,GDT 和超级块就是最核心的两部分:

  • GDT(Group Descriptor Table,块组描述符表)
    • 该组的数据块使用情况
    • 该组的 inode 使用情况
    • 该组内各区域的位置(如 Bitmap、inode 表)
  • 可以理解为 “块组的小管家”,帮助系统快速定位某个组内资源。

  • Super Block(超级块)
    • 总共有多少 block / inode
    • 当前未用的 block / inode 数量
    • block / inode 的大小
    • 最近挂载、修改、检查的时间
    • 其他文件系统配置信息
  • Super Block 是整个文件系统的“核心配置表”,如果它损坏,整个文件系统就无法正常工作。

超级块的备份机制

  • 虽然 Super Block 是存在块组内部的,但是它记录的是整个分区的信息,理论上一个 Super Block 足够。
  • 但因为 Super Block 极为重要,一旦损坏文件系统就崩,所以文件系统一般会在分区内的多个组中存放 Super Block 的备份。
  • 这样即使主 Super Block 损坏,操作系统也可以用备份 Super Block 来恢复文件系统结构。

问题:操作系统怎么找到超级块?它在那么多块里怎么定位?
  靠“魔数”,超级块内部有一个固定偏移位置,存放一个特殊值,叫做 魔数(Magic Number),这个魔数是文件系统格式定义的唯一标识。操作系统读取磁盘块时,只要在固定偏移处找到正确的魔数,就知道这个块是超级块,从而完成定位。

  GDT 负责管理块组内的资源,超级块负责记录整个文件系统的元信息,魔数帮助操作系统准确定位超级块,保证文件系统的可靠性和稳定性!

2.7 格式化

  在一个分区使用之前,必须先格式化 ——也就是提前将一部分文件系统的属性信息(比如超级块、GDT、Bitmap、inode 表等)写入分区对应的位置,这样才能保证后续系统在使用该分区时,知道如何管理磁盘空间,如何存取数据。

  另外,格式化也可以让磁盘恢复到“未使用”状态,Bitmap 置零,逻辑上所有空间重新可用,方便新的数据写入。

3 对目录的理解

3.1 新建 & 删除文件,系统背后做了什么

新建文件

  • 系统首先会根据路径信息,定位到对应的分区,读取该分区的超级块。
  • 超级块里知道当前 block 总数、inode 总数等基础信息。
  • 系统再通过 GDT 确认该分区中哪些 block group 状态最好(空间最多、inode 最空闲)。
  • 然后通过 inode Bitmap 找到一个空闲 inode,分配给新文件,并填写文件属性信息。
  • 如果文件需要写入数据,还会根据内容大小,确定需要多少 数据块,通过 Block Bitmap 查找空闲块,把块号写入 inode 结构体的 block 数组里,最后把文件内容写入这些数据块中。

一句话总结:新建文件,其实就是分配 inode + 分配数据块 + 填写属性 + 写入数据。


删除文件

  • 系统同样先根据路径定位分区,找到对应的 inode。
  • 然后修改 inode Bitmap 和 Block Bitmap,把对应位置的 bit 置 0,表示 inode 和数据块已释放,可再次使用。
  • 文件数据实际上还在磁盘上,直到被新的文件覆盖。

一句话总结:删除文件,只是修改 Bitmap 标志位,数据本身不会立刻被清空。

3.2 目录文件

  在 Linux 文件系统中,目录也是一种特殊的文件。它也有自己的 inode,也由 属性 + 内容 两部分组成。但它的内容部分不是普通数据,而是由“文件名 → inode编号” 的映射关系表组成,起到类似键值对的效果

问题1:为什么一个目录下不能有同名文件?
  因为在目录中,文件名是 key,inode 是 value,key 必须唯一。如果有两个文件名相同,系统将无法通过文件名准确查找 inode,因此这是被禁止的。

问题2:为什么没有写权限(w)无法创建文件?
  因为创建文件时,系统要把“文件名 → inode”写入当前目录的内容区域。如果目录本身不可写,就无法建立这条映射关系,自然就无法创建新文件。

问题3:为什么没有读权限(r)无法查看目录内容?
  因为查看目录其实是读取目录文件的内容,只有具备读权限,系统才能读出文件名 → inode 的映射,进而显示目录下有哪些文件。没有读权限,系统就“看不到”这个映射表,自然就“看不到”目录下的文件。

问题4:为什么没有执行权限(x)无法进入目录?
  因为进入一个目录本质上是对该目录执行“路径解析 + 目录跳转”的操作,系统需要“执行”该目录条目,将其 inode 载入并更新工作目录。没有执行权限,系统无法完成这一步,就无法 cd 进入该目录。

问题5:如何找到目录本身的 inode?
  目录文件本身也有 inode,但它不会出现在自己内部的映射里。要找到一个目录的 inode,需要从它的父目录读取其文件名对应的 inode 号,再从父目录的父目录……一路递归直到根目录 /,再反向解析回目标目录路径。这也是为什么:访问一个文件必须指定路径(即目录树),因为只有路径才能层层查找到 inode,进而找到目标文件。

3.3 dentry缓存(扩展)

  在 Linux 文件系统中,访问一个文件必须先从路径解析出对应的 inode,而路径是分层的,也就是:

/home/user/docs/file.txt → 要先找到 / → 然后找 home → 再找 user → 最后找到 file.txt

  而每一级目录都需要读取目录文件,找到对应文件名的 inode,这个过程就涉及到不断查找、不断磁盘 IO,如果每次都从磁盘递归查找,效率会非常低!

  所以Linux提供了dentry缓存,将常用文件的inode信息缓存起来!

  dentry缓存,简称dcache,是Linux为了提高目录项对象的处理效率而设计的。它是一个slab cache,保存在全局变量dentry_cache中,用于保存目录项的缓存。dentry结构是一种含有指向父节点和子节点指针的双向结构,多个这样的双向结构构成一个内存里面的树状结构,也就是文件系统的目录结构在内存中的缓存了。

4 软硬链接

4.1 软链接

在这里插入图片描述

  软连接(Symbolic Link),也称为符号链接,本质上是一个独立的文件,拥有自己的 inode 和数据块。它的内容并不是实际的数据,而是一个指向目标文件路径的字符串。可以类比为 Windows 系统中的快捷方式。

  由于软链接只是路径的引用,如果原始文件被删除或移动,软链接就会“失效” —— 我们常说的“断链”(broken link),

4.1.1 应用场景举例

  当一个可执行程序放在了某个路径很深的目录中(如 /usr/local/lib/xxx/target/bin/run.sh),每次手动执行很麻烦。我们可以在当前目录下创建一个指向它的软链接:

ln -s /usr/local/lib/xxx/target/bin/run.sh ./run

  这样以后只要在当前目录下执行 ./run 就可以了,可执行文件的位置再深也无所谓,有软链接就能随时调用,非常适合常用工具的快速访问。

4.2 硬链接

在这里插入图片描述

  和软链接不同,硬链接不是一个独立的文件,因为它没有独立的 inode。所谓“创建硬链接”,本质上就是:

  • 在某个目录的数据块中新增一个“文件名 → inode编号”的映射关系,也就是说,只是给现有 inode 多取了一个“别名”。
  • 多个硬链接共享同一个 inode 和同一组数据块,谁被打开其实都等价于访问原始文件。

4.2.1 硬链接下的引用计数机制

  每个 inode 都有一个引用计数,表示有多少个目录项指向它。

问题1:为什么一个目录的引用计数是 2

  • 因为一个目录创建完成后会包含两个默认的子项:
    • .→ 指向自己
    • ..→ 指向父目录

问题2:当目录下再新建一个文件,为什么引用计数变为 3

  • 因为新建的文件会指向该目录作为“父目录”,即该目录被 .. 多引用了一次,所以引用计数加 1。

补充:

  • 某个目录的引用计数 - 2 = 它包含的实际子目录数量
  • 删除文件时,系统会先删除该文件名与 inode 的映射,然后 inode 的引用计数减 1,只有当引用计数减为 0,才会真正释放对应的 inode 和数据块(即清空位图)

问题3:为什么 Linux 不允许对目录建立硬链接?
  操作系统禁止用户对目录创建硬链接,最主要的原因是:防止目录结构出现“环”!

Linux 的路径解析是递归向上回溯到根目录,再逐层向下查找回来。如果在中间某一层手动创建了一个指向“上级目录”的硬链接,可能会造成无限回溯,形成死循环,文件系统就崩了。

问题4:但是 ... 不就是对目录的硬链接吗?
  ... 本质上确实是目录的硬链接!

  • 但是这两个特殊链接是由操作系统自动创建的,我们无法手动创建;
  • 操作系统还强制规定路径查找时不会对 ... 做“循环遍历”,所以不会构成环;
  • 它们的存在是为了支持相对路径的便捷访问,否则用户只能用繁琐的绝对路径定位文件。

硬链接应用场景:通常用来做路径定位!!可以通过硬链接进行目录切换!(不常用)

相关文章:

  • 【机器学习深度学习】线性回归(基本模型训练流程)
  • 【BugkuCTF】overflow
  • 为什么python处理csv文件将某个值替换成另一个值并另存后,csv文件的大小减小了一半
  • 视觉疲劳检测如何优化智能驾驶的险情管理
  • 基于STM32设计的扫地机器人
  • 双向循环链表及实现
  • 数学术语之源——(矩阵或行列式的)秩数(rank)
  • 机器学习1——贝叶斯理论上
  • GPU 性能可变性分析框架
  • 60 python asyncio模块(异步IO)
  • CANdela/Diva系列10--CDD文件在CANoe工程的应用2
  • LeetCode 312 戳气球题解(Swift)+ 区间 DP 原理详解 + 可运行代码
  • 高斯过程动态规划(GPDP)
  • FLUX.1 Kontext(Dev 版)训练lora基础教程
  • 将listener转换为事件流
  • 系统思考:结构影响行为
  • VS2022配置x86/x64调用32位和64位汇编语言动态库环境
  • 【C/C++】C++26新特性前瞻:全面解析未来编程
  • 【k近邻】 K-Nearest Neighbors算法原理及流程
  • 双指针技巧深度解析