Linux文件系统

Linux 文件系统

1 硬盘寻址方式

硬盘寻址方式的核心是 精准定位数据所在的扇区 ,主流分为两类:CHS(物理寻址)LBA(逻辑寻址) —— 前者依赖机械硬盘物理结构,后者是现代系统的标准逻辑抽象,本质是 “从物理依赖到逻辑通用” 的演进,最终都是为了高效读写数据。

1.1 CHS 物理寻址

CHS(Cylinder-Head-Sector)是早期机械硬盘的寻址方式, 直接映射硬盘物理结构 ,完全依赖硬件参数。

机械硬盘的物理结构围绕“磁存储”设计,核心组件包括盘片、磁头、磁头臂、电机、控制电路,各组件精准配合完成数据读写。

在这里插入图片描述

  • 盘片:每个盘片的正反面都可存储数据(称为“盘面”),盘面数量 = 盘片数 * 2。
  • 磁头:负责读写盘面数据,每个盘面对应一个磁头。
  • 磁头臂:带动所有磁头同步移动,精准定位到盘片上的目标磁道。

扇区时硬盘的最小物理读写单位,那么,机械硬盘读写时需要先定位扇区,核心就是通过 Cylinder(柱面)Head(磁头)Sector(扇区)三个物理参数的组合,来精准定位数据在硬盘的物理存储位置。这种寻址方法成为 CHS寻址方法。

机械硬盘通过 CHS寻址时,会按 “柱面→磁头→扇区” 的顺序定位,流程类似 “找书架上的书”

  • 移动磁头臂到目标柱面(确定“书架列”):所有磁头同步移动,对准目标柱面的磁道(即同一半径的圆形轨迹)。
  • 选择目标磁头(确认“书架层”):通过磁头编号,激活对应盘面上的磁头。
  • 等待目标扇区旋转到位(确定“书架格”):盘片高速旋转,当目标扇区转到磁头下方时,开始读写数据。

三者的组合(如 C=10、H=3、S=5)能唯一确定一个物理扇区,硬盘控制器通过这个组合计算出数据的物理位置。

由于早期 BIOS 和接口的限制(例如柱面最大1024、磁头最大16、扇区最大63),CHS 寻址方式最大仅支持约 8 GB 的硬盘(1024 柱面 * 16 磁头 * 63 扇区/磁道 * 512 字节/扇区 ≈ 8 GB)。

1.2 LBA 逻辑寻址

LBA(Logical Block Addressing)是目前所有硬盘(机械 / SSD)的标准寻址方式, 屏蔽物理结构,用逻辑编号统一管理扇区

在这里插入图片描述

磁带是一种线性存储介质,数据依次排列,形成线性结构。

在这里插入图片描述

尽管磁盘在物理结构上是圆盘状的,但在逻辑上,我们可以将其抽象为一条连续的、卷绕起来的磁带。这样,磁盘的逻辑存储结构可以被理解为:

在这里插入图片描述

某一盘面的某一磁道可以展开成一维数组:

在这里插入图片描述

整个磁盘所有盘面的同一个磁道(即柱面),可以展开成二维数组:

在这里插入图片描述

整个磁盘是由多个柱面组成,可以展开成三维数组:

在这里插入图片描述

无论几维数组,本质上都是一维数组,这样整个磁盘可以按如下逻辑展开:

在这里插入图片描述

LBA 寻址将整个磁盘的所有扇区进行统一的线性编号(从 0 到 N-1)。每个扇区都被赋予一个唯一的逻辑块地址(LBA),这本质上是一个线性地址。

操作系统在定位特定扇区时,只需向硬盘控制器发送对应的 LBA 地址和读写指令,从而实现了对底层物理结构的屏蔽

硬盘控制器负责将接收到的 LBA 地址转换为对应的物理位置(例如,机械硬盘会将其转换为 CHS 地址,而 SSD 则直接映射到闪存块)。

CHSLBA之间的转换可以链接操作系统和硬盘的读写定位操作。

2 引入文件系统

2.1 分区:硬盘的“逻辑划分”

硬盘可以被划分为一个或多个“分区”。在 Windows 系统中,一块硬盘通常会被分为 C、D、E 等盘符,这些盘符本质上就代表了硬盘被分割后形成的独立逻辑分区。

从本质上讲,分区操作并非直接格式化硬盘,而是对硬盘整个逻辑地址空间(LBA 地址范围)进行逻辑分割。它将硬盘的 LBA 地址划分为多个不重叠的连续区域,每个分区占据一段独立的地址范围。例如,一块 1TB 硬盘可以被划分为 500GB 的系统分区(对应 LBA 0 ~ 976773167)和 500GB 的数据分区(对应 LBA 976773168 ~ 1953546319),从而形成逻辑上相互隔离的边界。

这种划分的核心目的是实现“分治管理”:面对大容量硬盘,通过分区可以将系统文件、个人数据等进行分类存储。这样做既能避免不同类型数据混杂无序,又能提升文件查找和管理效率;同时,某个分区即使出现问题,通常也不会影响其他分区的数据安全。

需要注意的是,传统上分区的边界通常以柱面为单位(由机械硬盘的物理结构决定,旨在确保分区边界与硬件存储单元对齐)。分区本身不直接存储文件,它仅提供一个“容器”。只有通过文件系统格式化(即写入文件系统)后,分区才会被进一步拆分为“块”(文件系统的最小分配单元),进而用于存储文件数据。

下面是对硬盘分区后的逻辑图:

在这里插入图片描述

硬盘的第一个扇区(通常是 MBR 或 GPT 分区表所在位置)会记录各个分区的起始 LBA 地址、大小和类型等信息。操作系统正是通过读取这些分区表信息来识别和管理硬盘上的分区。

2.2 块(Block):文件系统的“最小分配单元”

块(Block) 是文件系统在分区内进行数据分配的最小单位。文件系统不会直接以硬盘的“扇区”(硬盘的最小物理读写单位)为单位分配空间,而是将多个连续的扇区组合成一个“块”,然后按块为文件分配存储空间。

硬盘上的每个分区在文件系统格式化后,都会被逻辑上划分为一个个大小相同的“块”。一个“块”的大小在文件系统格式化时被确定,之后通常不可更改。最常见的块大小是 4 KB,这意味着在默认 512 字节/扇区的情况下,连续八个扇区组成一个“块”

下图可以看到,Linux 中一个块大小为 4 KB:

在这里插入图片描述

引入块概念后,磁盘逻辑图如下所示:

在这里插入图片描述

按块为文件分配空间的设计,有助于将操作系统与底层硬件解耦。即使硬件的扇区大小发生变化(例如从 512B 变为 1KB 或 4KB),对于操作系统而言,只需调整块号到 LBA 地址的转换算法,而无需修改文件系统对块的管理逻辑。

此外,这种方式还能有效减少文件碎片。若以扇区(512 字节)为单位分配,一个 1 GB 的文件将需要占用 2,097,152 个扇区,这可能导致数据高度分散。而按块(例如 4 KB)分配,同一个 1 GB 文件仅需占用 262,144 个块,大大降低了数据分散的程度,从而提升了读写效率。

3 ext2文件系统

ext2(Second Extended File System)是 Linux 系统中经典的 非日志型文件系统 ,也是 ext 系列文件系统的基础版本,核心设计是 “块组化管理 + inode 索引机制”,兼顾效率与稳定性,虽现已被带日志的 ext3/ext4 替代,但仍是理解 Linux 文件系统底层逻辑的关键。

在这里插入图片描述

简单来说:硬盘上的数据可分为两部分:

  • 用户数据:直接存储用户实际内容的部分,核心包含 inode Table(inode 区)和 Data Blocks(数据块区);
  • 管理数据:用于管理用户数据的元数据区域,具体涵盖 Super Block(超级块)、GDT(组描述符表)、Block Bitmap(块位图)和 inode Bitmap(inode 位图)。

硬盘首先会被划分为多个独立的分区,每个分区都可以被格式化并运行一套独立的文件系统。这套文件系统会进一步将分区逻辑上划分为多个块组(Block Group)。除了超级块 (Super Block) 和组描述符表 (GDT) 在分布上有特殊考虑外,所有块组的内部结构均采用上图所示的统一布局(包含 Block Bitmap、inode Bitmap、inode Table 和 Data Blocks 等)。

所谓“格式化”,就是在硬盘已完成分区的基础上,向目标分区内写入超级块(Super Block)、组描述符表(GDT)、块位图(Block Bitmap)、inode 位图(inode Bitmap)等各类管理数据。同时,它还会逻辑上划分出 inode Table(inode 区)和 Data Blocks(数据块区)等用于存储用户数据的区域。这一过程最终为文件存储搭建起一套完整的管理框架。若没有格式化写入这些管理数据,分区就只是一片无序的存储空间,系统将无法识别和有效利用。

3.1 块组内部构成

3.1.1 超级块(Super Block):文件系统的“全局总控中心”

超级块(Super Block)存放着文件系统自身的关键结构信息,它描述了整个分区文件系统的元数据。这个数据结构对于文件系统的正常运行至关重要。操作系统在挂载分区、创建文件或查找 inode / 数据块时,首要步骤就是读取超级块,以获取全局配置和规则,进而指导后续的局部数据操作

Super Block 这个数据结构的大小固定为 1024 字节。它记录的信息主要包括:块(block)和 inode的总量、未使用的块和inode数量、块和inode 的大小、文件系统最近一次挂载时间、最近一次写入数据时间等与文件系统整体相关的信息。这些信息是整个文件系统(即所有块组)共享的全局性数据

在这里插入图片描述

由于 Super Block 记录的是整个分区的文件系统元数据,一旦其损坏,整个文件系统便会崩溃。因此,为了提高容错性,文件系统会在多个块组中存放 Super Block 的备份副本。早期版本的文件系统可能在每个块组中都存放 Super Block 的完整副本,这会严重降低磁盘空间利用率。在后续的 ext2 版本中,为了平衡冗余性和空间效率,Super Block 的备份副本仅存放在特定的块组中(例如,块组号为 3N,5N,7N(N=0,1,2,3...)3^N, 5^N, 7^N (N=0, 1, 2, 3...)3N,5N,7N(N=0,1,2,3...) 等)。

3.1.2 组描述符表(GDT):所有块组的“管理档案总集”

GDT (Group Descriptor Table, 组描述符表) 是 ext2 文件系统中所有块组描述符的有序集合。一个分区被划分为多少个块组,GDT 中就包含多少个对应的块组描述符。这些块组描述符按照块组的编号顺序存储,共同构成了整个文件系统的 GDT。GDT 通常紧随 Super Block 之后,并且与 Super Block 一样,GDT 也会在多个块组中拥有备份副本(通常备份位置与 Super Block 保持一致),以提高数据可靠性。

组描述符的大小固定,其结构如下图:

在这里插入图片描述

每个块组描述符都存储着对应块组的详细元数据信息,主要包括:inode 位图和块位图的起始块号、inode Table 的起始块号、以及该块组中空闲的 inode 数量和空闲的块数量等

3.1.3 数据块区(Data Blocks):文件的“实际存储区”

数据块区 (Data Blocks) 是 ext2 文件系统块组中真正用于存放文件内容的核心区域,也是块组中占用空间最大的部分(通常占据块组 90% 以上的空间)。

该区域按文件系统块(例如 4KB) 进行划分。每个数据块都拥有一个唯一的编号(块号)。文件系统通过这些块号来定位数据块的物理位置,并根据 inode 结构体中的指针信息来关联和访问对应的块号。

数据块有三种类型:

  • 普通数据块:存储普通文件的内容
  • 目录块:存储目录的内容
  • 间接块:存储数据块的地址指针(仅用于大文件)

在这里插入图片描述

例如:一个 4 KB 的文本文件,会占用 1 个普通数据块;一个 10 MB 的大文件,会占用多个普通数据块和间接块。

3.1.4 inode 区(inode Table):文件的“元数据仓库”

在 Linux 系统中,文件的两大核心组成部分——文件内容文件属性(如权限、大小、创建时间等)是独立存储的。所有的文件属性信息都集中存放在 inode 中。

每个块组都包含一片连续的存储区域,即 inodeTable,专门用于存放该块组内所有文件的inode结构体(每个文件对应一个唯一的inode)。

inode 结构体的核心作用是记录文件的全部元数据属性。在 ext2 文件系统中,这个结构体被称为 struct ext2_inode

struct ext2_inode {
	// 基础标识:文件类型、所有者、访问权限
	// ACM时间信息
	// 存储关联:文件大小、占用的数据块数量,以及指向文件内容数据块的指针
	__u32	i_blocks;	/* Blocks count */
	__u32	i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
};

注意:ext2_inode 结构体自身不直接存储其 inode 号,也不存储文件名。这些信息(尤其是文件名)实际上是存放在该文件所在目录的文件内容中。在本篇后续内容中会详细讲解目录结构,目前只需理解通过 inode 号可以找到文件系统中的 ext2_inode 结构体。

通过 ls -i 指令可以看到文件的 inode 号(ls其实本质就是查看目录文件的内容)

# ls -i
984391 makefile  984390 test.c

Super Block 记录着整个文件系统共享的全局信息,例如每个块组统一的 inode 数量、文件系统的块大小(例如 4KB,inode Table 与数据块区均以此为单位进行组织),以及固定为 128 字节的 inode 结构体大小(仅存储文件固定属性,不含文件名等变长信息)。 当需要查找某个特定 inode 时,系统会首先通过计算

(目标inode号−1)/每个块组的inode数量 (目标inode号 - 1) / 每个块组的inode数量 (目标inode1)/每个块组的inode数量

来确定该 inode 所属的块组。接着,从 GDT 中读取对应块组的描述符,以获取该块组 inode Table 的起始地址。最后,通过

(目标inode号−1)MOD每个块组的inode数量∗inode结构体大小(128字节) (目标inode号 - 1) MOD 每个块组的inode数量 * inode结构体大小(128字节) (目标inode1)MOD每个块组的inode数量inode结构体大小(128字节)

计算出该 inode 在其块组 inode Table 内的字节偏移量,将 inode Table 的起始地址与此偏移量相加,即可精准定位到目标 inode 结构体的物理存储位置。

在这里插入图片描述

3.1.5 inode 位图和 block 位图:块组的“资源状态速查表”

inode 位图(inode Bitmap)块位图(Block Bitmap) 是 ext2 文件系统每个块组内重要的管理数据。它们本质上都是二进制位数组,核心作用是快速标记块组内 inode 和数据块的“占用/空闲”状态。通过查询位图,系统可以高效地判断资源的可用状态,而无需遍历所有的 inode 或数据块。

每个块组都拥有一套独立的 inode 位图和块位图。位图中的位序与对应 inode 号、数据块号的顺序完全一致:位值为 1 表示相应的资源已被占用,位值为 0 则表示空闲。

位图中的每个位,其在位图中的位置(索引),对应着 inode 区或数据块区相应 inode 或数据块的相对偏移。结合块组号和这个相对偏移,系统可以唯一确定具体的 inode 号和块号。

3.2 由 inode 号理解磁盘文件操作

在 Linux 文件系统中,文件名只是为了方便用户记忆和识别文件而存在的“标签”。操作系统在内部管理和操作文件时,真正依赖的并非文件名,而是 inode 号。inode 号是文件系统为每个文件和目录分配的唯一标识符。

理解文件操作,就是理解操作系统如何通过 inode 号,一步步找到文件的元数据(inode 结构体),再通过元数据找到文件实际存储在磁盘上的数据块。

3.2.1 文件创建

当用户创建一个新文件(例如 touch new_file.txt )时,文件系统会进行如下操作

  1. 查找空闲 inode :
    文件系统首先会遍历其管理数据,特别是 inode 位图,寻找一个值为 0 的位。一旦找到空闲位,就将其置为 1,并记录下这个位对应的 inode 号。同时还会更新超级块中记录的空闲 inode 数量。
  2. 分配和初始化 inode 结构体:
    根据上一步得到的 inode 号,文件系统计算出其在 inode Table 中的物理存储位置(参考 3.1.4),同时填入该新文件的元数据(文件类型、访问权限及其他信息)。
  3. 创建目录项
    文件系统找到新文件所在的父目录(例如,如果是在 /home/usr/ 下创建 new_file.txt ,父目录就是 /home/usr/ )。接下来读取父目录的数据块内容,找到一个空闲位置,创建新的目录项(包含文件名和 inode 号),随后将该目录项写入父目录的数据块中。

由于该文件没有内容,所以尚未在数据块区分配数据块。

3.2.2 文件写入

当用户向一个已经存在的文件写入数据时(例如 echo "hello" > new_file.txt ):

  1. 查找文件 inode:
    操作系统根据文件的路径,找到文件的父目录,读取父目录的数据块,根据文件名查找到目录项中的 inode 号。通过 inode 号,文件系统定位并加载 inode 结构体到内存。
  2. 分配数据块
    根据 inode 结构体中的 i_size 和写入数据的大小,文件系统判断是否需要分配新的数据块。如果需要,它会遍历块位图(Block Bitmap),寻找一个或多个空闲的数据块,一旦找到,就将其置为 1。
  3. 写入数据、更新 inode 结构体:
    文件系统将数据写入已经分配的数据块后,在 inode 结构体中,更新信息。
3.2.3 文件删除

当用户删除一个文件时,(例如 rm new_file.txt ):

  1. 查找文件 inode:
    与读写流程类似,通过文件路径找到 new_file.txt 的 inode 号,随后加载其 inode 结构体到内存。
  2. 释放数据块:
    遍历 inode 结构体的 i_block 字段,获取所有指向该文件内容的数据块号,接着在块位图(Block Bitmap)中将这些数据块对应的位置为 0,最后更新超级块记录的“空闲块数量”(数据块的实际内容并不会被立即擦除)。
  3. 释放 inode:
    在 inode 位图(inode Bitmap)中将该文件的 inode 号对应的位置为 0,同时更新超级块中记录的“空闲 inode 数量”
  4. 删除目录项:
    文件系统找到该文件所在的父目录,读取父目录的数据块内容,删除 new_file.txt 对应的目录项,同时更新父目录的 inode

文件在文件系统层面被删除,虽然数据可能仍然还在磁盘上,但是文件系统已经不知道它的存在。

3.3 inode 的 i_block 数组

我们已经知道,inode 结构体存储了文件所有的元数据,但文件的实际内容存放在数据块(Data Blocks)中。那么 inode 是如何与这些数据块关联起来的呢?答案就在 inode 结构体内部的 i_block 数组,它是一组指向数据块的指针。

为了兼顾小文件的访问效率和对大文件的支持,ext2 文件系统设计了一套精妙的多级指针方案。在标准的 ext2_inode 结构体中,i_block 数组共有 15 个指针:__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */EXT2_N_BLOCKS = 15

在这里插入图片描述

指针类型对应 inode 字段工作方式单指针管理容量累计最大管理容量(含前置指针)I/O 访问开销适用场景
直接指针i_block[0]-i_block[11]12 个指针直接指向存放文件内容的数据块每个指针 4KB12 × 4KB = 48KB1 次小文件(≤48KB),高效
一级间接指针i_block[12]指针指向一个「指针块」,指针块内存储 1024 个直接指向数据块的指针1024 × 4KB = 4MB48KB + 4MB ≈ 4.048MB2 次中等大小文件(48KB~4MB)
二级间接指针i_block[13]指针指向「二级指针块」,其内部存 1024 个指向一级指针块的地址1024×1024×4KB = 4GB4.048MB + 4GB ≈ 4.004GB3 次大文件(4MB~4GB)
三级间接指针i_block[14]指针指向「三级指针块」,其内部存 1024 个指向二级指针块的地址1024³×4KB = 4TB4.004GB + 4TB ≈ 4.004TB4 次巨型文件(4GB~4TB)

这样的设计体现了空间换时间的思想,通过增加少量间接访问的开销,极大地扩展了单个文件的最大尺寸。

同时需要补充的是,inode所属的块组仅决定 inode自身的存储位置,其指向的数据块归属于整个分区,一个大文件其文件内容所存放的数据块是可以跨组分布的。

3.4 目录文件的内容存放文件名和 inode 号的映射

既然 inode 结构体内部并不存储文件名和自身的 inode 号,那么这些关键信息究竟存放在哪里?文件系统又是如何通过文件名找到对应的 inode 结构体的?

要解答这个问题,首先要明确 Linux “一切皆文件”的核心设计 —— 目录本身也是一种特殊的文件 ,这是理解文件名与 inode 关联逻辑的关键:

  1. 目录和普通文件一样,拥有专属的 inode 结构体和数据块;
  2. 两者的核心区别在于:普通文件的数据块存储用户实际数据(如文本、二进制内容),而目录文件的数据块中存储的并非用户数据,而是一个“目录项(Directory Entry, dentry)列表”;
  3. 每个目录项都记录了 当前目录下一个文件名与其对应的 inode 号的映射关系 ,这是文件名与 inode 关联的核心纽带。

inode 号直接反映了该文件的 inode 结构体在 inode 区(inode Table)中的具体存储位置,且在文件创建时就已完成分配。我们对文件的操作虽以文件名为入口,但文件系统最终需通过 inode 号定位并操作 inode 结构体,这一映射过程正是通过目录项实现的。

执行以下命令可直接读取目录文件的数据块,打印出目录下所有文件 / 子目录的 inode 号与文件名的映射关系:

# 查看 Linux-learning 目录下的文件名与inode号映射
# ls -ai Linux-learning/
264237 .   264285 BasicIO  264270 .gitignore  264298 mystdio  264303 test_01
264234 ..  264238 .git     264295 myshell     264288 Process  264312 笔记

ls -ai 命令就是解析目录文件的数据块,将其中的目录项(文件名 + inode 号)直接展示出来。

3.5 路径解析和路径缓存

现在,可以将所有的知识点串联起来,当命令行输入 cat /home/user/notes.txt 时,操作系统究竟做了什么。这个过程被称为路径解析

  1. 从根目录开始 :路径以 / 开头,表示从根目录开始。根目录的 inode 号是固定的(通常是 2)。
  2. 查找 home :操作系统读取根目录(inode 2)的数据块,在其中查找名为 home 的目录项,并获取其对应的 inode 号(比如是 1234)。
  3. 查找 user :接着,操作系统读取 home 目录(inode 1234)的数据块,查找名为 user 的目录项,获取其 inode 号(比如是 5678)。
  4. 查找 notes.txt :然后,操作系统读取 user 目录(inode 5678)的数据块,查找名为 notes.txt 的目录项,最终获取到目标文件的 inode 号(比如是 9012)。
  5. 读取文件内容 :拿到 notes.txt 的 inode 号(9012)后,系统从 inode Table 中读取其 inode 结构体,找到 i_block 指针,从而定位到所有数据块,并将它们的内容读取出来显示在屏幕上。

显而易见,上述每一步查找都可能涉及多次磁盘 I/O 操作(先读目录的 inode,再读目录的数据块),这是非常缓慢的。为了解决这个问题,Linux 内核引入了高效的缓存机制,在内核中维护树状路径结构的内核结构体叫做:struct dentry

dentry 是一个纯粹存在于内存中的内核数据结构,它能将一个文件名与其对应的 inode 建立起直接的关联。其自身不存储在磁盘上,当内核需要解析一个路径时,它会优先在 dcache 中查找对应的 dentry。如果找到了,就可以立刻获得 inode 的内存地址,从而避免了读取目录文件数据的磁盘 I/O。

struct dentry {
	// ...
	struct inode  * d_inode;	/* Where the name belongs to - NULL is negative */
	struct list_head d_lru;		/* LRU list */
	struct list_head d_child;	/* child of parent list */
	struct list_head d_subdirs;	/* our children */
	struct list_head d_alias;	/* inode alias list */
	// ...
	struct dentry * d_parent;	/* parent directory */
	struct qstr d_name;
	// ...
} ____cacheline_aligned;

dentry 将文件系统的层级结构在内存中以树状形式组织起来,每个文件都有其对应的 dentry 结构。整个树形节点隶属于 LRU 结构,进行节点淘汰,同时隶属于 Hash,方便快速查找。

让我们再次以访问 /home/user/file.txt 为例,看看 dcache 如何工作:

第一次访问 (缓存全空)

  1. 查找 / : 内核查找根目录的 dentry。这通常是系统启动时就创建好的。
  2. 查找 home : 内核读取根目录 / 的数据块,在磁盘上找到 home 目录项,获取其 inode 号。接着,创建一个新的 dentry,将其命名为 “home”,使其 d_parent 指向 /dentryd_inode 指向 home 的 VFS inode
  3. 查找 user : 内核读取 home 目录的数据块,找到 user… 创建一个新的 dentry,命名为 “user”,并将其链接到 “home” 的 dentry 之下。
  4. 查找 file.txt : …最终,为 “file.txt” 也创建了相应的 dentry
  5. 结果 :整个路径 /home/user/file.txt 都在内存中建立起了对应的 dentry 链。

第二次访问 (缓存命中)

  1. 查找 /home/user/file.txt : 内核再次收到这个路径。
  2. 它直接在 dcache 中,像遍历一棵快速的内存树一样进行查找:
    • 从根 dentry (/) 出发。
    • 在其子节点中快速找到名为 “home” 的 dentry
    • 接着在 “home” 的子节点中找到 “user”。
    • 最后在 “user” 的子节点中找到 “file.txt”。
  3. 结果 :从最后一个 dentry (file.txt) 中,直接获取到其 d_inode 指针,即文件的 VFS inode。整个过程 没有任何磁盘 I/O ,速度极快。

整个 dcache 结构可以如下图所示:

在这里插入图片描述

4 软硬链接

除了基本的文件操作,Linux 文件系统还提供了强大的“链接”功能,允许我们为一个文件创建多个
访问入口。这在软件部署、文件管理和节省磁盘空间等方面非常有用。链接主要分为两种:硬链接 (Hard Link) 和软链接 (Symbolic Link)。

4.1 硬链接 (Hard Link)

核心定义:硬链接是为同一个 inode 创建多个不同的文件名(目录项)。

可以把 inode 想象成一个文件的“身份证”,而文件名是贴在这张身份证上的“姓名标签”。创建一个硬链接,就相当于为同一个人(同一个 inode)贴上一个新的姓名标签。无论你通过哪个名字找到他,他还是同一个人。

创建命令ln source_file hard_link_file

工作原理

  1. 系统首先通过 source_file 这个名字,找到它对应的 inode 号。
  2. 接着,在当前目录的数据块中,创建一个全新的目录项,其文件名为 hard_link_file,但其 inode 号与 source_file完全相同
  3. 最后,系统找到这个被共享的 inode,并将其内部的链接计数 (i_links_count) 字段加 1

让我们通过一个实例来观察这个过程:

# 首先查看原始文件 test.c 的信息,特别注意 inode 号和链接数
# ls -li test.c
264600 -rw-r--r-- 1 root root 488 Mar 18 15:19 test.c

# 创建一个名为 link 的硬链接
# ln test.c link

# 再次查看原始文件和硬链接文件的信息
# ls -li test.c link
264600 -rw-r--r-- 2 root root 488 Mar 18 15:19 link
264600 -rw-r--r-- 2 root root 488 Mar 18 15:19 test.c

从输出中可以清晰地看到,linktest.c 拥有完全相同的 inode264600,并且它们的链接计数都从 1 变成了 2。这证明了它们本质上是同一个文件的两个不同名字。

硬链接的特性:

  • 平等的地位:所有硬链接(包括最初的文件名)地位完全平等。没有“源文件”和“链接文件”之分。
  • 数据同步:由于所有链接都指向同一个 inode 和同一份数据块,所以修改任何一个文件的内容,其他所有链接文件的内容都会同步改变。
  • 删除机制:执行 rm 命令删除一个硬链接时,仅仅是删除了一个目录项,并将 inode 的链接计数减 1。只有当链接计数降为 0 时,文件系统才会真正回收该 inode 和其对应的数据块,文件才算被彻底删除。
  • 限制
    • 不能跨文件系统创建:因为 inode 号只在单个文件系统(分区)内是唯一的,无法将一个分区的 inode 链接到另一个分区。
    • 不能链接目录:为了防止在目录结构中产生循环(例如 A/B 链接到 A),这会使 finddu 等文件系统遍历工具陷入无限循环,所以系统不允许用户对目录创建硬链接。唯一的例外是系统自动为每个目录创建的 . (指向当前目录) 和 .. (指向父目录),它们本身就是特殊的硬链接。

4.2 软链接 (Symbolic Link)

核心定义:软链接是一个独立的文件,其文件内容是另一个文件的路径

如果说硬链接是文件的“别名”,那么软链接更像是 Windows 系统中的“快捷方式”或一个指向目标的“路牌”。它本身是一个全新的文件,只是它的内容告诉系统“真正的文件在哪里”。

创建命令ln -s source_file soft_link_file (注意 -s 参数)

工作原理

  1. 系统创建一个全新的文件 soft_link_file
  2. 为这个新文件分配一个全新的、独立的 inode
  3. 在这个新 inode 中,文件类型被明确标记为“符号链接 (symbolic link)”。
  4. 将目标文件的路径字符串(例如 "test.c""/path/to/test.c")作为文件内容写入到 soft_link_file 的数据块中。

让我们同样通过实例来观察:

# ls -li test.c
264600 -rw-r--r-- 1 root root 488 Mar 18 15:19 test.c

# 创建一个名为 soft_link 的软链接
# ln -s test.c soft_link

# 查看所有文件的信息
# ls -li test.c soft_link
264600 -rw-r--r-- 1 root root 488 Mar 18 15:19 test.c
264601 lrwxrwxrwx 1 root root   6 Mar 18 15:25 soft_link -> test.c

可以看到,soft_link 拥有一个全新的 inode264601。它的文件类型由第一个字符 l 标明,并且其文件大小为 6 字节,正好是路径名 “test.c” 的字符数(包括末尾的\0)。

软链接的特性:

  • 独立的文件:软链接拥有自己独立的 inode 和数据块,与源文件完全分离。
  • 指针本质:它本质上是一个指向另一个路径的指针。访问软链接时,系统会根据其内容(路径)去访问真正的目标文件。
  • 删除影响
    • 删除软链接本身,对源文件没有任何影响
    • 删除源文件,软链接依然存在,但它指向了一个不存在的路径,变成了一个“悬空链接 (dangling link)”或“死链接 (broken link)”。此时再访问软链接会报错。
  • 灵活性
    • 可以跨文件系统:因为它存储的是路径字符串,不受 inode 区域的限制,可以指向任何地方。
    • 可以链接目录:可以非常方便地为一个目录创建软链接。

总结对比

为了更清晰地理解两者的区别,这里有一个总结性的对比表格:

特性硬链接 (Hard Link)软链接 (Symbolic Link)
inode与源文件共享同一个 inode拥有自己独立的 inode
本质同一个文件的不同名字一个独立的文件,内容是目标文件的路径
跨文件系统不可以可以
链接目录不可以可以
删除源文件链接依然有效,只要链接数不为0,文件内容就存在链接失效,变成“悬空链接”
命令ln file linkln -s file link
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值