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 则直接映射到闪存块)。
CHS和 LBA之间的转换可以链接操作系统和硬盘的读写定位操作。
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数量 (目标inode号−1)/每个块组的inode数量
来确定该 inode 所属的块组。接着,从 GDT 中读取对应块组的描述符,以获取该块组 inode Table 的起始地址。最后,通过
(目标inode号−1)MOD每个块组的inode数量∗inode结构体大小(128字节) (目标inode号 - 1) MOD 每个块组的inode数量 * inode结构体大小(128字节) (目标inode号−1)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 )时,文件系统会进行如下操作
- 查找空闲 inode :
文件系统首先会遍历其管理数据,特别是 inode 位图,寻找一个值为 0 的位。一旦找到空闲位,就将其置为 1,并记录下这个位对应的 inode 号。同时还会更新超级块中记录的空闲 inode 数量。 - 分配和初始化 inode 结构体:
根据上一步得到的 inode 号,文件系统计算出其在 inode Table 中的物理存储位置(参考 3.1.4),同时填入该新文件的元数据(文件类型、访问权限及其他信息)。 - 创建目录项
文件系统找到新文件所在的父目录(例如,如果是在/home/usr/下创建new_file.txt,父目录就是/home/usr/)。接下来读取父目录的数据块内容,找到一个空闲位置,创建新的目录项(包含文件名和 inode 号),随后将该目录项写入父目录的数据块中。
由于该文件没有内容,所以尚未在数据块区分配数据块。
3.2.2 文件写入
当用户向一个已经存在的文件写入数据时(例如 echo "hello" > new_file.txt ):
- 查找文件 inode:
操作系统根据文件的路径,找到文件的父目录,读取父目录的数据块,根据文件名查找到目录项中的 inode 号。通过 inode 号,文件系统定位并加载 inode 结构体到内存。 - 分配数据块
根据 inode 结构体中的i_size和写入数据的大小,文件系统判断是否需要分配新的数据块。如果需要,它会遍历块位图(Block Bitmap),寻找一个或多个空闲的数据块,一旦找到,就将其置为 1。 - 写入数据、更新 inode 结构体:
文件系统将数据写入已经分配的数据块后,在 inode 结构体中,更新信息。
3.2.3 文件删除
当用户删除一个文件时,(例如 rm new_file.txt ):
- 查找文件 inode:
与读写流程类似,通过文件路径找到new_file.txt的 inode 号,随后加载其 inode 结构体到内存。 - 释放数据块:
遍历 inode 结构体的i_block字段,获取所有指向该文件内容的数据块号,接着在块位图(Block Bitmap)中将这些数据块对应的位置为 0,最后更新超级块记录的“空闲块数量”(数据块的实际内容并不会被立即擦除)。 - 释放 inode:
在 inode 位图(inode Bitmap)中将该文件的 inode 号对应的位置为 0,同时更新超级块中记录的“空闲 inode 数量” - 删除目录项:
文件系统找到该文件所在的父目录,读取父目录的数据块内容,删除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 个指针直接指向存放文件内容的数据块 | 每个指针 4KB | 12 × 4KB = 48KB | 1 次 | 小文件(≤48KB),高效 |
| 一级间接指针 | i_block[12] | 指针指向一个「指针块」,指针块内存储 1024 个直接指向数据块的指针 | 1024 × 4KB = 4MB | 48KB + 4MB ≈ 4.048MB | 2 次 | 中等大小文件(48KB~4MB) |
| 二级间接指针 | i_block[13] | 指针指向「二级指针块」,其内部存 1024 个指向一级指针块的地址 | 1024×1024×4KB = 4GB | 4.048MB + 4GB ≈ 4.004GB | 3 次 | 大文件(4MB~4GB) |
| 三级间接指针 | i_block[14] | 指针指向「三级指针块」,其内部存 1024 个指向二级指针块的地址 | 1024³×4KB = 4TB | 4.004GB + 4TB ≈ 4.004TB | 4 次 | 巨型文件(4GB~4TB) |
这样的设计体现了空间换时间的思想,通过增加少量间接访问的开销,极大地扩展了单个文件的最大尺寸。
同时需要补充的是,inode所属的块组仅决定 inode自身的存储位置,其指向的数据块归属于整个分区,一个大文件其文件内容所存放的数据块是可以跨组分布的。
3.4 目录文件的内容存放文件名和 inode 号的映射
既然 inode 结构体内部并不存储文件名和自身的 inode 号,那么这些关键信息究竟存放在哪里?文件系统又是如何通过文件名找到对应的 inode 结构体的?
要解答这个问题,首先要明确 Linux “一切皆文件”的核心设计 —— 目录本身也是一种特殊的文件 ,这是理解文件名与 inode 关联逻辑的关键:
- 目录和普通文件一样,拥有专属的 inode 结构体和数据块;
- 两者的核心区别在于:普通文件的数据块存储用户实际数据(如文本、二进制内容),而目录文件的数据块中存储的并非用户数据,而是一个“目录项(Directory Entry, dentry)列表”;
- 每个目录项都记录了 当前目录下一个文件名与其对应的 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 时,操作系统究竟做了什么。这个过程被称为路径解析。
- 从根目录开始 :路径以
/开头,表示从根目录开始。根目录的 inode 号是固定的(通常是 2)。 - 查找 home :操作系统读取根目录(inode 2)的数据块,在其中查找名为 home 的目录项,并获取其对应的 inode 号(比如是 1234)。
- 查找 user :接着,操作系统读取 home 目录(inode 1234)的数据块,查找名为 user 的目录项,获取其 inode 号(比如是 5678)。
- 查找 notes.txt :然后,操作系统读取 user 目录(inode 5678)的数据块,查找名为 notes.txt 的目录项,最终获取到目标文件的 inode 号(比如是 9012)。
- 读取文件内容 :拿到 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 如何工作:
第一次访问 (缓存全空) :
- 查找
/: 内核查找根目录的dentry。这通常是系统启动时就创建好的。 - 查找
home: 内核读取根目录/的数据块,在磁盘上找到home目录项,获取其inode号。接着,创建一个新的dentry,将其命名为 “home”,使其d_parent指向/的dentry,d_inode指向home的 VFSinode。 - 查找
user: 内核读取home目录的数据块,找到user… 创建一个新的dentry,命名为 “user”,并将其链接到 “home” 的dentry之下。 - 查找
file.txt: …最终,为 “file.txt” 也创建了相应的dentry。 - 结果 :整个路径
/home/user/file.txt都在内存中建立起了对应的dentry链。
第二次访问 (缓存命中) :
- 查找
/home/user/file.txt: 内核再次收到这个路径。 - 它直接在
dcache中,像遍历一棵快速的内存树一样进行查找:- 从根
dentry(/) 出发。 - 在其子节点中快速找到名为 “home” 的
dentry。 - 接着在 “home” 的子节点中找到 “user”。
- 最后在 “user” 的子节点中找到 “file.txt”。
- 从根
- 结果 :从最后一个
dentry(file.txt) 中,直接获取到其d_inode指针,即文件的 VFSinode。整个过程 没有任何磁盘 I/O ,速度极快。
整个 dcache 结构可以如下图所示:

4 软硬链接
除了基本的文件操作,Linux 文件系统还提供了强大的“链接”功能,允许我们为一个文件创建多个
访问入口。这在软件部署、文件管理和节省磁盘空间等方面非常有用。链接主要分为两种:硬链接 (Hard Link) 和软链接 (Symbolic Link)。
4.1 硬链接 (Hard Link)
核心定义:硬链接是为同一个
inode创建多个不同的文件名(目录项)。
可以把 inode 想象成一个文件的“身份证”,而文件名是贴在这张身份证上的“姓名标签”。创建一个硬链接,就相当于为同一个人(同一个 inode)贴上一个新的姓名标签。无论你通过哪个名字找到他,他还是同一个人。
创建命令:ln source_file hard_link_file
工作原理:
- 系统首先通过
source_file这个名字,找到它对应的inode号。 - 接着,在当前目录的数据块中,创建一个全新的目录项,其文件名为
hard_link_file,但其inode号与source_file的 完全相同。 - 最后,系统找到这个被共享的
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
从输出中可以清晰地看到,link 和 test.c 拥有完全相同的 inode 号 264600,并且它们的链接计数都从 1 变成了 2。这证明了它们本质上是同一个文件的两个不同名字。
硬链接的特性:
- 平等的地位:所有硬链接(包括最初的文件名)地位完全平等。没有“源文件”和“链接文件”之分。
- 数据同步:由于所有链接都指向同一个
inode和同一份数据块,所以修改任何一个文件的内容,其他所有链接文件的内容都会同步改变。 - 删除机制:执行
rm命令删除一个硬链接时,仅仅是删除了一个目录项,并将inode的链接计数减 1。只有当链接计数降为 0 时,文件系统才会真正回收该inode和其对应的数据块,文件才算被彻底删除。 - 限制:
- 不能跨文件系统创建:因为
inode号只在单个文件系统(分区)内是唯一的,无法将一个分区的inode链接到另一个分区。 - 不能链接目录:为了防止在目录结构中产生循环(例如
A/B链接到A),这会使find、du等文件系统遍历工具陷入无限循环,所以系统不允许用户对目录创建硬链接。唯一的例外是系统自动为每个目录创建的.(指向当前目录) 和..(指向父目录),它们本身就是特殊的硬链接。
- 不能跨文件系统创建:因为
4.2 软链接 (Symbolic Link)
核心定义:软链接是一个独立的文件,其文件内容是另一个文件的路径。
如果说硬链接是文件的“别名”,那么软链接更像是 Windows 系统中的“快捷方式”或一个指向目标的“路牌”。它本身是一个全新的文件,只是它的内容告诉系统“真正的文件在哪里”。
创建命令:ln -s source_file soft_link_file (注意 -s 参数)
工作原理:
- 系统创建一个全新的文件
soft_link_file。 - 为这个新文件分配一个全新的、独立的
inode。 - 在这个新
inode中,文件类型被明确标记为“符号链接 (symbolic link)”。 - 将目标文件的路径字符串(例如
"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 拥有一个全新的 inode 号 264601。它的文件类型由第一个字符 l 标明,并且其文件大小为 6 字节,正好是路径名 “test.c” 的字符数(包括末尾的\0)。
软链接的特性:
- 独立的文件:软链接拥有自己独立的
inode和数据块,与源文件完全分离。 - 指针本质:它本质上是一个指向另一个路径的指针。访问软链接时,系统会根据其内容(路径)去访问真正的目标文件。
- 删除影响:
- 删除软链接本身,对源文件没有任何影响。
- 删除源文件,软链接依然存在,但它指向了一个不存在的路径,变成了一个“悬空链接 (dangling link)”或“死链接 (broken link)”。此时再访问软链接会报错。
- 灵活性:
- 可以跨文件系统:因为它存储的是路径字符串,不受
inode区域的限制,可以指向任何地方。 - 可以链接目录:可以非常方便地为一个目录创建软链接。
- 可以跨文件系统:因为它存储的是路径字符串,不受
总结对比
为了更清晰地理解两者的区别,这里有一个总结性的对比表格:
| 特性 | 硬链接 (Hard Link) | 软链接 (Symbolic Link) |
|---|---|---|
inode | 与源文件共享同一个 inode | 拥有自己独立的 inode |
| 本质 | 同一个文件的不同名字 | 一个独立的文件,内容是目标文件的路径 |
| 跨文件系统 | 不可以 | 可以 |
| 链接目录 | 不可以 | 可以 |
| 删除源文件 | 链接依然有效,只要链接数不为0,文件内容就存在 | 链接失效,变成“悬空链接” |
| 命令 | ln file link | ln -s file link |

2015

被折叠的 条评论
为什么被折叠?



