一、引入"inode"概念
文件 = 数据 + 属性 , 当我们使用ls -l 的时候看到了除了文件名 , 还能看到文件的元数据 (属性)

ls -l 读取存储在磁盘上的文件信息 , 然后显示出来

其实这个信息除了通过这种方式来读取 , 一个stat 命令能够查看更多信息

文件数据都存储在 "块" 中 , 那么很显然,我们还必须找到一个地方存储文件的元信息(属性信息),比如文件的创建者 、文件的创建日期 、 文件大小等等 。这种存储文件元信息的区域就叫做 inode , 中文译名 , "索引结点"



每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。
- Linux下文件的存储是 属性 和 内容 分离存储的
- Linux下,保存文件属性的集合叫做 inode, 一个文件 , 一个inode , inode内有一个唯一的标识符 , 叫做inode号
所以一个文件的属性 inode长什么样子呢 ?


重点:
- 文件名不存放在 inode 中(因为string的大小会浮动,导致inode结点大小改变),而是存放在目录文件的数据块里(目录项中)。这是 inode 设计的一个关键点:文件名和 inode 分离,使得硬链接成为可能。
- inode 的大小一般是128字节或者256 , 我们后面统一 128字节 。这使得文件系统可以像数组一样管理 inode,通过 inode 号直接计算出它在磁盘上的位置。
- 任何文件的内容大小可以不同,但是属性的大小一定是相同的
问题引出:
-
硬盘是典型的 "块" 设备 , 操作系统读取硬盘数据的时候 , 读取的基本单位是块 。 块又是硬盘的每个分区下的结构 , 难道 "块" 是随意在分区上排布的吗 ? 要如何找到块呢?
-
上面提到的存储文件属性的 inode , 又是如何放置的 ?
文件系统就是为了组织管理这些的!
文件系统通过在分区中预先规划出块组 ,在块组中设立位图 来管理空闲状态,设立inode表 来集中存放属性,并利用inode内的指针数组来记录内容块的位置,从而将一盘散沙的LBA扇区,组织成了一个井然有序、可以通过路径和文件名快速存取数据的结构化空间。
二、ext2文件系统



- 我们想要在硬盘上存储文件,必须先把硬盘格式化为某种格式的文件系统 ,才能存储文件
- 裸硬盘本身只是一堆无序的物理扇区,没有任何 "文件" 的概念,操作系统无法直接在上面创建、读取或删除文件。
- 格式化的本质:给硬盘分区写入一套固定的管理规则(文件系统),比如 Ext2/Ext3/Ext4、NTFS、FAT32 等。
- 只有完成格式化,分区才会被划分成块、块组、inode 表等结构,操作系统才能用 "文件" 的方式去组织和访问数据。
- 文件系统的目的就是组织和管理硬盘中的文件
- 数据怎么存:把文件内容拆分成块,记录块的位置;
- 属性怎么存:用 inode 存储文件的权限、大小、时间戳等元数据;
- 资源怎么分配:用位图管理空闲块和 inode,避免数据覆盖;
- 路径怎么解析:用目录文件维护 "文件名 → inode 号" 的映射,让用户能通过路径找到文件。
2.1 宏观认识
ext2 文件系统将整个分区划分成若干个同样大小的块组 (Block Group) ,只要能管理一个分区就能管理所有分区,也就能管理所有磁盘文件

上图中启动块(Boot Block/Sector)的大小是确定的,为1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是ext2文件系统的开始。
2.2 Block Group
Ext2 文件系统将每个分区划分为若干个 Block Group,所有块组的结构完全相同 ,就像一个 "行政区",每个行政区独立管理自己的资源,整体又由超级块统一协调。
每个 Block Group 的结构从前往后依次为:启动块(Boot Block)→ 超级块(Super Block)→ 块组描述符表(GDT)→ 块位图(Block Bitmap)→ inode 位图(Inode Bitmap)→ inode 表(Inode Table)→ 数据块(Data Block)。
2.3 块组内部构成
- 块组的各个组件各司其职,共同完成文件的存储与管理,缺一不可。
- inode 和 数据块 , 跨组编号的
- inode和数据块,不能跨分区!!!(所以,一个分区内部,inode编号和块号是唯一的)
2.3.1 超级块(Suoer Block)


超级块存储整个分区的文件系统全局信息,是 Ext 文件系统的核心,若超级块损坏,整个文件系统将无法使用。其记录的关键信息包括:
- Block 和 Inode 的总数量、空闲数量;
- Block 和 Inode 的大小;
- 文件系统的挂载时间、写入时间、磁盘检查时间;
- 块组的数量、每个块组的 Block/Inode 数量。
为了保证可靠性,超级块会在多个块组中进行备份(第一个块组必须有),防止单个扇区物理损坏导致超级块丢失。



2.3.2 GDT(Group Descriptor Table)
GDT 包含多个块组描述符,一个块组对应一个描述符,记录每个块组的局部信息:
- 块组内 Block Bitmap、Inode Bitmap、Inode Table 的起始块号;
- 块组内空闲 Block、空闲 Inode 的数量;
- 块组内已使用的目录数量。
GDT 同样会在多个块组中备份,与超级块配合实现文件系统的整体管理。

2.3.3 块位图(Block Bitmap)

位图采用位(bit) 作为单位,每一位对应一个 Block 或 Inode,用于快速标记资源的空闲 / 占用状态:
- Block Bitmap :每一位对应一个 Data Block,0 表示空闲,1 表示已占用;
2.3.4 inode位图(Inode Bitmap)
- Inode Bitmap :每一位对应一个 Inode ,0 表示空闲,1 表示已占用。
位图的设计让文件系统能快速找到空闲的 Block/Inode,避免了遍历整个分区的低效操作。
举一个例子:
- 删除电影 , 不需要把内容删掉 , 只需要把位图清0 ;
- 对于磁盘来说,只要块 inode 没有,内容就是乱码,无效
- 对于文件的恢复 : 把位图再置1

2.3.5 节点表(Inode Table)
在 Linux 中,文件 = 内容 + 属性 ,文件的内容存储在 Data Block 中,而文件的属性(元信息) 则存储在Inode(索引节点) 中,Inode 表就是块组内所有 Inode 的集合。
什么是 Inode?
Inode 是一个固定大小的结构体(通常 128/256 字节),每个文件对应唯一的一个 Inode,且 Inode 编号以分区为单位全局唯一,不可跨分区。Inode 中存储的文件属性包括:
- 文件的权限、所有者、所属组;
- 文件的大小、创建时间、访问时间、修改时间;
- 文件占用的 Data Block 编号;
- 文件的硬链接数。
重要注意 :文件名并不存储在 Inode 中,这是 Linux 文件系统的关键设计,也是软硬链接实现的基础。
2.3.6 Data Block

Data Block 是真正存储文件内容的区域,根据文件类型的不同,Data Block 的存储内容也不同:
- 普通文件:直接存储文件的实际数据;
- 目录文件 :存储文件名与 Inode 号的映射关系(这是目录的核心本质);
- 设备文件 / 管道文件:无实际数据,Data Block 为空。
**Data Block 的编号以分区为单位全局唯一,不可跨分区,**文件占用的 Data Block 编号会记录在其对应的 Inode 中。

2.4 inode 和 datablock 映射(弱化)
Inode 中包含一个块指针数组 i_block [EXT2_N_BLOCKS] (EXT2_N_BLOCKS=15),**用于建立 Inode 与 Data Block 的映射关系,**通过这个数组,就能找到文件内容所在的所有 Data Block。

问:知道inode 号的情况下,在指定分区 , 解释 : 对文件的增 、 删 、 查 、 改 是在做什么?
- 分区之后的格式化操作,就是对分区进行分组 , 在每个组中写入 SB 、GDT , Block bitmap等管理信息 , 这些管理信息统称为:文件系统
- 只要知道文件的inode号 , 就能在指定分区中确定是哪一个分组 , 进而在哪一个分组中确定是哪一个inode
- 拿到inode文件的属性和内容就全部都有了
下面,通过touch一个新文件来看看如何工作。





第一步:用 inode 编号查 inode 位图(有效性校验)
- 每个块组都有一个 inode 位图(inode bitmap) ,用 1 个 bit 标记对应 inode 是否被占用:
bit=1:这个 inode 有效,代表存在一个文件 / 目录;bit=0:这个 inode 空闲,未被使用。- 拿到 inode 编号后,先去位图里查对应 bit:
- 如果是 1 → 说明这个 inode 是合法存在的;
- 如果是 0 → 说明这个文件已经被删除或不存在。
第二步:从 inode 表中读取 inode 结构体
每个块组都有一个 inode 表(inode table),是一段连续的磁盘空间,里面按顺序存放所有 inode 结构体(每个 inode 大小固定,比如 128/256 字节)。
计算偏移:
inode 在 inode 表中的偏移 = (inode 编号 - 1) × 每个 inode 的大小直接读取这段偏移的数据,就能得到完整的
struct ext2_inode,里面包含了文件的所有元信息:权限、大小、时间戳、数据块指针等。第三步:通过 inode 里的 data block 数组找到文件内容
struct ext2_inode里有一个数组i_block[EXT2_N_BLOCKS](通常是 15 个指针):
- 前 12 个是直接块指针 :直接指向存储文件内容的 data block 编号;
- 后 3 个是间接块指针:指向索引块,索引块里再存 data block 编号,用来存大文件。
- 只要遍历这个数组,就能拿到文件所有 data block 的编号,再根据块号 → LBA 地址 → 磁盘扇区,最终读出文件的全部内容。
2.5 目录与文件名
我们平时访问文件时,使用的是路径 + 文件名 (如 /home/whb/test.c),但 Linux 的文件系统核心是 Inode,并不直接识别文件名,那么从 "文件名" 到 "文件内容 / 属性" 的过程是如何实现的?这就涉及到目录的本质 、路径解析 和路径缓存。
目录的本质:文件名与 Inode 的映射文件
在 Linux 中,目录也是一种文件,没有特殊的 "目录结构",其本质是:
- 目录的属性:存储在其对应的 Inode 中(与普通文件一致);
- 目录的内容 :存储在其对应的 Data Block 中,内容为一系列 "文件名 - Inode 号" 的键值对。(所以在一个目录中,文件名不可以重复)
简单来说,**目录就是一个 "映射表",**建立了文件名和 Inode 号的关联,这也是为什么删除文件名只是删除目录中的一条映射记录,而不是直接删除文件数据。
2.6 路径解析
问题:打开当前工作目录文件 , 查看当前工作目录文件的内容 ? 当前工作目录不也是文件吗 ? 我们访问当前工作目录不也是只知道当前工作目录的文件名吗 ? 要访问它 , 不也要知道当前工作目录的inode ?
所以也要打开 , 当前工作目录的 上一级 目录 , ... , 上一级目录也是目录 !所以类似 "递归" , 需要把路径中所有目录全部解析 , 出口是 '/' 根目录 。
任何文件 , 都有路径 , 访问目标文件 ,例如:

以访问/home/whb/test.c为例,路径解析的步骤为:
- 系统开机后默认知道根目录 "/" 的 Inode 号(固定值),先打开根目录的 Inode,找到其对应的 Data Block;
- 在根目录的 Data Block 中,查找 "home" 对应的 Inode 号,打开 home 目录的 Inode;
- 在 home 目录的 Data Block 中,查找 "whb" 对应的 Inode 号,打开 whb 目录的 Inode;
- 在 whb 目录的 Data Block 中,查找 "test.c" 对应的 Inode 号,找到后即可通过该 Inode 获取文件的属性和内容所在的 Data Block。
核心结论 :访问文件必须依赖路径 ,**路径的本质是逐层的目录映射表,**最终通过文件名找到 Inode 号,进而访问文件。


所以,这个用户提供文件名 , 进程提供cwd , 共同构建了linux的路径结构
2.7 路径缓存

如果每次访问文件都要从根目录开始逐层解析磁盘上的目录,效率会非常低。因此,Linux 内核引入了路径缓存(dentry 缓存) ,在内存中(只在OS中保存,不存在磁盘里)维护了一棵树形的目录结构 ,由**内核结构体struct dentry(目录项)**构成。
- 每个文件 / 目录都对应一个 dentry 结构,包含其 Inode 指针、父目录 dentry 指针、子目录 dentry 链表;
- 当首次访问某个路径时,内核会从磁盘加载目录信息,在内存中创建 dentry 结构,形成树形缓存;
- 后续再次访问该路径时,直接从内存的 dentry 缓存中查找,无需再访问磁盘,大幅提升效率。
dentry 缓存采用LRU(最近最少使用) 机制进行淘汰,同时结合哈希表实现快速查找,平衡了缓存效率和内存占用。



2.8 挂载分区
Inode 和 Block 的编号都是以分区为单位 的,跨分区后 Inode 号会重复,那么 Linux 如何管理多个分区的文件系统?答案是挂载(mount)。

挂载的本质
将一个格式化好的分区 与一个空目录 建立关联,这个目录称为挂载点 ,此后访问该挂载点,就是访问对应分区的文件系统。简单来说,挂载就是将分区的文件系统 "接入" Linux 的根目录树形结构中。
以挂载一个 ext4 格式的磁盘镜像文件为例:
# 创建磁盘镜像(5M)
dd if=/dev/zero of=./disk.img bs=1M count=5
# 格式化为ext4文件系统
mkfs.ext4 disk.img
# 创建挂载点
mkdir /mnt/mydisk
# 挂载分区到挂载点
sudo mount -t ext4 ./disk.img /mnt/mydisk/
# 卸载分区
sudo umount /mnt/mydisk
关键结论
- 未挂载的分区,即使格式化了文件系统,也无法被访问;
- 访问文件时,系统会根据路径前缀判断目标文件所在的分区(挂载点),进而在对应分区中解析 Inode;
- Linux 的根目录树形结构,本质是多个挂载的分区通过挂载点连接而成的整体。
- 所以 , 可以根据访问目标文件的 "路径前缀" 准确判断我在哪一个分区!!!
2.9 文件系统总结





三、软硬链接
基于 "文件名与 Inode 分离" 的设计,Linux 实现了文件链接 机制,分为硬链接(Hard Link) 和软链接(Symbolic Link),两者的实现原理和使用场景截然不同,是 Linux 文件系统的重要特性。
3.1 硬链接
硬链接的本质是在目录中添加一条新的 "文件名 - Inode 号" 映射记录 ,让多个文件名指向同一个 Inode,这些文件名就是该文件的硬链接。
# 创建文件abc
touch abc
# 为abc创建硬链接def
ln abc def
# 查看Inode号(两者Inode号相同)
ls -li abc def
# 输出:263466 abc 263466 def
硬链接的核心特性
-
硬链接与原文件共享同一个 Inode,拥有相同的文件属性和内容,修改其中一个,另一个也会同步变化;
-
Inode 中记录了硬链接数,创建硬链接时链接数 + 1,删除硬链接时链接数 - 1;
-
只有当硬链接数为 0 时,系统才会真正删除文件的 Inode 和 Data Block,释放磁盘空间;
-
硬链接不能跨分区 (Inode 号仅在分区内唯一
# 为abc创建软链接abc.s ln -s abc abc.s # 查看Inode号(软链接有独立的Inode号) ls -li # 输出:263563 abc 2631678 lrwxrwxrwx 1 root root 3 abc.s -> abc);
-
硬链接不能指向目录(避免目录树形结构出现循环,导致路径解析死循环)。
硬链接的特殊应用
Linux 中的 .(当前目录) 和..(上级目录) 就是硬链接:
- 每个目录的硬链接数至少为 2(自身的
.+ 父目录中的映射记录); - 若目录有子目录,子目录的
..会让该目录的硬链接数 + 1。
3.2 软连接
链接(也叫符号链接)的本质是一个独立的文件 ,有自己的 Inode 和 Data Block, 其 Data Block 中存储的内容不是实际数据,而是目标文件的路径 + 文件名 ,相当于 Windows 中的 "快捷方式"。
# 为abc创建软链接abc.s
ln -s abc abc.s
# 查看Inode号(软链接有独立的Inode号)
ls -li
# 输出:263563 abc 2631678 lrwxrwxrwx 1 root root 3 abc.s -> abc
软链接的文件权限位以l开头,明确标识其为软链接,箭头指向目标文件。
软链接的核心特性
- 软链接是独立文件,有自己的 Inode 和 Data Block,与目标文件的 Inode 无关;
- 删除软链接 ,不会影响目标文件;删除目标文件,软链接会变成 "死链接"(指向不存在的文件);
- 软链接可以跨分区 ,也可以指向目录;
- 访问软链接时,系统会根据其 Data Block 中存储的路径,自动跳转到目标文件,实现间接访问。
3.3 软硬链接对比
| 特性 | 硬链接 | 软链接 |
|---|---|---|
| Inode 号 | 与目标文件相同 | 有独立的 Inode 号 |
| 本质 | 目录的映射记录 | 独立的文件 |
| 跨分区 | 不支持 | 支持 |
| 指向目录 | 不支持 | 支持 |
| 目标文件删除 | 仍可访问(链接数 > 0) | 变成死链接 |
| 存储内容 | 无独立内容,共享数据 | 存储目标文件的路径 |
3.4 软硬链接的用途
- 硬链接 :用于文件备份(删除原文件仍可通过硬链接访问)、实现
.和..目录标识; - 软链接:用于简化文件路径(如将深层目录的文件链接到根目录)、版本管理(如将 python3 链接到 python)、跨分区访问文件 / 目录。

