Linux系统学习【掌握Ext系列⽂件系统的相关内容】


🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》 《MySQL数据库学习》 《Python知识内容》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在Linux系统中,文件系统负责组织、存储和管理磁盘中的数据,是理解系统存储机制不可绕过的核心知识.我们日常进行文件创建、目录访问、数据读写、磁盘挂载等操作时,背后都离不开文件系统的支持.其中,Ext系列文件系统长期以来都是 Linux 环境中最具代表性的文件系统之一,从 Ext2、Ext3 到 Ext4,其功能、可靠性和性能不断演进.本文将围绕 Ext 系列文件系统展开学习,介绍其发展过程、基本结构与核心组成,包括超级块、块组描述符、inode、数据块、位图和目录项等关键概念.同时还会分析日志机制、文件访问流程、磁盘挂载、容量查看、文件系统检查与修复等常见操作.通过这些内容,我们不仅能够掌握 Ext2、Ext3、Ext4 之间的区别,还能进一步理解 Linux 是如何在底层定位、读取和管理文件数据的,为后续学习磁盘管理、系统性能优化以及 Linux 内核相关知识打下基础.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!

目录

1.理解硬件

1.1认识磁盘、服务器、机柜、机房

磁盘装在服务器里,服务器安装在机柜中,多个机柜部署在机房里.

磁盘 → 服务器 → 机柜 → 机房

1.磁盘

磁盘用于长期存储操作系统、应用程序、数据库、文件等数据,断电后数据通常不会丢失.

常见类型:

  • HDD 机械硬盘:容量大、价格低,适合备份、归档和普通文件存储.
  • SSD 固态硬盘:速度快、延迟低,适合操作系统、数据库和高性能应用.
  • NVMe SSD:通过 PCIe 通道传输,性能通常高于 SATA SSD,常用于高性能数据库和计算场景.

常见指标:

  • 容量:GB、TB
  • 读写速度:MB/s、GB/s
  • 随机读写能力:IOPS
  • 延迟:通常以毫秒或微秒衡量
  • 接口:SATA、SAS、NVMe
  • 可靠性:故障率、写入寿命、企业级耐久度

服务器通常通过 RAID 将多块磁盘组合使用,以提高性能、容量或容错能力.但 RAID 不能替代数据备份.

2.服务器

服务器是一种为其他计算机或用户提供计算、存储、网络及应用服务的设备.

常见组成:

  • CPU:执行计算任务
  • 内存:临时保存运行中的数据
  • 磁盘:存储系统和业务数据
  • 网卡:连接网络
  • 电源:服务器通常采用双电源冗余
  • RAID 卡或存储控制器
  • 风扇和散热系统

常见类型:

  • 机架式服务器:安装在标准机柜中,数据中心最常见.
  • 塔式服务器:外形类似台式主机,适合小型办公室.
  • 刀片服务器:多个计算节点集中安装在刀片机箱中,密度较高.
  • 高密度服务器:一个机箱中包含多个独立节点,适合云计算和大规模集群.

服务器可承担多种角色,例如:

  • Web 服务器
  • 数据库服务器
  • 文件服务器
  • 邮件服务器
  • 虚拟化服务器
  • AI 或高性能计算服务器
  • 备份服务器

服务器规格常用 U 表示高度.

例如,1U 服务器高度约为44.45 毫米,2U 服务器约占两个机柜单位.

3.机柜

机柜用于集中安装服务器、交换机、存储设备、配电设备等,同时提供固定、布线、供电和散热条件.

常见标准:

  • 宽度通常为 19 英寸
  • 高度通常为 42U、45U、47U
  • 深度常见为 1000 毫米或 1200 毫米

机柜内常见设备:

  • 服务器
  • 网络交换机
  • 防火墙
  • 存储设备
  • 配线架
  • PDU 配电单元
  • KVM 管理设备
  • 理线架和光纤配线设备

机柜设计需要重点考虑:

  • 设备总功率
  • 承重能力
  • 前后风道
  • 网络和电源布线
  • 双路供电
  • 设备安装位置
  • 后续扩容空间

一般采用"前进冷风、后排热风 "的散热方式.设备安装不合理、线缆堵塞或功率过高,都可能导致局部过热.

4.机房

机房是集中部署服务器、网络、存储及配套基础设施的物理场所.大型专业机房也称为数据中心.

机房主要系统包括:

供配电系统

  • 市电输入
  • UPS 不间断电源
  • 蓄电池
  • 柴油发电机
  • 配电柜
  • 机柜 PDU
  • 双路供电系统

主要目标是确保市电故障时,服务器仍能持续运行.

制冷系统

  • 精密空调
  • 冷通道和热通道
  • 风冷或液冷系统
  • 温湿度监控

机房不是简单地"温度越低越好",而是要保持稳定、合理的温湿度,并避免局部热点和结露.

网络系统

  • 核心交换机
  • 汇聚交换机
  • 接入交换机
  • 路由器
  • 防火墙
  • 运营商线路
  • 光纤及综合布线系统

重要业务通常配置多条网络线路和冗余网络设备.

安全与监控系统

  • 门禁
  • 视频监控
  • 烟雾及温湿度监测
  • 漏水检测
  • 气体灭火
  • 动力环境监控
  • 设备告警系统

四者之间的关系

层级 主要作用 典型内容
磁盘 保存数据 HDD、SSD、NVMe
服务器 运行系统和业务 CPU、内存、磁盘、网卡
机柜 集中安装设备 服务器、交换机、PDU
机房 提供运行环境 供电、制冷、网络、消防

例如,一个网站的业务数据存放在磁盘中,网站程序运行在服务器上;服务器安装在机柜中,机柜部署在具备供电、制冷、网络和消防条件的机房内.

核心设计原则

建设或管理服务器环境时,通常重点关注四个方面:

  1. 可靠性:磁盘、服务器、电源和网络均应考虑冗余.
  2. 可维护性:设备编号、线缆标签和资产信息要清晰.
  3. 可扩展性:预留机柜空间、电力、网络端口和制冷能力.
  4. 安全性:做好访问控制、数据备份、消防和监控告警.

1.2磁盘物理结构


1.3磁盘的存储结构

扇区:是磁盘存储数据的基本单位,512字节,块设备

如何定位⼀个扇区呢?

定位一个磁盘扇区,传统上使用 CHS 地址

柱面号 Cylinder + 磁头号 Head + 扇区号 Sector

具体过程如下:

  1. 选择盘面

    根据磁头号选择对应盘面的读写磁头.通常每个盘面对应一个磁头.

  2. 定位磁道

    磁臂带动所有磁头一起径向移动,到达目标柱面.

    目标盘面与该柱面的交叉位置,就是目标磁道.

  3. 等待扇区转到磁头下方

    盘片持续高速旋转.磁头保持不动,等待目标扇区旋转到磁头下方.

  4. 识别并读写扇区

    磁盘控制器通过扇区地址标记确认目标扇区,然后开始读取或写入数据.

例如:

text 复制代码
柱面号:100
磁头号:2
扇区号:30

表示:使用第2个磁头,在第100号柱面对应的磁道上,找到第30个扇区.

定位时间通常由三部分组成:

text 复制代码
访问时间 ≈ 寻道时间 + 旋转等待时间 + 数据传输时间

现代硬盘通常不再让操作系统直接使用 CHS,而是使用 LBA(逻辑块地址).操作系统只提供一个连续编号,例如LBA 50000,硬盘控制器会在内部将其映射到实际盘面、磁道和扇区.

⽂件 = 内容+属性 都是数据,⽆⾮就是占据那⼏个扇区的问题!能定位⼀个扇区了,能不能定位多个扇区呢?

• 扇区是从磁盘读出和写⼊信息的最⼩单位,通常⼤⼩为512字节.

• 磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头.

• 磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道...,靠近主轴的同⼼圆⽤于停靠磁头,不存储数据

• 柱⾯(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数.

• 扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同.

• 圆盘(platter)数:就是盘⽚的数量.

• 磁盘容量=磁头数 × 磁道(柱⾯)数 × 每道扇区数 × 每扇区字节数

• 细节:传动臂上的磁头是共进退的(这点⽐较重要,后⾯会说明)

柱⾯(cylinder),磁头(head),扇区(sector),显然可以定位数据了,这就是数据定位(寻址)⽅式之⼀,CHS寻址⽅式.

📌 CHS寻址

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


1.4磁盘的逻辑结构

(1)理解过程

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

那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻辑存储结构我们也可以类似于:

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


(2)真实过程

⼀个细节:传动臂上的磁头是共进退的.

柱⾯是⼀个逻辑上的概念,其实就是每⼀⾯上,相同半径的磁道逻辑上构成柱⾯.所以,磁盘物理上分了很多⾯,但是在我们看来,逻辑上,磁盘整体是由"柱⾯"卷起来的.

所以,磁盘的真实情况是:

磁道

某⼀盘⾯的某⼀个磁道展开:

即:⼀维数组.

柱⾯

整个磁盘所有盘⾯的同⼀个磁道,即柱⾯展开:

柱⾯上的每个磁道,扇区个数是⼀样的.这不就是⼆维数组吗!

整盘

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

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

我们之前学过C/C++的数组,在我们看来,其实全部都是⼀维数组:

所以,每⼀个扇区都有⼀个下标,我们叫做LBA(Logical Block Address) 地址,其实就是线性地址.所以怎么计算得到这个LBA地址呢?

LBA,1000,CHS必须要!LBA地址转成CHS地址,CHS如何转换成为LBA地址?

操作系统只需要使⽤LBA就可以了!!!LBA地址转成CHS地址,CHS如何转换成为LBA地址.谁做啊??磁盘⾃⼰来做!固件(硬件电路,伺服系统)


1.5CHS和LBA地址相互转化

CHS 与 LBA 的转换公式

设:

  • C:柱面号 Cylinder,通常从 0 开始.
  • H:磁头号 Head,通常从 0 开始.
  • S:扇区号 Sector,通常从 1 开始.
  • HPC:每个柱面的磁头数,也就是盘面数.
  • SPT:每条磁道的扇区数.

CHS转成LBA:

• 磁头数每磁道扇区数 = 单个柱⾯的扇区总数
• LBA = 柱⾯号C
单个柱⾯的扇区总数 + 磁头号H每磁道扇区数 + 扇区号S - 1
• 即:LBA = 柱⾯号C
(磁头数每磁道扇区数) + 磁头号H 每磁道扇区数 + 扇区号S - 1

• 扇区号通常是从1开始的,⽽在LBA中,地址是从0开始的

• 柱⾯和磁道都是从0开始编号的

• 总柱⾯,磁道个数,扇区总数等信息,在磁盘内部会⾃动维护,上层开机的时候,会获取到这些参数.

CHS转LBA具体操作

text 复制代码
LBA = (C × HPC + H) × SPT + (S - 1)

也可以展开为:

text 复制代码
LBA = C × HPC × SPT + H × SPT + S - 1

示例

假设磁盘参数:

text 复制代码
HPC = 4
SPT = 16

目标地址:

text 复制代码
C = 2
H = 1
S = 5

计算:

text 复制代码
LBA = (2 × 4 + 1) × 16 + (5 - 1)
    = 9 × 16 + 4
    = 148

因此:

text 复制代码
CHS(2, 1, 5) = LBA 148

LBA转成CHS:

• 柱⾯号C = LBA // (磁头数每磁道扇区数)【就是单个柱⾯的扇区总数】
• 磁头号H = (LBA % (磁头数
每磁道扇区数)) // 每磁道扇区数

• 扇区号S = (LBA % 每磁道扇区数) + 1

• "//": 表⽰除取整

LBA 转 CHS具体操作

首先计算每个柱面的扇区数:

text 复制代码
每柱面扇区数 = HPC × SPT

然后:

text 复制代码
C = LBA ÷ (HPC × SPT) 的整数部分
text 复制代码
余数 = LBA mod (HPC × SPT)
text 复制代码
H = 余数 ÷ SPT 的整数部分
text 复制代码
S = 余数 mod SPT + 1

完整公式:

text 复制代码
C = LBA // (HPC × SPT)

Temp = LBA % (HPC × SPT)

H = Temp // SPT

S = Temp % SPT + 1

其中:

  • // 表示整数除法
  • % 表示取余

示例

已知:

text 复制代码
LBA = 148
HPC = 4
SPT = 16

计算柱面号:

text 复制代码
C = 148 // (4 × 16)
  = 148 // 64
  = 2

计算柱面内偏移:

text 复制代码
Temp = 148 % 64
     = 20

计算磁头号:

text 复制代码
H = 20 // 16
  = 1

计算扇区号:

text 复制代码
S = 20 % 16 + 1
  = 4 + 1
  = 5

因此:

text 复制代码
LBA 148 = CHS(2, 1, 5)

所以:从此往后,在磁盘使⽤者看来,根本就不关⼼CHS地址,⽽是直接使⽤LBA地址,磁盘内部⾃⼰转换.所以:从现在开始,磁盘就是⼀个元素为扇区的⼀维数组,数组的下标就是每⼀个扇区的LBA地址.操作系统使⽤磁盘,就可以⽤⼀个数字访问磁盘扇区了.

(1)为什么扇区号要减1或加1

CHS 中通常:

text 复制代码
柱面号从 0 开始
磁头号从 0 开始
扇区号从 1 开始

而 LBA 从 0 开始连续编号.

所以:

  • CHS 转 LBA 时使用 S - 1
  • LBA 转 CHS 时使用 余数 + 1

(2)地址排列顺序

LBA 连续递增时,通常按以下顺序变化:

text 复制代码
先增加扇区号 S
扇区走完后增加磁头号 H
所有磁头走完后增加柱面号 C

例如,假设:

text 复制代码
HPC = 2
SPT = 4
LBA CHS
0 (0, 0, 1)
1 (0, 0, 2)
2 (0, 0, 3)
3 (0, 0, 4)
4 (0, 1, 1)
5 (0, 1, 2)
7 (0, 1, 4)
8 (1, 0, 1)

(3)注意事项

现代硬盘和 SSD 通常直接使用 LBA 地址.CHS 转换中的磁头数、柱面数和每磁道扇区数,很多时候只是控制器或 BIOS 提供的逻辑参数,并不一定对应磁盘内部真实的物理结构.


2.引⼊⽂件系统

2.1引⼊"块"概念

虽然磁盘可以按 扇区 寻址,但操作系统通常不会逐个扇区管理数据,而是把若干连续扇区组合成一个更大的单位,称为:

磁盘块(Block)

块通常是操作系统或文件系统进行磁盘空间分配和 I/O 操作的基本单位.

扇区与块的关系

假设:

  • 每个扇区大小为 512 B
  • 每个块大小为 4 KiB

那么:

text 复制代码
每块扇区数 = 4096 ÷ 512 = 8

即:

text 复制代码
1 个块 = 8 个连续扇区

如果块号从 0 开始,则块号 B 对应的起始 LBA 为:

text 复制代码
起始 LBA = B × 每块扇区数

块包含的 LBA 范围为:

text 复制代码
B × N ~ B × N + N - 1

其中 N 是每块包含的扇区数.

示例

块号为 100,每块包含 8 个扇区:

text 复制代码
起始 LBA = 100 × 8 = 800

因此块 100 对应:

text 复制代码
LBA 800 ~ LBA 807

地址转换关系

整体关系可以表示为:

text 复制代码
文件偏移
   ↓
文件系统块号
   ↓
LBA 地址
   ↓
磁盘控制器内部物理位置

在传统磁盘模型中,还可以继续转换为:

text 复制代码
块号 → LBA → CHS

但现代操作系统一般只使用 LBA,不再关心真实的柱面、磁头和扇区位置.

其实硬盘是典型的"块"设备,操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样

效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个"块"(block).

硬盘的每个分区是被划分为⼀个个的"块".⼀个"块"的⼤⼩是由格式化的时候确定的,并且不可

以更改,最常⻅的是4KB,即连续⼋个扇区组成⼀个"块"."块"是⽂件存取的最⼩单位.

注意:

• 磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇区.

• 每个扇区都有LBA,那么8个扇区⼀个块,每⼀个块的地址我们也能算出来.

• 知道LBA:块号 = LBA/8

• 知道块号:LAB=块号*8 + n. (n是块内第⼏个扇区)


2.2引⼊"分区"概念

"块"解决了操作系统如何按固定大小管理数据的问题,但整块磁盘如果只作为一个整体使用,会出现管理不便.

因此,引入:

分区(Partition):把一块物理磁盘的 LBA 地址空间划分成若干相对独立的连续区域.

每个分区在操作系统看来,类似一块独立的逻辑磁盘.

分区的本质

假设一块磁盘共有:

text 复制代码
LBA 0 ~ LBA 1,999,999

可以划分为:

text 复制代码
分区 1:LBA 2048 ~ 499999
分区 2:LBA 500000 ~ 1499999
分区 3:LBA 1500000 ~ 1999999

一个分区主要由两项信息确定:

text 复制代码
分区起始 LBA
分区包含的扇区数量

因此:

text 复制代码
分区结束 LBA = 起始 LBA + 扇区数量 - 1

分区一般要求占用一段连续的 LBA 空间.

为什么需要分区

1.将系统和数据分开

例如:

text 复制代码
C 盘:操作系统和软件
D 盘:个人文件和业务数据

重新安装系统时,可以只格式化系统分区,尽量不影响数据分区.

2.使用不同的文件系统

同一块磁盘的不同分区可以使用不同文件系统:

text 复制代码
分区 1:FAT32
分区 2:NTFS
分区 3:ext4

3.安装多个操作系统

例如:

text 复制代码
分区 1:Windows
分区 2:Linux
分区 3:共享数据

4.隔离空间和故障影响

某个分区空间耗尽,通常不会直接占用其他分区已经划定的空间.

但需要注意:

分区只能实现逻辑隔离,无法解决整块磁盘发生物理故障的问题.

核心理解

text 复制代码
扇区:磁盘寻址的基本单位
块:文件系统分配和读写的基本单位
分区:磁盘 LBA 空间中的一段连续区域
文件系统:在分区或卷中组织文件的数据结构

例如:

text 复制代码
磁盘总容量:1 TB
├─ 分区 1:系统分区
├─ 分区 2:软件分区
└─ 分区 3:数据分区

分区的主要作用,就是把一块磁盘划分成多个便于独立管理、格式化和使用的逻辑区域.

其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将

它分区成C,D,E盘.那个C,D,E就是分区.分区从实质上说就是对硬盘的⼀种格式化.但是Linux的设备都是以⽂件形式存在,那是怎么分区的呢?

柱⾯是分区的最⼩单位,我们可以利⽤参考柱⾯号码的⽅式来进⾏分区,其本质就是设置每个区的起始柱⾯和结束柱⾯号码.此时我们可以将硬盘上的柱⾯(分区)进⾏平铺,将其想象成⼀个⼤的平⾯,如下图所示:

📌 注意:

柱⾯⼤⼩⼀致,扇区个位⼀致,那么其实只要知道每个分区的起始和结束柱⾯号,知道每⼀个柱⾯多少个扇区,那么该分区多⼤,其实和解释LBA是多少也就清楚了.


2.3引⼊"inode"概念

分区和块解决了"磁盘空间如何划分、数据放在哪里"的问题,但文件系统还需要回答:

  • 一个文件有多大?
  • 属于谁?
  • 有什么权限?
  • 数据存在哪些磁盘块中?
  • 什么时候创建、修改?
  • 有多少个文件名指向它?

因此,Unix/Linux 文件系统引入了 inode(索引节点)

inode 是文件在文件系统中的核心元数据结构,用来描述文件属性,并记录文件数据所在的位置.

inode中保存什么

一个 inode 通常包含:

  • 文件类型:普通文件、目录、符号链接等
  • 文件权限:读、写、执行
  • 所有者 UID 和所属组 GID
  • 文件大小
  • 创建、修改、访问等时间
  • 硬链接计数
  • 指向数据块的地址或索引信息

可简化表示为:

text 复制代码
inode
├─ 文件类型
├─ 权限
├─ 所有者
├─ 文件大小
├─ 时间信息
├─ 链接数
└─ 数据块位置

inode不保存文件名

这是理解 inode 最关键的一点:

inode 通常不保存文件名,文件名保存在目录中.

目录本质上也是一种文件,其内容可以理解为一张映射表:

text 复制代码
文件名 → inode 编号

例如:

text 复制代码
目录 /home/user:

report.txt  → inode 1001
photo.jpg   → inode 1002
notes       → inode 1003

查找 /home/user/report.txt 时,文件系统大致执行:

text 复制代码
读取 /home/user 目录
        ↓
找到文件名 report.txt
        ↓
获得 inode 编号 1001
        ↓
读取 inode 1001
        ↓
找到文件的数据块
        ↓
读取文件内容

inode与数据块的关系

假设某个文件内容分散在多个块中:

text 复制代码
块 20、块 21、块 35

对应的 inode 可以理解为:

text 复制代码
inode 1001
├─ 文件大小:10 KiB
├─ 权限:rw-r--r--
├─ 所有者:用户 1000
└─ 数据位置:块 20、21、35

因此:

text 复制代码
文件名 → inode → 数据块

完整关系是:

text 复制代码
目录项
"report.txt"
      ↓
inode 1001
      ↓
数据块 20、21、35

现代文件系统可能使用 extent(区段) 来记录一段连续的数据块,而不是逐个保存块号.例如:

text 复制代码
从块 20 开始,连续使用 8 个块

inode编号

每个 inode 都有一个编号,称为 inode number

在 Linux 中可以使用:

bash 复制代码
ls -i

查看文件对应的 inode 编号:

text 复制代码
1001 report.txt
1002 photo.jpg

需要注意:

inode 编号只在当前文件系统内部唯一,不保证在整台计算机中全局唯一。

两个不同分区中的文件可能具有相同的 inode 编号.

核心理解

概念 作用
扇区 磁盘寻址的基本单位
文件系统分配存储空间的单位
分区 磁盘 LBA 空间中的连续区域
inode 描述文件属性,并定位数据块
目录项 建立文件名与 inode 编号的映射
文件内容 实际存储在数据块中

一句话概括:

文件名用于找到 inode,inode 用于找到数据块,数据块中保存真正的文件内容.

之前我们说过 ⽂件=数据+属性 ,我们使⽤ls -l的时候看到的除了看到⽂件名,还能看到⽂件元数据(属性).ls -l读取存储在磁盘上的⽂件信息,然后显示出来.

其实这个信息除了通过这种⽅式来读取,还有⼀个stat命令能够看到更多信息.

到这我们要思考⼀个问题,⽂件数据都储存在"块"中,那么很显然,我们还必须找到⼀个地⽅储存

⽂件的元信息(属性信息),⽐如⽂件的创建者、⽂件的创建⽇期、⽂件的⼤⼩等等.这种储存⽂件元信息的区域就叫做inode,中⽂译名为"索引节点".

每⼀个⽂件都有对应的inode,⾥⾯包含了与该⽂件有关的⼀些信息.为了能解释清楚inode,我们需要是深⼊了解⼀下⽂件系统.

📌 注意:

• Linux下⽂件的存储是属性和内容分离存储的.

• Linux下,保存⽂件属性的集合叫做inode,⼀个⽂件,⼀个inode,inode内有⼀个唯⼀的标识符,叫做inode号.

所以⼀个⽂件的属性inode⻓什么样⼦呢?

cpp 复制代码
/*
* Structure of an inode on the disk
*/
struct ext2_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks; /* Blocks count */
__le32 i_flags; /* File flags */
union {
struct {
__le32 l_i_reserved1;
} linux1;
struct {
__le32 h_i_translator;
} hurd1;
struct {
__le32 m_i_reserved1;
} masix1;
} osd1; /* OS dependent 1 */
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
__le32 i_generation; /* File version (for NFS) */
__le32 i_file_acl; /* File ACL */
__le32 i_dir_acl; /* Directory ACL */
__le32 i_faddr; /* Fragment address */
union {
struct {
__u8 l_i_frag; /* Fragment number */
__u8 l_i_fsize; /* Fragment size */
__u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; /* Fragment number */
__u8 h_i_fsize; /* Fragment size */
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; /* Fragment number */
__u8 m_i_fsize; /* Fragment size */
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; /* OS dependent 2 */
};
/*
* Constants relative to the data blocks
*/
#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)
备注:EXT2_N_BLOCKS = 15

📌 再次注意:

• ⽂件名属性并未纳⼊到inode数据结构内部.

• inode的⼤⼩⼀般是128字节或者256,我们后⾯统⼀128字节.

• 任何⽂件的内容⼤⼩可以不同,但是属性⼤⼩⼀定是相同的.


3.EXT2⽂件系统

3.1宏观认识

所有的准备⼯作都已经做完,是时候认识下⽂件系统了.我们想要在硬盘上储⽂件,必须先把硬盘格式化为某种格式的⽂件系统,才能存储⽂件.⽂件系统的⽬的就是组织和管理硬盘中的⽂件.在Linux 系统中,最常⻅的是ext2系列的⽂件系统.其早期版本为ext2,后来⼜发展出ext3和ext4.ext3和ext4虽然对ext2进⾏了增强,但是其核⼼设计并没有发⽣变化,我们仍是以较⽼的ext2 作为演示对象.ext2⽂件系统将整个分区划分成若⼲个同样⼤⼩的块组(Block Group),如下图所示.只要能管理⼀个分区就能管理所有分区,也就能管理所有磁盘⽂件.

上图中启动块(Boot Block/Sector)的⼤⼩是确定的,为1KB,由PC标准规定,⽤来存储磁盘分区信

息和启动信息,任何⽂件系统都不能修改启动块.启动块之后才是ext2⽂件系统的开始.


3.2Block Group

Block Group(块组) 是 ext2、ext3、ext4 等 Linux 文件系统中,对一个分区内部空间进行的进一步分组.

一个文件系统被划分成多个 Block Group,每个 Block Group 都包含一部分 inode 和数据块.

可以理解为:

text 复制代码
磁盘
  ↓
分区
  ↓
文件系统
  ↓
Block Group 0
Block Group 1
Block Group 2
...

为什么要引入 Block Group

如果整个文件系统只有一张巨大的 inode 表和空闲块表,会出现几个问题:

  • 文件系统越大,管理结构越庞大;
  • 查找空闲 inode 和数据块效率下降;
  • inode 与文件数据距离过远,机械硬盘寻道开销增加;
  • 某些关键元数据损坏时,整个文件系统风险较大.

因此,文件系统把空间切分成多个较小的管理区域.

Block Group 的核心目标是:局部管理、就近存储、提高性能、增强可靠性.

Block Group 的典型结构

一个 Block Group 通常包含:

text 复制代码
Block Group
├─ Superblock 副本
├─ Group Descriptor Table 副本
├─ Block Bitmap
├─ Inode Bitmap
├─ Inode Table
└─ Data Blocks

1.Superblock

超级块保存整个文件系统的全局信息,例如:

  • 文件系统总块数;
  • inode 总数;
  • 块大小;
  • 空闲块数量;
  • 文件系统状态;
  • 挂载信息。

主 Superblock 一般位于文件系统前部,部分 Block Group 中还会保存备份副本.

2.Group Descriptor Table

块组描述符表记录每个 Block Group 的信息,例如:

  • Block Bitmap 在哪里;
  • Inode Bitmap 在哪里;
  • Inode Table 在哪里;
  • 当前块组还有多少空闲块;
  • 当前块组还有多少空闲 inode.

它相当于各个 Block Group 的"目录".

3.Block Bitmap

Block Bitmap 用于记录该 Block Group 中哪些数据块已经被使用.

例如:

text 复制代码
1 1 0 0 1 0 0 1

可以理解为:

text 复制代码
1:该块已占用
0:该块空闲

文件系统分配磁盘空间时,会查询这个位图.

4.Inode Bitmap

Inode Bitmap 用于记录该 Block Group 中哪些 inode 已被使用.

例如:

text 复制代码
1 0 1 1 0 0

表示部分 inode 已分配,部分仍为空闲.

5.Inode Table

Inode Table 保存该 Block Group 中的 inode.

每个 inode 保存:

  • 文件类型
  • 权限
  • 所有者
  • 文件大小
  • 时间信息
  • 数据块位置
  • 硬链接计数

注意:

inode 中通常不保存文件名,文件名保存在目录项中.

6.Data Blocks

Data Blocks 保存实际内容,包括:

  • 普通文件内容
  • 目录项
  • 符号链接内容
  • 文件系统的其他数据结构.

文件与 Block Group 的关系

文件系统通常尽量把:

  • 文件的 inode
  • 文件的数据块
  • 文件所在目录的数据

放在相同或相近的 Block Group 中.

例如:

text 复制代码
Block Group 3
├─ inode 1001:report.txt
├─ inode 1002:photo.jpg
├─ 数据块 300~305:report.txt 内容
└─ 数据块 320~340:photo.jpg 内容

这样读取文件时,不需要频繁跨越很远的磁盘区域.

对于机械硬盘,这可以减少磁头移动;对于 SSD,也能减少元数据查找范围并提高管理效率.

inode号如何定位到 Block Group

假设:

text 复制代码
每个 Block Group 有 8192 个 inode
inode 编号为 20000

由于 inode 通常从 1 开始编号,可以计算:

text 复制代码
块组号 = (inode号 - 1) // 每组inode数

代入:

text 复制代码
块组号 = (20000 - 1) // 8192
       = 2

因此 inode 20000 位于:

text 复制代码
Block Group 2

它在该组内的索引为:

text 复制代码
组内索引 = (inode号 - 1) % 每组inode数
text 复制代码
组内索引 = 19999 % 8192
         = 3615

因此:

text 复制代码
inode 20000
→ Block Group 2
→ Inode Table 中第 3615 个位置

数据块如何定位到 Block Group

假设:

text 复制代码
每个 Block Group 有 32768 个块
目标块号为 70000

计算:

text 复制代码
块组号 = 70000 // 32768
       = 2

组内偏移:

text 复制代码
组内偏移 = 70000 % 32768
         = 4464

因此:

text 复制代码
块 70000
→ Block Group 2
→ 组内第 4464 个块

实际文件系统中还要考虑文件系统起始位置、保留块和元数据布局.

Block Group 的主要优点

提高局部性

inode 和数据块尽量放在附近,减少磁盘寻道和随机访问.

缩小管理范围

每个块组拥有自己的 bitmap 和 inode table,不必每次扫描整个文件系统.

支持大型文件系统

文件系统可以通过增加 Block Group 来扩展,而不是维护一个极其庞大的单一区域.

提高可靠性

部分关键元数据可以在多个 Block Group 中保存副本.主Superblock损坏时,可能使用备份恢复.

降低碎片

文件系统分配器可优先在同一 Block Group 中分配连续块,提高连续性.

Block Group 与前面概念的关系

text 复制代码
磁盘
  ↓
分区
  ↓
文件系统
  ↓
Block Group
  ├─ inode bitmap
  ├─ inode table
  ├─ block bitmap
  └─ data blocks

可以这样概括:

概念 作用
扇区 磁盘设备的寻址单位
文件系统分配空间的基本单位
分区 磁盘上的连续地址区域
文件系统 组织文件和目录的规则与数据结构
Block Group 文件系统内部的局部管理区域
inode 记录文件属性及数据位置
Data Block 保存实际文件内容

一句话理解:

Block Group 就是把一个大型文件系统拆成多个"小型文件系统管理区域",每个区域分别管理自己的 inode 和数据块.


3.3块组内部构成

(1)超级块(Super Block)

超级块是文件系统的"总说明书",保存整个文件系统的全局信息.

操作系统挂载文件系统时,首先读取超级块,确认文件系统的类型、大小、块结构和当前状态.

超级块描述的是整个文件系统,而不是某一个文件.

超级块通常保存什么

以 Linux 的 ext2、ext3、ext4 文件系统为例,超级块通常记录:

  • 文件系统总块数
  • 空闲块数量
  • inode 总数
  • 空闲 inode 数量
  • 文件系统块大小
  • 每个 Block Group 的块数
  • 每个 Block Group 的 inode 数
  • 文件系统 UUID
  • 文件系统状态
  • 最近挂载时间
  • 最近检查时间
  • 挂载次数
  • 文件系统版本及兼容特性
  • 是否正常卸载
  • 是否存在错误

可以简化表示为:

text 复制代码
Super Block
├─ 文件系统总容量
├─ 块大小
├─ 总块数与空闲块数
├─ inode 总数与空闲 inode 数
├─ 每组块数、每组 inode 数
├─ 文件系统 UUID
├─ 挂载与检查信息
└─ 文件系统状态和特性

超级块不保存什么

超级块通常不直接保存

  • 文件名
  • 文件内容
  • 单个文件的权限
  • 单个文件的数据块位置

这些信息分别保存在其他结构中:

text 复制代码
文件名          → 目录项
文件属性        → inode
文件内容        → Data Blocks
空闲块状态      → Block Bitmap
空闲 inode 状态 → Inode Bitmap

为什么要保存超级块副本

超级块包含文件系统的关键全局参数.如果主超级块损坏,操作系统可能无法正常识别或挂载文件系统.

因此,ext 文件系统会在部分Block Group 中保存备份副本:

text 复制代码
主超级块损坏
      ↓
寻找备用超级块
      ↓
读取文件系统参数
      ↓
尝试检查和修复文件系统

但超级块备份并不等同于完整数据备份.它只能帮助恢复文件系统结构信息,不能保证用户文件内容一定可恢复.

文件系统挂载时如何使用超级块

例如挂载一个 ext4 分区时:

text 复制代码
读取分区起始位置
        ↓
读取超级块
        ↓
检查文件系统类型和状态
        ↓
获取块大小、块组数量等参数
        ↓
定位 Group Descriptor Table
        ↓
读取根目录 inode
        ↓
建立文件系统访问入口

如果超级块内容无效,可能出现:

text 复制代码
wrong fs type
bad superblock
filesystem corrupted

超级块和 Group Descriptor 的区别

结构 管理范围 主要作用
Super Block 整个文件系统 保存全局参数
Group Descriptor 单个 Block Group 记录该组位图、inode 表等位置
Block Bitmap 单个块组 记录块是否被占用
Inode Bitmap 单个块组 记录 inode 是否被占用
Inode Table 单个块组 保存文件 inode
Data Blocks 文件系统数据区 保存目录和文件内容

可以理解为:

text 复制代码
Super Block
   ↓ 描述整个文件系统

Group Descriptor
   ↓ 描述各个 Block Group

inode
   ↓ 描述具体文件

Data Block
   ↓ 保存实际内容

查看ext 文件系统超级块

Linux 中可以使用:

bash 复制代码
sudo dumpe2fs /dev/sda1

只查看超级块概要:

bash 复制代码
sudo dumpe2fs -h /dev/sda1

也可以使用:

bash 复制代码
sudo tune2fs -l /dev/sda1

常见输出包括:

text 复制代码
Filesystem UUID
Filesystem state
Block count
Free blocks
Inode count
Free inodes
Block size
Blocks per group
Inodes per group
Mount count
Last mount time

操作前应确认设备名称,避免对错误分区执行维护命令.

查看备用超级块位置

可使用:

bash 复制代码
sudo mke2fs -n /dev/sda1

其中 -n 表示只模拟创建文件系统,不真正格式化,通常可以显示备用超级块的位置.

但必须谨慎:

不要遗漏 -n,否则错误使用格式化命令可能破坏原有文件系统.

核心理解

text 复制代码
磁盘
  ↓
分区
  ↓
文件系统
  ├─ Super Block:整个文件系统的全局信息
  ├─ Group Descriptor:各块组的位置和状态
  ├─ inode:文件属性和数据位置
  └─ Data Blocks:实际文件内容

一句话概括:

超级块负责说明"这个文件系统整体是什么样的",inode负责说明"某个文件是什么样的",数据块负责保存"文件的实际内容".

存放⽂件系统本⾝的结构信息,描述整个分区的⽂件系统信息.记录的信息主要有:bolck 和 inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息.Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了.

超级块在每个块组的开头都有⼀份拷⻉(第⼀个块组必须有,后⾯的块组可以没有).为了保证⽂件系统在磁盘部分扇区出现物理问题的情况下还能正常⼯作,就必须保证⽂件系统的superblock信息在这种情况下也能正常访问。所以⼀个⽂件系统的super block会在多个block group中进⾏备份,这些super block区域的数据保持⼀致.

cpp 复制代码
/*
* Structure of the super block
*/
struct ext2_super_block {
__le32 s_inodes_count; /* Inodes count */
__le32 s_blocks_count; /* Blocks count */
__le32 s_r_blocks_count; /* Reserved blocks count */
__le32 s_free_blocks_count; /* Free blocks count */
__le32 s_free_inodes_count; /* Free inodes count */
__le32 s_first_data_block; /* First Data Block */
__le32 s_log_block_size; /* Block size */
__le32 s_log_frag_size; /* Fragment size */
__le32 s_blocks_per_group; /* # Blocks per group */
__le32 s_frags_per_group; /* # Fragments per group */
__le32 s_inodes_per_group; /* # Inodes per group */
__le32 s_mtime; /* Mount time */
__le32 s_wtime; /* Write time */
__le16 s_mnt_count; /* Mount count */
__le16 s_max_mnt_count; /* Maximal mount count */
__le16 s_magic; /* Magic signature */
__le16 s_state; /* File system state */
__le16 s_errors; /* Behaviour when detecting errors */
__le16 s_minor_rev_level; /* minor revision level */
__le32 s_lastcheck; /* time of last check */
__le32 s_checkinterval; /* max. time between checks */
__le32 s_creator_os; /* OS */
__le32 s_rev_level; /* Revision level */
__le16 s_def_resuid; /* Default uid for reserved blocks */
__le16 s_def_resgid; /* Default gid for reserved blocks */
/*
* These fields are for EXT2_DYNAMIC_REV superblocks only.
*
* Note: the difference between the compatible feature set and
* the incompatible feature set is that if there is a bit set
* in the incompatible feature set that the kernel doesn't
* know about, it should refuse to mount the filesystem.
*
* e2fsck's requirements are more strict; if it doesn't know
* about a feature in either the compatible or incompatible
* feature set, it must abort and not try to meddle with
* things it doesn't understand...
*/
__le32 s_first_ino; /* First non-reserved inode */
__le16 s_inode_size; /* size of inode structure */
__le16 s_block_group_nr; /* block group # of this superblock */
__le32 s_feature_compat; /* compatible feature set */
__le32 s_feature_incompat; /* incompatible feature set */
__le32 s_feature_ro_compat; /* readonly-compatible feature set */
__u8 s_uuid[16]; /* 128-bit uuid for volume */
char s_volume_name[16]; /* volume name */
char s_last_mounted[64]; /* directory where last mounted */
__le32 s_algorithm_usage_bitmap; /* For compression */
/*
* Performance hints. Directory preallocation should only
* happen if the EXT2_COMPAT_PREALLOC flag is on.
*/
__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
__u16 s_padding1;
/*
* Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
*/
__u8 s_journal_uuid[16]; /* uuid of journal superblock */
__u32 s_journal_inum; /* inode number of journal file */
__u32 s_journal_dev; /* device number of journal file */
__u32 s_last_orphan; /* start of list of inodes to delete */
__u32 s_hash_seed[4]; /* HTREE hash seed */
__u8 s_def_hash_version; /* Default hash version to use */
__u8 s_reserved_char_pad;
__u16 s_reserved_word_pad;
__le32 s_default_mount_opts;
__le32 s_first_meta_bg; /* First metablock block group */
__u32 s_reserved[190]; /* Padding to the end of the block */
};

(2)GDT(Group Descriptor Table)

GDT,块组描述符表 ,用于记录文件系统中各个 Block Group(块组) 的管理信息.

超级块描述"整个文件系统",GDT 描述"每个块组".

可以把它理解为文件系统的块组索引目录

text 复制代码
Super Block
    ↓ 告诉系统文件系统有多少个块组

GDT
    ├─ Block Group 0 的描述符
    ├─ Block Group 1 的描述符
    ├─ Block Group 2 的描述符
    └─ ...

GDT中保存什么

GDT 由多个 Group Descriptor(块组描述符) 组成,一个块组对应一个描述符.

每个描述符通常记录:

  • 该块组的 Block Bitmap 位于哪个块
  • 该块组的 Inode Bitmap 位于哪个块
  • 该块组的 Inode Table 从哪个块开始
  • 该块组当前的空闲块数量
  • 该块组当前的空闲 inode 数量
  • 该块组中的目录数量
  • 校验和、标志位等扩展信息

简化表示:

text 复制代码
Group Descriptor
├─ Block Bitmap 地址
├─ Inode Bitmap 地址
├─ Inode Table 起始地址
├─ 空闲块数量
├─ 空闲 inode 数量
└─ 目录数量

为什么需要GDT

如果没有 GDT,操作系统想查找某个块组的 inode 表或位图,就需要逐块扫描磁盘.

有了 GDT 后,可以直接定位:

text 复制代码
块组号
   ↓
GDT 中对应的描述符
   ↓
Block Bitmap / Inode Bitmap / Inode Table

它主要解决三个问题:

快速定位元数据

直接找到某个块组中的位图和 inode 表.

统计块组状态

快速获得每个块组的空闲块、空闲 inode 和目录数量.

辅助空间分配

文件系统创建文件时,可以优先选择空闲 inode 和空闲块较多的块组.

GDT如何参与数据块分配

创建新文件时,文件系统可能执行:

text 复制代码
查看 GDT
   ↓
选择空闲 inode 和空闲块较多的 Block Group
   ↓
读取该组 Inode Bitmap
   ↓
分配 inode
   ↓
读取该组 Block Bitmap
   ↓
分配数据块
   ↓
更新位图和 Group Descriptor 统计信息

例如,分配了一个数据块后:

text 复制代码
Block Bitmap:对应位由 0 变为 1
Group Descriptor:空闲块数量减 1
Super Block:整个文件系统空闲块数量减 1

因此,这三类结构的信息需要保持一致:

text 复制代码
Super Block:全局统计
GDT:各块组统计
Bitmap:具体每一块或 inode 的占用情况

GDT 和超级块的区别

对比项 Super Block GDT
管理范围 整个文件系统 各个 Block Group
主要内容 块大小、总块数、inode 总数等 位图和 inode 表位置、组内空闲数量
数量 一个主超级块,可能有备份 每个块组对应一个描述符
作用 描述文件系统总体结构 定位并管理具体块组

可以概括为:

text 复制代码
Super Block
    ↓
文件系统整体参数

GDT
    ↓
每个 Block Group 的位置和状态

Bitmap
    ↓
具体哪些块或 inode 已使用

Inode Table
    ↓
具体文件的 inode

完整关系

text 复制代码
磁盘
  ↓
分区
  ↓
ext 文件系统
  ├─ Super Block
  │    └─ 描述整个文件系统
  │
  ├─ GDT
  │    ├─ Descriptor 0
  │    ├─ Descriptor 1
  │    └─ Descriptor 2
  │
  └─ Block Groups
       ├─ Block Bitmap
       ├─ Inode Bitmap
       ├─ Inode Table
       └─ Data Blocks

一句话概括:

GDT 是 Block Group 的索引表,通过它可以快速找到每个块组的位图、inode 表,并了解该块组的空间使用情况.

块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符.每个块组描述符存储⼀个块组的描述信息,如在这个块组中从哪⾥开始是inode Table,从哪⾥开始是Data

Blocks,空闲的inode和数据块还有多少个等等.块组描述符在每个块组的开头都有⼀份拷⻉.

cpp 复制代码
// 磁盘级blockgroup的数据结构
/*
* Structure of a blocks group descriptor
*/
struct ext2_group_desc
{
__le32 bg_block_bitmap; /* Blocks bitmap block */
__le32 bg_inode_bitmap; /* Inodes bitmap */
__le32 bg_inode_table; /* Inodes table block*/
__le16 bg_free_blocks_count; /* Free blocks count */
__le16 bg_free_inodes_count; /* Free inodes count */
__le16 bg_used_dirs_count; /* Directories count */
__le16 bg_pad;
__le32 bg_reserved[3];
};

(3)块位图(Block Bitmap)

块位图用于记录一个 Block Group 中,每个文件系统块是"已占用"还是"空闲".

通常每个数据块对应位图中的 1 个二进制位.

常见约定:

text 复制代码
0:空闲,可以分配
1:已占用,不能重复分配

基本结构

假设某个块组有 8 个块,其块位图为:

text 复制代码
块号:  0 1 2 3 4 5 6 7
位图:  1 1 0 1 0 0 1 0

表示:

text 复制代码
块 0:已占用
块 1:已占用
块 2:空闲
块 3:已占用
块 4:空闲
块 5:空闲
块 6:已占用
块 7:空闲

因此,文件系统可以把块 2、4、5、7 分配给新文件.

块位图管理哪些块

Block Bitmap 不仅记录普通文件的数据块,还会记录块组中所有已占用的文件系统块,包括:

  • Super Block 或其备份
  • GDT 或其备份
  • Block Bitmap 自身
  • Inode Bitmap
  • Inode Table
  • 目录数据块
  • 普通文件数据块
  • 日志或其他元数据块

因此,位图中的 1 不一定表示这个块保存了普通文件内容,也可能表示它被文件系统元数据占用.

为什么使用位图

如果逐个记录每个块的状态,会占用大量空间.位图只需一个 bit 就能表示一个块.

假设:

text 复制代码
块组中有 32768 个块

位图所需大小:

text 复制代码
32768 bit ÷ 8 = 4096 B = 4 KiB

所以一个 4 KiB 的 Block Bitmap,就可以描述 32768 个文件系统块.

如果每个文件系统块也是 4 KiB,那么它可以管理:

text 复制代码
32768 × 4 KiB = 128 MiB

这也是 ext 文件系统中常见块组规模形成的原因之一.

Block Bitmap 在哪里

每个 Block Group 通常有自己的 Block Bitmap:

text 复制代码
Block Group
├─ Super Block 备份(可选)
├─ GDT 备份(可选)
├─ Block Bitmap
├─ Inode Bitmap
├─ Inode Table
└─ Data Blocks

Block Bitmap 的实际块号记录在对应的 Group Descriptor 中.

访问过程:

text 复制代码
块组号
   ↓
查询 GDT 中对应的 Group Descriptor
   ↓
获得 Block Bitmap 所在块号
   ↓
读取块位图

分配数据块的过程

创建文件或扩大文件时,文件系统大致执行:

text 复制代码
选择合适的 Block Group
        ↓
查看 GDT 中该组的空闲块数量
        ↓
读取该组 Block Bitmap
        ↓
查找值为 0 的位
        ↓
将该位修改为 1
        ↓
把对应块分配给文件
        ↓
更新 GDT 和 Super Block 的空闲块数量

例如,原位图为:

text 复制代码
1 1 0 1 0 0 1 0

分配块 2 后:

text 复制代码
1 1 1 1 0 0 1 0

同时:

text 复制代码
当前块组空闲块数 - 1
整个文件系统空闲块数 - 1

释放数据块的过程

删除文件或截断文件时:

text 复制代码
找到文件使用的数据块
        ↓
将 Block Bitmap 中对应位由 1 改为 0
        ↓
更新块组空闲块计数
        ↓
更新文件系统全局空闲块计数

例如释放块 3:

text 复制代码
释放前:1 1 1 1 0 0 1 0
释放后:1 1 1 0 0 0 1 0

需要注意:

普通删除通常只是把块标记为空闲,不一定立即清除块中的原始数据.

之后这些块可能被其他文件重新覆盖.

如何由位图位置确定数据块

假设某块组的起始块号为:

text 复制代码
组起始块号 = 65536

目标位图索引为:

text 复制代码
索引 = 100

那么它通常对应块组中的第 100 个块:

text 复制代码
目标文件系统块号 = 65536 + 100
                   = 65636

不过实际实现中必须根据文件系统定义确认:

  • 块组起始块号
  • 首个数据块编号
  • 是否存在保留块
  • 位图索引的编号规则

如何找到位图中的某一位

假设要检查组内索引为 100 的块:

text 复制代码
字节偏移 = 100 // 8 = 12
位偏移   = 100 % 8  = 4

因此需要读取:

text 复制代码
Block Bitmap 的第 12 个字节中的第 4 位

一般公式:

text 复制代码
字节位置 = 组内块索引 // 8
位位置   = 组内块索引 % 8

需要注意实际磁盘格式中的 bit 顺序通常由文件系统规范决定,不能仅凭视觉上的二进制书写顺序判断.


与GDT、Super Block 的关系

三者保存的是不同粒度的信息:

结构 保存内容
Super Block 整个文件系统有多少空闲块
GDT 每个 Block Group 有多少空闲块、位图在哪里
Block Bitmap 具体哪些块空闲、哪些块已占用

例如:

text 复制代码
Super Block:
整个文件系统还有 100000 个空闲块

Group Descriptor 2:
Block Group 2 还有 5000 个空闲块
Block Bitmap 位于块 65537

Block Bitmap:
精确指出组内第 3、8、20......号块为空闲

这些信息必须保持一致,否则文件系统可能出现空间统计错误或块被重复分配。

完整关系

text 复制代码
文件系统
├─ Super Block
│    └─ 全局空闲块统计
├─ GDT
│    └─ 各块组位图位置和空闲块统计
└─ Block Group
     ├─ Block Bitmap
     │    └─ 逐块记录占用状态
     ├─ Inode Bitmap
     ├─ Inode Table
     └─ Data Blocks

一句话概括:

GDT 告诉系统块位图在哪里、还有多少空闲块;Block Bitmap 则精确告诉系统究竟是哪几个块空闲.


(4)inode位图(Inode Bitmap)

inode 位图用于记录一个 Block Group 中,每个 inode 是"已分配"还是"空闲".

通常一个 inode 对应位图中的 1 个二进制位.

常见约定:

text 复制代码
0:空闲,可以分配
1:已使用,不能重复分配

基本结构

假设某个块组有 8 个 inode:

text 复制代码
组内索引: 0 1 2 3 4 5 6 7
位图:     1 1 0 1 0 0 1 0

表示:

text 复制代码
inode 0:已使用
inode 1:已使用
inode 2:空闲
inode 3:已使用
inode 4:空闲
inode 5:空闲
inode 6:已使用
inode 7:空闲

创建新文件时,文件系统可以选择值为 0 的位置,例如组内 inode 索引 2.

inode位图记录什么

它只记录:

text 复制代码
这个 inode 是否已经被分配

它不记录:

  • 文件名
  • 文件权限
  • 文件大小
  • 数据块地址
  • 文件内容

这些信息分别保存在:

信息 保存位置
inode 是否被使用 Inode Bitmap
文件元数据 Inode Table
文件名与 inode 号的映射 目录项
文件内容 Data Blocks

因此:

text 复制代码
Inode Bitmap
    ↓ 判断 inode 是否可用

Inode Table
    ↓ 读取 inode 的具体内容

inode位图在哪里

每个 Block Group 通常都有自己的 inode 位图:

text 复制代码
Block Group
├─ Super Block 备份(可选)
├─ GDT 备份(可选)
├─ Block Bitmap
├─ Inode Bitmap
├─ Inode Table
└─ Data Blocks

inode 位图所在的块号,记录在对应的 Group Descriptor 中.

定位过程:

text 复制代码
Block Group 编号
        ↓
查询 GDT 中对应的 Group Descriptor
        ↓
获得 Inode Bitmap 的块地址
        ↓
读取 inode 位图

创建文件时如何使用 inode 位图

创建一个新文件时,文件系统大致执行:

text 复制代码
选择合适的 Block Group
        ↓
查看 GDT 中该组的空闲 inode 数量
        ↓
读取该组 Inode Bitmap
        ↓
查找值为 0 的位
        ↓
将该位改为 1
        ↓
初始化对应的 inode
        ↓
在目录中添加"文件名 → inode号"
        ↓
更新 GDT 和 Super Block

例如原位图:

text 复制代码
1 1 0 1 0 0 1 0

分配组内索引 2 后:

text 复制代码
1 1 1 1 0 0 1 0

同时:

text 复制代码
该块组空闲 inode 数量 - 1
文件系统全局空闲 inode 数量 - 1

inode号如何计算

假设:

text 复制代码
每个 Block Group 有 8192 个 inode
目标位图索引为 100
当前块组编号为 2

如果 inode 号从 1 开始,则:

text 复制代码
inode号
= 块组号 × 每组inode数 + 组内索引 + 1

代入:

text 复制代码
inode号
= 2 × 8192 + 100 + 1
= 16485

因此:

text 复制代码
Block Group 2 中组内索引 100
对应 inode 16485

反向计算:

text 复制代码
块组号 = (inode号 - 1) // 每组inode数

组内索引 = (inode号 - 1) % 每组inode数

如何找到位图中的某一位

假设要检查组内索引为 100 的 inode:

text 复制代码
字节偏移 = 100 // 8 = 12
位偏移   = 100 % 8  = 4

因此,需要检查:

text 复制代码
Inode Bitmap 第 12 个字节中的第 4 位

通用公式:

text 复制代码
字节位置 = 组内 inode 索引 // 8
位位置   = 组内 inode 索引 % 8

实际字节中的 bit 顺序要以文件系统格式规范为准.

删除文件时如何释放 inode

执行删除操作时,并不一定立即释放 inode.

文件真正释放通常需要满足:

text 复制代码
硬链接计数为 0
并且
没有进程继续打开该文件

之后文件系统才会:

text 复制代码
释放文件占用的数据块
        ↓
Block Bitmap 对应位由 1 改为 0
        ↓
清理或标记 inode
        ↓
Inode Bitmap 对应位由 1 改为 0
        ↓
更新 GDT 和 Super Block 统计

例如:

text 复制代码
释放前:1 1 1 1 0 0 1 0
释放后:1 1 0 1 0 0 1 0

空文件也需要 inode

即使文件大小为 0

bash 复制代码
touch empty.txt

它仍然需要:

  • 一个目录项;
  • 一个 inode;
  • inode 位图中的一个已使用标记.

但空文件通常暂时不需要普通数据块.

因此可能出现:

text 复制代码
Inode Bitmap:发生变化
Block Bitmap:可能不变

inode 耗尽问题

文件系统的 inode 数量通常在创建文件系统时确定.

如果存在大量小文件,可能出现:

text 复制代码
磁盘容量还有很多
但空闲 inode 已经为 0

此时无法继续创建新文件.

查看容量:

bash 复制代码
df -h

查看 inode 使用情况:

bash 复制代码
df -i

典型现象:

text 复制代码
磁盘空间未满
inode 使用率 100%

这说明问题不是缺少数据块,而是 inode 已耗尽.

与 GDT、Super Block 的关系

结构 作用
Super Block 记录整个文件系统的空闲 inode 总数
GDT 记录每个块组的空闲 inode 数和位图位置
Inode Bitmap 精确记录具体哪些 inode 已使用
Inode Table 保存每个 inode 的详细元数据

例如:

text 复制代码
Super Block:
整个文件系统还有 50000 个空闲 inode

Group Descriptor 2:
Block Group 2 还有 4000 个空闲 inode
Inode Bitmap 位于块 65538

Inode Bitmap:
具体指出组内哪些 inode 空闲

与 Block Bitmap 的区别

对比项 Inode Bitmap Block Bitmap
管理对象 inode 文件系统块
每一位代表 一个 inode 一个块
创建空文件时 一般会变化 可能不变化
文件写入数据时 通常不再变化 会分配数据块
文件删除后 inode 被释放时变为 0 数据块被释放时变为 0

完整关系

text 复制代码
创建文件
   ↓
Inode Bitmap:分配 inode
   ↓
Inode Table:写入文件元数据
   ↓
目录数据块:记录文件名和 inode 号
   ↓
Block Bitmap:按需分配数据块
   ↓
Data Blocks:保存文件内容

一句话概括:

GDT 告诉系统 inode 位图在哪里、还有多少空闲 inode;Inode Bitmap 则精确指出哪些 inode 可以使用.


(5)i节点表(Inode Table)

Inode Table 是一个 Block Group 中所有 inode 的连续存储区域.

Inode Bitmap 只记录"某个 inode 是否已使用",Inode Table 则保存这个 inode 的具体内容.

整体关系:

text 复制代码
Inode Bitmap
    ↓ 判断 inode 是否已分配

Inode Table
    ↓ 保存 inode 的详细元数据

Inode Table 中保存什么

Inode Table 由一系列固定大小的 inode 结构组成:

text 复制代码
Inode Table
├─ inode 0
├─ inode 1
├─ inode 2
├─ inode 3
└─ ...

每个 inode 通常保存:

  • 文件类型
  • 文件权限
  • 所有者 UID
  • 所属组 GID
  • 文件大小
  • 硬链接数量
  • 访问时间
  • 修改时间
  • inode 状态改变时间
  • 删除时间等信息
  • 数据块或 extent 的位置
  • 文件标志和扩展属性信息

简化表示:

text 复制代码
inode
├─ 文件类型与权限
├─ UID / GID
├─ 文件大小
├─ 时间信息
├─ 硬链接数
└─ 数据块位置

Inode Table 不保存文件名

这是最关键的一点:

inode 通常不保存文件名.

文件名保存在目录的数据块中,目录项记录:

text 复制代码
文件名 → inode 编号

例如:

text 复制代码
report.txt → inode 1001

访问过程:

text 复制代码
文件名
  ↓
目录项
  ↓
inode 编号
  ↓
Inode Table
  ↓
找到对应 inode
  ↓
找到数据块

Inode Table 在哪里

每个 Block Group 通常都有自己的 Inode Table:

text 复制代码
Block Group
├─ Block Bitmap
├─ Inode Bitmap
├─ Inode Table
└─ Data Blocks

对应的 Group Descriptor 中记录:

text 复制代码
该块组 Inode Table 的起始块号

定位过程:

text 复制代码
inode 编号
   ↓
计算所在 Block Group
   ↓
查询 GDT
   ↓
获得 Inode Table 起始块
   ↓
计算 inode 在表中的具体位置

如何定位一个 inode

假设:

text 复制代码
每个 Block Group 有 8192 个 inode
目标 inode 号为 20000

1.计算所在块组

inode 编号通常从 1 开始:

text 复制代码
块组号 = (inode号 - 1) // 每组inode数

代入:

text 复制代码
块组号 = (20000 - 1) // 8192
       = 2

因此 inode 20000 位于:

text 复制代码
Block Group 2

2.计算组内索引

text 复制代码
组内索引 = (inode号 - 1) % 每组inode数

代入:

text 复制代码
组内索引 = 19999 % 8192
         = 3615

因此:

text 复制代码
inode 20000
→ Block Group 2
→ Inode Table 中第 3615 个 inode

计算inode 所在磁盘块

假设:

text 复制代码
文件系统块大小 = 4096 B
inode 大小 = 256 B

每个块可以容纳:

text 复制代码
4096 ÷ 256 = 16 个 inode

目标 inode 的组内索引为 3615.

它位于 Inode Table 中的块偏移:

text 复制代码
块偏移 = 3615 // 16
       = 225

在该块中的 inode 索引:

text 复制代码
块内索引 = 3615 % 16
         = 15

如果 GDT 记录该组 Inode Table 从块 70000 开始,那么:

text 复制代码
目标 inode 所在块
= 70000 + 225
= 70225

块内字节偏移:

text 复制代码
15 × 256 = 3840 B

最终位置:

text 复制代码
文件系统块 70225
块内偏移 3840 B

inode 如何定位文件数据

传统 ext2/ext3 inode 中会保存:

  • 直接块指针
  • 一级间接块指针
  • 二级间接块指针
  • 三级间接块指针

可以简化为:

text 复制代码
inode
├─ 直接指针 → 数据块
├─ 一级间接 → 指针块 → 数据块
├─ 二级间接 → 指针块 → 指针块 → 数据块
└─ 三级间接 → 多级指针 → 数据块

ext4 通常更多使用 extent

text 复制代码
逻辑块 0 开始
映射到物理块 10000
连续 128 个块

相比逐块记录,extent 更适合大文件和连续空间.

创建文件时 Inode Table 如何变化

创建文件时,大致执行:

text 复制代码
在 Inode Bitmap 中找空闲 inode
        ↓
将对应位由 0 改为 1
        ↓
在 Inode Table 中初始化该 inode
        ↓
写入权限、UID、大小、时间等信息
        ↓
在目录中建立"文件名 → inode号"
        ↓
按需分配数据块

例如创建空文件:

bash 复制代码
touch test.txt

通常会发生:

text 复制代码
Inode Bitmap:分配一个 inode
Inode Table:写入 inode 元数据
目录数据块:增加目录项
Block Bitmap:文件本身可能暂时不分配数据块

删除文件时 Inode Table 如何变化

当文件满足以下条件时:

text 复制代码
硬链接计数为 0
并且没有进程继续打开

文件系统才会真正释放:

text 复制代码
释放数据块
    ↓
Block Bitmap 对应位变为 0
    ↓
释放 inode
    ↓
Inode Bitmap 对应位变为 0
    ↓
Inode Table 中的 inode 可被重新使用

需要注意:

inode 被释放后,原有内容不一定立即全部清零,但该 inode 已经可以重新分配.

Inode Table 与其他结构的区别

结构 作用
Super Block 保存整个文件系统的全局信息
GDT 记录各 Block Group 的元数据位置
Inode Bitmap 记录哪些 inode 已使用
Inode Table 保存每个 inode 的详细内容
Block Bitmap 记录哪些块已使用
Data Blocks 保存文件和目录的实际内容

完整关系

text 复制代码
文件名
  ↓
目录项
  ↓
inode 编号
  ↓
计算 Block Group
  ↓
查询 GDT
  ↓
定位 Inode Table
  ↓
读取 inode
  ↓
获取数据块或 extent
  ↓
读取文件内容

一句话概括:

Inode Bitmap 负责说明 inode 是否可用,Inode Table 负责保存 inode 的具体信息,inode 再负责定位文件的数据块.


(6)Data Block

Data Block(数据块)是文件系统中保存实际内容的区域.

inode 保存"文件的属性和数据位置",Data Block 保存"文件真正的数据".

基本关系:

text 复制代码
文件名
  ↓
目录项
  ↓
inode
  ↓
Data Block
  ↓
文件实际内容

Data Block 中保存什么

不同类型的文件,其 Data Block 中保存的内容不同.

普通文件

保存文件的实际字节内容,例如:

text 复制代码
hello.txt
  ↓
Data Block
  ↓
Hello, Linux!

图片、视频、程序、数据库文件等,最终都以字节形式存放在数据块中.

目录

目录本身也是一种文件,其数据块保存的是目录项

text 复制代码
文件名 → inode 编号

例如:

text 复制代码
.          → inode 100
..         → inode 2
test.txt   → inode 101
photo.jpg  → inode 102

因此,目录的数据块并不保存目录下文件的实际内容,而是保存名称和 inode 的对应关系.

符号链接

较短的符号链接目标路径,可能直接保存在 inode 内部;路径较长时,通常会使用 Data Block 保存.

例如:

text 复制代码
link.txt → /home/user/report.txt

数据块中可能保存:

text 复制代码
/home/user/report.txt

Data Block 在文件系统中的位置

一个 Block Group 的典型结构为:

text 复制代码
Block Group
├─ Super Block 备份(可选)
├─ GDT 备份(可选)
├─ Block Bitmap
├─ Inode Bitmap
├─ Inode Table
└─ Data Blocks

其中 Data Blocks 通常占据绝大部分空间,用来保存:

  • 普通文件内容
  • 目录项
  • 符号链接内容
  • 间接索引块
  • extent 索引节点
  • 部分文件系统元数据

Data Block 与文件系统块

在 ext 文件系统中,Data Block 的大小就是文件系统的块大小,常见为:

text 复制代码
1 KiB
2 KiB
4 KiB

例如,文件系统块大小为 4 KiB

text 复制代码
1 个 Data Block = 4096 字节

一个大小为 10 KiB 的文件,至少需要:

text 复制代码
向上取整(10 KiB ÷ 4 KiB) = 3 个数据块

实际分配空间:

text 复制代码
3 × 4 KiB = 12 KiB

最后一个块只使用了 2 KiB,剩余空间通常形成内部碎片.

Data Block 与扇区的关系

假设:

text 复制代码
逻辑扇区大小 = 512 B
文件系统块大小 = 4 KiB

那么:

text 复制代码
1 个 Data Block = 4096 ÷ 512 = 8 个连续扇区

例如文件系统块号 1000,若文件系统从某个固定 LBA 开始,则它会映射到一组连续的磁盘扇区.

因此可以理解为:

text 复制代码
扇区:磁盘设备的寻址单位
块:文件系统的分配单位
Data Block:用于保存数据的文件系统块

inode 如何找到 Data Block

inode 不直接保存文件内容,而是保存数据块的位置.

传统 ext2/ext3 inode 使用:

text 复制代码
inode
├─ 直接块指针
├─ 一级间接块指针
├─ 二级间接块指针
└─ 三级间接块指针

直接块指针

inode 直接记录数据块号:

text 复制代码
inode
├─ 指针 1 → Data Block 100
├─ 指针 2 → Data Block 101
└─ 指针 3 → Data Block 205

适合较小文件.

间接块指针

当文件较大时,一个块专门用来保存更多数据块地址:

text 复制代码
inode
   ↓
间接索引块
   ├─ Data Block 500
   ├─ Data Block 501
   └─ Data Block 800

这里的间接索引块本身也占用一个文件系统块,但它保存的是地址,不是普通文件内容.

ext4 中的 extent

ext4 通常使用 extent(区段) 描述连续的数据块.

例如:

text 复制代码
文件逻辑块 0
映射到物理块 10000
连续 128 个块

可以表示为:

text 复制代码
逻辑块 0~127
    ↓
物理块 10000~10127

相比逐个记录块号,extent 的优势是:

  • 减少元数据量
  • 更适合大文件
  • 更容易保存连续数据
  • 降低碎片和索引开销

文件逻辑块与物理块

文件内部通常按逻辑块编号:

text 复制代码
文件逻辑块 0
文件逻辑块 1
文件逻辑块 2

这些逻辑块不一定映射到连续的物理数据块.

例如:

text 复制代码
文件逻辑块 0 → 文件系统块 1000
文件逻辑块 1 → 文件系统块 1001
文件逻辑块 2 → 文件系统块 3500

虽然用户看到的是一个连续文件,但其数据在磁盘上可能是分散的.

这种现象称为:

文件碎片

创建和写入文件时如何分配 Data Block

创建空文件时:

bash 复制代码
touch test.txt

通常只需要:

  • 分配 inode
  • 创建目录项

此时文件可能还没有数据块.

写入数据后:

text 复制代码
写入文件内容
    ↓
查找合适的 Block Group
    ↓
读取 Block Bitmap
    ↓
查找空闲块
    ↓
将对应位从 0 改为 1
    ↓
把数据写入 Data Block
    ↓
在 inode 中记录块地址
    ↓
更新文件大小和时间

例如原块位图:

text 复制代码
1 1 0 0 1 0

分配第 2 个块后:

text 复制代码
1 1 1 0 1 0

删除文件时 Data Block 如何释放

文件真正删除后,文件系统通常会:

text 复制代码
从目录中删除目录项
        ↓
硬链接计数减 1
        ↓
链接计数为 0 且无进程打开
        ↓
释放文件的数据块
        ↓
Block Bitmap 对应位由 1 改为 0
        ↓
释放 inode

需要注意:

释放 Data Block 通常只是把它标记为空闲,不一定立即把原始内容擦除。

之后该数据块可能被新文件重新分配和覆盖.

稀疏文件

某些文件的逻辑大小很大,但并不是所有区域都真正分配了 Data Block,这种文件称为:

稀疏文件(Sparse File)

例如文件逻辑大小为 1 GiB,但只写入了少量数据:

text 复制代码
文件逻辑块 0      → 已分配
文件逻辑块 1~999 → 未分配,读取时返回 0
文件逻辑块 1000   → 已分配

因此可能出现:

text 复制代码
文件显示大小:1 GiB
实际占用空间:几十 KiB

可以使用:

bash 复制代码
ls -lh file
du -h file

分别查看逻辑大小和实际占用空间.

Data Block 与 Block Bitmap 的区别

结构 作用
Block Bitmap 记录数据块是否被占用
Data Block 保存实际数据
inode 记录文件属性及数据块位置
Inode Bitmap 记录 inode 是否已分配
Inode Table 保存 inode 的具体内容

例如:

text 复制代码
Block Bitmap
   ↓
块 100 已占用

inode
   ↓
文件使用块 100

Data Block 100
   ↓
保存文件实际内容

一个文件的完整访问过程

访问 /home/user/test.txt 时:

text 复制代码
解析路径 /home/user
        ↓
读取目录 Data Block
        ↓
找到 test.txt 对应的 inode 编号
        ↓
从 Inode Table 读取 inode
        ↓
获得 extent 或数据块地址
        ↓
读取相应 Data Block
        ↓
返回文件内容

核心理解

text 复制代码
文件名        → 保存在目录项中
文件属性      → 保存在 inode 中
数据块位置    → 保存在 inode 或 extent 中
文件实际内容  → 保存在 Data Block 中
块占用状态    → 保存在 Block Bitmap 中

一句话概括:

inode 告诉操作系统文件的数据在哪里,Data Block 则真正保存文件、目录或链接的内容.


3.4inode和datablock映射(弱化)

• inode内部存在 __le32 i_blockEXT2_N_BLOCKS;/* Pointers to blocks */ ,EXT2_N_BLOCKS =15,就是⽤来进⾏inode和block映射的

• 这样⽂件=内容+属性,就都能找到了.

cpp 复制代码
/*
* Structure of an inode on the disk
*/
struct ext2_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks; /* Blocks count */
__le32 i_flags; /* File flags */
union {
struct {
__le32 l_i_reserved1;
} linux1;
struct {
__le32 h_i_translator;
} hurd1;
struct {
__le32 m_i_reserved1;
} masix1;
} osd1; /* OS dependent 1 */
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
__le32 i_generation; /* File version (for NFS) */
__le32 i_file_acl; /* File ACL */
__le32 i_dir_acl; /* Directory ACL */
__le32 i_faddr; /* Fragment address */
union {
struct {
__u8 l_i_frag; /* Fragment number */
__u8 l_i_fsize; /* Fragment size */
__u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; /* Fragment number */
__u8 h_i_fsize; /* Fragment size */
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; /* Fragment number */
__u8 m_i_fsize; /* Fragment size */
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; /* OS dependent 2 */
};
#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)
//inode 的⼤⼩通常是 128 字节 或 256 字节

📌 思考:

请解释:知道inode号的情况下,在指定分区.

请解释:对⽂件进⾏增、删、查、改是在做什么?

💡 结论:

• 分区之后的格式化操作,就是对分区进⾏分组,在每个分组中写⼊SB、GDT、Block

Bitmap、Inode Bitmap等管理信息,这些管理信息统称: ⽂件系统.

• 只要知道⽂件的inode号,就能在指定分区中确定是哪⼀个分组,进⽽在哪⼀个分组确定是哪⼀个inode.

• 拿到inode⽂件属性和内容就全部都有了.

为了说明问题,我们将上图简化:

创建⼀个新⽂件主要有以下4个操作:

  1. 存储属性
    内核先找到⼀个空闲的i节点.内核把⽂件信息记录到其中.
  2. 存储数据
    该⽂件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800.将内核缓冲区的第⼀块
    数据复制到300,下⼀块复制到500,以此类推.
  3. 记录分配情况
    ⽂件内容按顺序300,500,800存放.内核在inode上的磁盘分布区记录了上述块列表.
  4. 添加⽂件名到⽬录
    新的⽂件名abc.linux如何在当前的⽬录中记录这个⽂件?内核将⼊口添加到⽬录⽂件.⽂件名和inode之间的对应关系将⽂件名和⽂件的内容及属性连接起来.

3.5⽬录与⽂件名

问题:

• 我们访问⽂件,都是⽤的⽂件名,没⽤过inode号啊?

• ⽬录是⽂件吗?如何理解?

答案:

• ⽬录也是⽂件,但是磁盘上没有⽬录的概念,只有⽂件属性+⽂件内容的概念.

• ⽬录的属性不⽤多说,内容保存的是:⽂件名和Inode号的映射关系.

cpp 复制代码
// 验证说明代码
// readdir.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
exit(EXIT_FAILURE);
}
DIR *dir = opendir(argv[1]); // 系统调⽤,⾃⾏查阅
if (!dir) {
perror("opendir");
exit(EXIT_FAILURE);
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) { // 系统调⽤,⾃⾏查阅
// Skip the "." and ".." directory entries
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..")
== 0) {
continue;
}
printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned
long)entry->d_ino);
}
closedir(dir);
return 0;
}

所以,访问⽂件,必须打开当前⽬录,根据⽂件名,获得对应的inode号,然后进⾏⽂件访问.访问⽂件必须要知道当前⼯作⽬录,本质是必须能打开当前⼯作⽬录⽂件,查看⽬录⽂件的内容!⽐如:要访问test.c,就必须打开test(当前⼯作⽬录),然后才能获取test.c对应的inode进⽽对⽂件进⾏访问.


3.6路径解析

问题:打开当前⼯作⽬录⽂件,查看当前⼯作⽬录⽂件的内容?当前⼯作⽬录不也是⽂件吗?我们访问当前⼯作⽬录不也是只知道当前⼯作⽬录的⽂件名吗?要访问它,不也得知道当前⼯作⽬录的inode吗?

实际上,任何⽂件,都有路径,访问⽬标⽂件,⽐如:/home/code/test/test/test.c都要从根⽬录开始,依次打开每⼀个⽬录,根据⽬录名,依次访问每个⽬录下指定的⽬录,直到访问到test.c.这个过程叫做Linux路径解析.

💡 注意:

• 所以,我们知道了:访问⽂件必须要有⽬录+⽂件名=路径的原因.

• 根⽬录固定⽂件名,inode号,⽆需查找,系统开机之后就必须知道.

可是路径谁提供?

• 你访问⽂件,都是指令/⼯具访问,本质是进程访问,进程有CWD!进程提供路径.

• 你open⽂件,提供了路径.

可是最开始的路径从哪⾥来?所以Linux为什么要有根⽬录, 根⽬录下为什么要有那么多缺省⽬录?你为什么要有家⽬录,你⾃⼰可以新建⽬录?

• 上⾯所有⾏为:本质就是在磁盘⽂件系统中,新建⽬录⽂件.⽽你新建的任何⽂件,都在你或者系统指定的⽬录下新建,这不就是天然就有路径了嘛!

• 系统+⽤⼾共同构建Linux路径结构.


3.7路径缓存

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

答案:不存在,只有⽂件.只保存⽂件属性+⽂件内容.

问题2:访问任何⽂件,都要从/⽬录开始进⾏路径解析?

答案:原则上是,但是这样太慢,所以Linux会缓存历史路径结构.

问题2:Linux⽬录的概念,怎么产⽣的?

答案:打开的⽂件是⽬录的话,由操作系统⾃⼰在内存中进⾏路径维护.

Linux中,在内核中维护树状路径结构的内核结构体叫做: struct dentry

cpp 复制代码
struct dentry {
atomic_t d_count;
unsigned int d_flags; /* protected by d_lock */
spinlock_t d_lock; /* per dentry lock */
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
/*
* The next three fields are touched by __d_lookup. Place them here
* so they all fit in a cache line.
*/
struct hlist_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct list_head d_lru; /* LRU list */
/*
* d_child and d_rcu can share memory
*/
union {
struct list_head d_child; /* child of parent list */
struct rcu_head d_rcu;
} d_u;
struct list_head d_subdirs; /* our children */
struct list_head d_alias; /* inode alias list */
unsigned long d_time; /* used by d_revalidate */
struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
void *d_fsdata; /* fs-specific data */
#ifdef CONFIG_PROFILING
struct dcookie_struct *d_cookie; /* cookie, if any */
#endif
int d_mounted;
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};

注意:

• 每个⽂件其实都要有对应的dentry结构,包括普通⽂件.这样所有被打开的⽂件,就可以在内存中形成整个树形结构.

• 整个树形节点也同时会⾪属于LRU(Least Recently Used,最近最少使⽤)结构中,进⾏节点淘汰.

• 整个树形节点也同时会⾪属于Hash,⽅便快速查找.

• 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何⽂件,都在先在这棵树下根据路径进⾏查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径.


3.8挂载分区

我们已经能够根据inode号在指定分区找⽂件了,也已经能根据⽬录⽂件内容,找指定的inode了,在指定的分区内,我们可以为所欲为了.可是:inode不是不能跨分区吗?Linux不是可以有多个分区吗?我怎么知道我在哪⼀个分区?下面通过一个实验进行说明.


(1)⼀个实验

bash 复制代码
dd if=/dev/zero of=./disk.img bs=1M count=5 #制作⼀个⼤的磁盘块,就当做⼀个分区
mkfs.ext4 disk.img #格式化写⼊⽂件系统
bash 复制代码
mkdir /mnt/mydisk #建⽴空⽬录
df -h #查看可以使⽤的分区
bash 复制代码
sudo mount -t ext4 ./disk.img /mnt/mydisk/ #将分区挂载到指定的⽬录
df -h
bash 复制代码
sudo umount /mnt/mydisk #卸载分区
df -h

📌注意:

/dev/loop0 在Linux系统中代表第⼀个循环设备(loop device).循环设备,也被称为回环设备或者loopback设备,是⼀种伪设备(pseudo-device),它允许将⽂件作为块设备(block device)来使⽤.这种机制使得可以将⽂件(⽐如ISO镜像⽂件)挂载(mount)为⽂件系统,就像它们是物理硬盘分区或者外部存储设备⼀样.

bash 复制代码
ls /dev/loop* -l

(2)⼀个结论

Linux 不是靠 inode 号单独定位文件,而是靠:

文件系统(或挂载实例) + inode 号

inode 号只保证在同一个文件系统内部唯一 .Linux 通过 VFS 的挂载树知道当前路径属于哪个文件系统;路径解析走到挂载点时,会自动切换到另一个文件系统.

1.Linux 没有 C 盘、D 盘,而是一棵统一目录树

假设:

text 复制代码
/dev/nvme0n1p2  挂载到 /
/dev/nvme0n1p3  挂载到 /home
/dev/sdb1       挂载到 /data

用户看到的是:

text 复制代码
/
├── etc
├── usr
├── home        ← 另一个文件系统挂载在这里
│   └── user
└── data        ← 又一个文件系统挂载在这里

虽然看起来是一棵连续的目录树,实际可能来自多个分区或文件系统.

text 复制代码
/etc/passwd       → / 所在文件系统
/home/user/a.txt  → /home 所在文件系统
/data/b.txt       → /data 所在文件系统

2.inode 号为什么不能单独定位文件

假设:

text 复制代码
根文件系统:
inode 100 → /etc/test.txt

/home 文件系统:
inode 100 → /home/user/a.txt

两个文件的 inode 号都可以是 100,但并不冲突,因为它们属于不同文件系统.

真正的文件身份可以理解为:

text 复制代码
(文件系统标识, inode号)

在用户空间通常表现为:

text 复制代码
(st_dev, st_ino)

其中:

  • st_dev:文件所在文件系统对应的设备标识
  • st_ino:inode 号

因此:

text 复制代码
设备 A + inode 100

和:

text 复制代码
设备 B + inode 100

是两个不同文件.

3.内核怎么知道当前位于哪个文件系统

Linux 在解析路径时,不只是保存 dentry,还需要保存它属于哪个挂载实例.

内核中可简化理解为:

c 复制代码
struct path {
    struct vfsmount *mnt;   // 属于哪个挂载文件系统
    struct dentry   *dentry; // 当前目录项
};

也就是:

路径位置 = 挂载点信息 + dentry

其中:

  • dentry 负责文件名与 inode 的联系
  • vfsmount 负责说明当前 dentry 属于哪个挂载的文件系统

inode 本身也会关联超级块:

text 复制代码
inode
  ├─ i_ino:inode号
  └─ i_sb:所属文件系统的超级块

所以内核看到的不是一个孤立的 inode 号,而是类似:

text 复制代码
Super Block A → inode 100
Super Block B → inode 100

4.路径跨越分区时发生什么

假设 /home 是单独分区:

text 复制代码
/                 根文件系统
└── home          根文件系统里的挂载点目录
      ↓ 挂载
/home             新文件系统的根目录
└── user
    └── a.txt

解析:

text 复制代码
/home/user/a.txt

大致过程是:

text 复制代码
从根文件系统的 / 开始
        ↓
查找目录项 home
        ↓
发现 /home 是挂载点
        ↓
切换到 /home 对应文件系统的根目录
        ↓
在新文件系统中查找 user
        ↓
找到 a.txt 对应的 inode

这里最关键的是:

走到挂载点时,VFS 自动从原文件系统切换到被挂载文件系统.

因此,应用程序通常不需要自己判断是否跨分区.

5.挂载后,原来的目录发生了什么

在挂载之前,根文件系统中本来存在一个 /home 目录,它也有自己的 inode.

当另一个文件系统挂载到 /home 后:

text 复制代码
原来的 /home 目录内容暂时被遮住

用户访问 /home 时,看到的是新文件系统的根目录,而不是原来的目录内容.

卸载后,原来的 /home 内容会重新出现.

6.如何查看某个路径属于哪个分区

最推荐:findmnt

bash 复制代码
findmnt -T /home/user/a.txt

或查看当前目录:

bash 复制代码
findmnt -T .

可能输出:

text 复制代码
TARGET SOURCE           FSTYPE OPTIONS
/home  /dev/nvme0n1p3  ext4   rw,relatime

表示当前路径属于 /dev/nvme0n1p3 上的 ext4 文件系统.

使用 df

bash 复制代码
df -T /home/user/a.txt

查看当前目录:

bash 复制代码
df -T .

同时查看设备号和 inode 号

bash 复制代码
stat -c '设备号=%d inode=%i 挂载点=%m 文件=%n' /home/user/a.txt

可能得到:

text 复制代码
设备号=259 inode=100 挂载点=/home 文件=/home/user/a.txt

ls -i 只能显示 inode 号:

bash 复制代码
ls -li /home/user/a.txt

它没有完整显示文件系统身份,所以仅凭 inode 号不能跨文件系统定位文件.

完整定位关系

前面的知识可以串起来:

text 复制代码
路径字符串
   ↓
挂载树确定当前文件系统
   ↓
目录 dentry 查找文件名
   ↓
获得当前文件系统内的 inode号
   ↓
通过对应 Super Block 和 Inode Table 找到 inode
   ↓
inode 定位 Data Block
   ↓
读取文件内容

一句话概括:

inode 号只负责文件系统内部定位;挂载树负责判断当前属于哪个文件系统.Linux 依靠"挂载实例 + dentry/inode"跨越多个分区,构造出统一的目录树.


3.9⽂件系统总结

下⾯⽤⼏张图总结,主要想从不同⻆度说明.


4.软硬连接

4.1硬链接

硬链接(Hard Link)本质上是:多个文件名指向同一个 inode.

text 复制代码
文件名 A ─┐
          ├──→ inode 1001 ───→ 数据块
文件名 B ─┘

所以,硬链接并不是"复制了一份文件",而是给同一个文件增加了一个新的名字.

创建硬链接

bash 复制代码
ln 原文件 新文件名

例如:

bash 复制代码
ln a.txt b.txt

此时:

text 复制代码
a.txt → inode 1001
b.txt → inode 1001

查看 inode:

bash 复制代码
ls -li a.txt b.txt

可能输出:

text 复制代码
1001 -rw-r--r-- 2 user user 100 a.txt
1001 -rw-r--r-- 2 user user 100 b.txt

其中:

  • 两个文件的 inode 号相同
  • 链接数为 2
  • 两个名字地位完全相同

修改其中一个会怎样

因为两个文件名指向同一个 inode 和同一组数据块:

bash 复制代码
echo "hello" >> a.txt

再查看:

bash 复制代码
cat b.txt

也会看到新增内容.

也就是说:

修改 a.txt,等价于修改 b.txt 指向的同一个文件对象.

删除一个硬链接会怎样

执行:

bash 复制代码
rm a.txt

只是删除目录中的映射:

text 复制代码
a.txt → inode 1001

此时仍然有:

text 复制代码
b.txt → inode 1001

所以文件数据不会消失,只是 inode 的链接计数减 1.

text 复制代码
删除前:链接数 2
删除后:链接数 1

只有当满足以下条件时,文件数据才会真正释放:

text 复制代码
硬链接计数 = 0
并且
没有进程仍然打开该文件

因此,Linux 中删除文件的本质不是立即擦除数据,而是:

text 复制代码
删除文件名
   ↓
链接计数减 1
   ↓
链接数为 0
   ↓
没有进程引用
   ↓
释放 inode 和数据块

硬链接为什么不能跨分区

目录项中记录的是:

text 复制代码
文件名 → inode 号

inode 号只在当前文件系统内有效.

不同分区可能都有 inode 1001

text 复制代码
分区 A:inode 1001
分区 B:inode 1001

因此硬链接无法表达:

text 复制代码
文件名 → 另一个文件系统中的 inode

例如:

bash 复制代码
ln /home/a.txt /data/b.txt

如果 /home/data 属于不同文件系统,通常会报错:

text 复制代码
Invalid cross-device link

为什么普通用户不能给目录创建硬链接

目录硬链接会破坏目录树结构,甚至形成环:

text 复制代码
目录 A → 目录 B
目录 B → 目录 A

这样路径遍历、磁盘检查和递归删除都会变得非常复杂.

所以 Linux 通常禁止普通用户对目录创建硬链接.

不过目录本身存在系统维护的特殊硬链接:

text 复制代码
.   → 当前目录
..  → 父目录

这也是目录链接数经常大于 1 的原因.

我们看到,真正找到磁盘上⽂件的并不是⽂件名,⽽是inode.其实在linux中可以让多个⽂件名对应于同⼀个inode.

bash 复制代码
touch abc
ln abc def
ls -li abc def

• abc和def的链接状态完全相同,他们被称为指向⽂件的硬链接.内核记录了这个连接数,inode265218的硬连接数为2.

• 我们在删除⽂件时⼲了两件事情:(1)在⽬录中将对应的记录删除.(2)将硬连接数-1,如果为0,则将对应的磁盘释放.

一句话总结

硬链接就是同一个 inode 的多个文件名;删除一个名字不会删除文件,只有所有硬链接都被删除且没有进程继续打开时,inode 和数据块才会释放.


4.2软链接

软链接(Symbolic Link,符号链接)是一个独立文件,它保存的是目标文件或目录的"路径字符串".

text 复制代码
软链接名
   ↓
自己的 inode
   ↓
保存目标路径 "/home/user/a.txt"
   ↓
按该路径重新查找目标文件

它类似 Windows 中的快捷方式,但属于文件系统对象,可以被程序像普通路径一样访问.

创建软链接

bash 复制代码
ln -s 目标路径 软链接名

例如:

bash 复制代码
ln -s /home/user/a.txt b.txt

此时:

text 复制代码
b.txt → "/home/user/a.txt" → a.txt 的 inode → 数据块

查看:

bash 复制代码
ls -li a.txt b.txt

可能显示:

text 复制代码
1001 -rw-r--r-- 1 user user 100 a.txt
2050 lrwxrwxrwx 1 user user 16  b.txt -> /home/user/a.txt

可以看到:

  • a.txtb.txt 的 inode 不同
  • b.txt 的文件类型是 l
  • b.txt 中保存的是目标路径

访问软链接的过程

执行:

bash 复制代码
cat b.txt

内核大致执行:

text 复制代码
找到 b.txt 的目录项
        ↓
读取 b.txt 的 inode
        ↓
发现它是符号链接
        ↓
读取其中保存的目标路径
        ↓
重新解析目标路径
        ↓
找到目标文件 inode
        ↓
读取目标文件内容

所以,软链接最终指向的是路径,而不是直接指向 inode.

删除目标文件会怎样

假设:

text 复制代码
b.txt → /home/user/a.txt

删除目标文件:

bash 复制代码
rm /home/user/a.txt

软链接 b.txt 本身仍然存在,但目标路径已经找不到:

text 复制代码
b.txt → 不存在的路径

这种软链接称为:

悬空链接、失效链接或 dangling symlink

此时执行:

bash 复制代码
cat b.txt

通常会提示:

text 复制代码
No such file or directory

删除软链接会怎样

执行:

bash 复制代码
rm b.txt

只会删除软链接本身,不会删除目标文件:

text 复制代码
删除 b.txt
a.txt 仍然存在

注意删除指向目录的软链接时,通常直接使用:

bash 复制代码
rm link_to_dir

不要随意在后面添加 /,否则某些命令可能把它当作目标目录处理.

可以跨文件系统

因为软链接保存的是路径,所以可以跨分区、跨文件系统:

bash 复制代码
ln -s /home/user/a.txt /data/a-link

即使 /home/data 位于不同分区,也可以创建.

这是软链接和硬链接的重要区别:

text 复制代码
硬链接:文件名 → 当前文件系统内的 inode
软链接:文件名 → 路径字符串

可以链接目录

软链接可以指向目录:

bash 复制代码
ln -s /var/log mylog

之后:

bash 复制代码
cd mylog

相当于进入:

text 复制代码
/var/log

硬链接通常不能由普通用户为目录创建,而软链接没有这个限制.

绝对路径与相对路径

绝对路径软链接

bash 复制代码
ln -s /home/user/a.txt b.txt

保存的内容是:

text 复制代码
/home/user/a.txt

无论软链接本身放在哪里,都会尝试访问这个绝对路径.

相对路径软链接

假设目录结构:

text 复制代码
project/
├── data/a.txt
└── links/

links 中创建:

bash 复制代码
ln -s ../data/a.txt links/b.txt

b.txt 保存:

text 复制代码
../data/a.txt

关键点是:

相对路径是相对于"软链接所在目录"解析的,不是相对于当前终端目录解析的.

软链接本身也有 inode

软链接不是一条抽象箭头,而是一个真正的文件系统对象:

text 复制代码
b.txt 的目录项
        ↓
b.txt 自己的 inode
        ↓
保存目标路径字符串

短目标路径在部分文件系统中可能直接保存在 inode 内部,这称为快速符号链接;较长路径则可能存放在数据块中.

statlstat

对软链接调用 stat() 时,通常会跟随软链接,返回目标文件的信息.

c 复制代码
stat("b.txt", &buf);

如果要查看软链接本身,应使用:

c 复制代码
lstat("b.txt", &buf);

命令行中:

bash 复制代码
stat b.txt

通常会显示软链接及目标信息;也可以使用:

bash 复制代码
readlink b.txt

直接查看软链接保存的目标路径:

text 复制代码
/home/user/a.txt

查看解析后的最终路径:

bash 复制代码
readlink -f b.txt

权限问题

ls -l 中软链接经常显示:

text 复制代码
lrwxrwxrwx

实际访问目标时,通常检查的是:

  • 路径中各级目录的权限
  • 目标文件本身的权限

软链接自身显示的传统权限位通常不决定是否能够读取目标文件

软链接形成循环

软链接可能形成循环:

text 复制代码
a → b
b → a

或者:

text 复制代码
dir/link → dir

内核会限制连续解析符号链接的次数,超过限制后通常报错:

text 复制代码
Too many levels of symbolic links

硬链接是通过inode引⽤另外⼀个⽂件,软链接是通过名字引⽤另外⼀个⽂件,但实际上,新的⽂件和被引⽤的⽂件的inode不同,应⽤常⻅上可以想象成⼀个快捷⽅式.在shell中的做法:

bash 复制代码
ln -s abc.s abc
ls -li

核心理解

text 复制代码
硬链接:
文件名 A ─┐
          ├→ 同一个 inode → 数据块
文件名 B ─┘
text 复制代码
软链接:
文件名 B → 自己的 inode → 保存路径 "A"
                              ↓
                         重新查找 A
                              ↓
                         A 的 inode

一句话概括:

硬链接是"同一个文件的另一个名字",软链接是"保存了目标路径的独立文件".


4.3软硬链接对比

对比项 软链接 硬链接
指向内容 路径字符串 inode
是否有独立 inode 多个名字共享同一 inode
能否跨文件系统 可以 不可以
能否链接目录 可以 通常不可以
目标删除后 软链接失效 其他硬链接仍可访问
inode 号 与目标不同 与目标相同
创建命令 ln -s a b ln a b

4.4软硬连接的⽤途

  • 硬链接适合:给同一个文件增加多个入口、避免因删除某个文件名导致数据丢失.
  • 软链接适合:创建快捷入口、跨目录或跨分区引用文件、切换软件版本和共享配置.

日常使用中,软链接更常见、更灵活;硬链接主要用于特定的文件管理和备份场景.


4.4.1硬链接的用途

硬链接本质是:

text 复制代码
多个文件名 → 同一个 inode → 同一份数据
(1)为同一文件提供多个名称
bash 复制代码
ln report.txt report-backup.txt

此时两个文件名指向同一份内容:

text 复制代码
report.txt ──────┐
                 ├→ inode → 数据块
report-backup.txt┘

修改其中任何一个,另一个看到的内容也会变化.

(2)防止误删某个文件名

假设文件有两个硬链接:

text 复制代码
a.txt → inode 100
b.txt → inode 100

删除 a.txt

bash 复制代码
rm a.txt

b.txt 仍然可以正常访问数据.

因此,硬链接可以作为一种有限的防误删机制.但它不是完整备份,因为修改任意硬链接都会修改同一份数据.

(3)增量备份和快照工具

部分备份工具会利用硬链接实现节省空间的"快照"效果:

text 复制代码
backup-day1/file.txt ─┐
                      ├→ 同一 inode
backup-day2/file.txt ─┘

未发生变化的文件使用硬链接,不需要重复保存数据;发生变化的文件再单独复制.

例如传统的 rsync --link-dest 方案.

(4)文件去重

如果确认两个文件内容完全相同,可以让它们共享同一个 inode,减少磁盘占用.

但这样做后,它们不再是独立副本,修改其中一个会影响另一个,因此必须谨慎.


4.4.2软链接的用途

软链接本质是:

text 复制代码
软链接 → 保存目标路径 → 目标文件
(1)创建快捷入口

例如文件实际位于:

text 复制代码
/var/lib/project/data/config.json

可以在当前目录创建快捷入口:

bash 复制代码
ln -s /var/lib/project/data/config.json config.json

之后直接访问:

bash 复制代码
cat config.json
(2)为目录创建快捷路径
bash 复制代码
ln -s /var/log/nginx ~/nginx-log

以后可以直接:

bash 复制代码
cd ~/nginx-log

软链接可以指向目录,这是非常常见的用途.

(3)跨分区、跨文件系统引用
bash 复制代码
ln -s /mnt/data/video.mp4 ~/video.mp4

即使 /mnt/data 和用户主目录位于不同分区,也可以正常创建软链接.

这是硬链接无法做到的.

(4)软件版本切换

系统中可能安装多个版本:

text 复制代码
/opt/app-1.0
/opt/app-2.0
/opt/app-3.0

创建统一入口:

bash 复制代码
ln -s /opt/app-3.0 /opt/app-current

程序统一使用:

text 复制代码
/opt/app-current

升级时只需要修改软链接:

bash 复制代码
ln -sfn /opt/app-3.1 /opt/app-current

不需要修改所有程序配置.

常见形式包括:

text 复制代码
python → python3
java → 具体的 JDK 版本
libxxx.so → libxxx.so.1.2.3
current → releases/2026-01
(5)共享配置文件

多个程序可以通过软链接使用同一个配置文件:

bash 复制代码
ln -s /etc/myapp/common.conf app1.conf
ln -s /etc/myapp/common.conf app2.conf

这样只需要维护一份配置.

(6)调整目录布局而不修改程序

假设程序固定读取:

text 复制代码
/app/data

但真实数据移动到:

text 复制代码
/mnt/storage/data

可以使用软链接:

bash 复制代码
ln -s /mnt/storage/data /app/data

程序仍然使用旧路径,不需要修改代码.

4.4.3怎么选择软硬链接

场景 推荐
给文件创建快捷入口 软链接
链接目录 软链接
跨分区引用 软链接
软件版本切换 软链接
多个路径共享配置 软链接
同一分区内给文件增加另一个名字 硬链接
防止删除某一个文件名后数据立即消失 硬链接
节省增量备份空间 硬链接
需要真正独立的备份 复制文件,不用链接

4.4.4核心理解

text 复制代码
硬链接:同一个文件的多个名字
软链接:指向另一个路径的快捷入口
复制文件:真正产生一份独立数据

因此,普通目录管理、软件部署和路径重定向优先使用软链接 ;只有明确需要共享同一个 inode 时,才使用硬链接 .

🚀真正的勇者不是流泪的人,而是含泪奔跑的人!


敬请期待下一篇文章内容


每日心灵鸡汤: 为什么受过你帮助的人,最后反而会恨你?

为什么有些人明明受过你的帮助,最后反而最恨你?因为当一个人长期接受你的帮助时,他的心理往往会经历这样一个过程:获得帮助时会感激,习惯帮助后会默认,持续获得后会觉得理所当然.当你的付出被他视为应得时,你一旦停止给予,他感受到的就不再是失去帮助,而是被"剥夺"了原本属于自己的东西.更深层地说,承认自己依赖过别人,会伤害一些人脆弱的自尊,于是他们不会面对自己的匮乏,而是把愤怒投向那个不再满足他们的人.所以很多时候,他们攻击的不是你这个人,而是你收回了他们以为永远拥有的使用权.

相关推荐
未知违规用户1 小时前
大模型项目: 学习FastAPI 服务器开发
服务器·人工智能·python·学习·fastapi
池以遇1 小时前
云原生——LVS
运维·云原生·lvs
幸福在路上wellbeing1 小时前
AI 智能体开发 · Day 3 详细学习手册
人工智能·学习·oracle
Ivan CloudBay2 小时前
服务器关机后,网站会发生什么?
运维·服务器
数智化管理手记4 小时前
手工统计指标误差大、效率低?指标管理系统如何告别人工算数痛点?
大数据·运维·数据库·人工智能·云计算
Better Bench9 小时前
WSL2 Ubuntu 中 Claude CLI “command not found” 故障排查与修复
linux·ubuntu·claude·wsl·claudecode
实心儿儿10 小时前
Linux —— 进程间关系和守护进程
linux·运维·服务器
FII工业富联科技服务10 小时前
工业设备运维如何Agentic化?拆解Factory Brain的设备运维架构
大数据·运维·人工智能·架构·制造
Dawn-bit11 小时前
Linux磁盘管理详解
linux·运维·服务器·计算机网络·云计算