596 lines
18 KiB
Markdown
596 lines
18 KiB
Markdown
|
|
# 第05讲:磁盘空间管理
|
|||
|
|
|
|||
|
|
> 🎯 **本节目标**:理解磁盘空间的外存组织方式,掌握FAT/NTFS/Ext2文件系统的结构,理解空闲空间管理和磁盘容错机制
|
|||
|
|
|
|||
|
|
## 📋 前置知识
|
|||
|
|
- [[04_文件IO编程]] — 文件读写的基本概念
|
|||
|
|
- [[01_系统运行机制]] — 存储层次结构
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🤔 为什么需要这个?
|
|||
|
|
|
|||
|
|
当你保存一个4GB的电影文件时,操作系统需要决定:把文件存在磁盘的哪些位置?如何记录哪些空间已用、哪些空闲?如果某个磁盘块坏了怎么办?不同的组织方式会直接影响文件的读写速度和磁盘空间利用率。
|
|||
|
|
|
|||
|
|
**生活比喻**:
|
|||
|
|
- **连续分配**就像电影院的连续座位:一整排坐在一起,找人很快,但如果有零散空位就浪费了
|
|||
|
|
- **链接分配**就像寻宝游戏:每个地点告诉你下一个地点在哪,灵活但不能直接跳到第N个
|
|||
|
|
- **索引分配**就像书的目录:通过目录直接找到对应页码,高效且灵活
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📖 核心概念
|
|||
|
|
|
|||
|
|
### 1. 外存组织方式
|
|||
|
|
|
|||
|
|
文件在磁盘上的存放方式有三种基本策略:
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
A[外存组织方式] --> B[连续组织
|
|||
|
|
顺序文件]
|
|||
|
|
A --> C[链接组织
|
|||
|
|
隐式链接/FAT]
|
|||
|
|
A --> D[索引组织
|
|||
|
|
单级/多级/混合]
|
|||
|
|
|
|||
|
|
B --> B1["优点:顺序和随机访问都快"]
|
|||
|
|
B --> B2["缺点:外部碎片,不能动态增长"]
|
|||
|
|
C --> C1["优点:消除了外部碎片"]
|
|||
|
|
C --> C2["缺点:不能高效随机访问"]
|
|||
|
|
D --> D1["优点:支持随机访问,无外部碎片"]
|
|||
|
|
D --> D2["缺点:索引块有开销"]
|
|||
|
|
|
|||
|
|
style A fill:#e1f5fe
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 连续组织(顺序文件)
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph LR
|
|||
|
|
A[文件目录] -->|"起始块号=2, 长度=3"| B[磁盘块2]
|
|||
|
|
B --> C[磁盘块3]
|
|||
|
|
C --> D[磁盘块4]
|
|||
|
|
|
|||
|
|
style B fill:#e8f5e9
|
|||
|
|
style C fill:#e8f5e9
|
|||
|
|
style D fill:#e8f5e9
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 文件占用一组**连续的磁盘块**
|
|||
|
|
- 目录项只需记录:起始块号 + 长度
|
|||
|
|
- 支持顺序访问和随机访问(直接计算偏移)
|
|||
|
|
- 问题:外部碎片严重,文件不能动态增长
|
|||
|
|
|
|||
|
|
#### 链接组织
|
|||
|
|
|
|||
|
|
**隐式链接**:每个磁盘块末尾存储指向下一个块的指针。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph LR
|
|||
|
|
A[目录] -->|"起始块号=2"| B[块2] -->|"指针→5"| C[块5] -->|"指针→8"| D[块8] -->|"指针→EOF"| E[结束]
|
|||
|
|
|
|||
|
|
style B fill:#e1f5fe
|
|||
|
|
style C fill:#e1f5fe
|
|||
|
|
style D fill:#e1f5fe
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 优点:消除了外部碎片,文件可以动态增长
|
|||
|
|
- 缺点:只能顺序访问,指针占用存储空间,可靠性差(一个指针损坏后续全部丢失)
|
|||
|
|
|
|||
|
|
**显式链接(FAT)**:将所有块的链接指针集中存放在一张**文件分配表(FAT)**中。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
subgraph 文件分配表FAT
|
|||
|
|
F0["0: —"]
|
|||
|
|
F1["1: —"]
|
|||
|
|
F2["2: 5"]
|
|||
|
|
F3["3: —"]
|
|||
|
|
F4["4: —"]
|
|||
|
|
F5["5: 8"]
|
|||
|
|
F6["6: —"]
|
|||
|
|
F7["7: —"]
|
|||
|
|
F8["8: EOF"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph 磁盘块
|
|||
|
|
D2[块2]
|
|||
|
|
D5[块5]
|
|||
|
|
D8[块8]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
D2 -.->|"FAT[2]=5"| D5
|
|||
|
|
D5 -.->|"FAT[5]=8"| D8
|
|||
|
|
D8 -.->|"FAT[8]=EOF"| STOP[结束]
|
|||
|
|
|
|||
|
|
style F2 fill:#ffcdd2
|
|||
|
|
style F5 fill:#ffcdd2
|
|||
|
|
style F8 fill:#ffcdd2
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- FAT常驻内存,随机访问时只需查表,不需要读磁盘
|
|||
|
|
- 比隐式链接更可靠,但也更占内存
|
|||
|
|
|
|||
|
|
#### 索引组织
|
|||
|
|
|
|||
|
|
为每个文件建立一个**索引块**,集中存储所有数据块的块号:
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
A[文件目录] -->|"索引块号=10"| B[索引块]
|
|||
|
|
B -->|"指针0"| C[块2]
|
|||
|
|
B -->|"指针1"| D[块5]
|
|||
|
|
B -->|"指针2"| E[块8]
|
|||
|
|
B -->|"指针3"| F[块12]
|
|||
|
|
|
|||
|
|
style B fill:#fff3e0
|
|||
|
|
style C fill:#e1f5fe
|
|||
|
|
style D fill:#e1f5fe
|
|||
|
|
style E fill:#e1f5fe
|
|||
|
|
style F fill:#e1f5fe
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 支持随机访问:访问第N个数据块,直接查索引块的第N个指针
|
|||
|
|
- 无外部碎片
|
|||
|
|
- 缺点:索引块本身占用空间,大文件需要多级索引
|
|||
|
|
|
|||
|
|
**三种方式对比**:
|
|||
|
|
|
|||
|
|
| 特性 | 连续组织 | 链接组织 | 索引组织 |
|
|||
|
|
|------|----------|----------|----------|
|
|||
|
|
| 顺序访问 | 快 | 快 | 快 |
|
|||
|
|
| 随机访问 | 快(直接计算) | 慢(需遍历链) | 快(查索引表) |
|
|||
|
|
| 外部碎片 | 严重 | 无 | 无 |
|
|||
|
|
| 文件增长 | 困难 | 容易 | 容易 |
|
|||
|
|
| 可靠性 | 高 | 低(指针损坏) | 高 |
|
|||
|
|
|
|||
|
|
### 2. FAT文件系统
|
|||
|
|
|
|||
|
|
FAT(File Allocation Table)是Windows早期广泛使用的文件系统。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
subgraph FAT磁盘布局
|
|||
|
|
A[引导扇区] --> B[FAT1]
|
|||
|
|
B --> C[FAT2 备份]
|
|||
|
|
C --> D[根目录区]
|
|||
|
|
D --> E[数据区]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
style A fill:#ffcdd2
|
|||
|
|
style B fill:#fff3e0
|
|||
|
|
style C fill:#fff3e0
|
|||
|
|
style D fill:#e1f5fe
|
|||
|
|
style E fill:#e8f5e9
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**簇(Cluster)**:FAT文件系统的最小分配单位,由若干连续扇区组成。
|
|||
|
|
|
|||
|
|
| FAT版本 | 最大分区 | 簇大小 | FAT表项位数 |
|
|||
|
|
|---------|---------|--------|-------------|
|
|||
|
|
| FAT12 | 2MB | 512B~4KB | 12位 |
|
|||
|
|
| FAT16 | 2GB | 2KB~32KB | 16位 |
|
|||
|
|
| FAT32 | 2TB | 4KB~32KB | 32位(实际28位) |
|
|||
|
|
|
|||
|
|
**FAT表结构**:每个簇在FAT表中占一个表项,表项内容的含义:
|
|||
|
|
|
|||
|
|
| 表项值 | 含义 |
|
|||
|
|
|--------|------|
|
|||
|
|
| 0 | 空闲簇 |
|
|||
|
|
| 2~N | 下一个簇的簇号 |
|
|||
|
|
| FF8H~FFFH (FAT12) | 文件结束标记 |
|
|||
|
|
| FFF8H~FFFFH (FAT16) | 文件结束标记 |
|
|||
|
|
| 0FFFFFF8H~0FFFFFFFH (FAT32) | 文件结束标记 |
|
|||
|
|
| 其他特殊值 | 坏簇标记 |
|
|||
|
|
|
|||
|
|
**FAT16的问题**:每个分区最多65536个簇,簇最小2KB时最大分区为2GB。小文件也会浪费一个簇的空间(簇内碎片)。
|
|||
|
|
|
|||
|
|
### 3. NTFS文件系统
|
|||
|
|
|
|||
|
|
NTFS(New Technology File System)是Windows NT系列的现代文件系统。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
subgraph NTFS磁盘布局
|
|||
|
|
A[引导扇区
|
|||
|
|
MBR/分区表] --> B[主控文件表MFT]
|
|||
|
|
B --> C[MFT副本]
|
|||
|
|
C --> D[系统文件区]
|
|||
|
|
D --> E[用户数据区]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
style A fill:#ffcdd2
|
|||
|
|
style B fill:#fff3e0
|
|||
|
|
style C fill:#fff3e0
|
|||
|
|
style D fill:#e1f5fe
|
|||
|
|
style E fill:#e8f5e9
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**关键特性**:
|
|||
|
|
|
|||
|
|
| 特性 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| 64位磁盘地址 | 支持超大分区(理论2^64字节) |
|
|||
|
|
| MFT主控文件表 | 核心数据结构,每个文件/目录对应一条MFT记录 |
|
|||
|
|
| LCN逻辑簇号 | 从分区开头算起的绝对簇号 |
|
|||
|
|
| VCN虚拟簇号 | 文件内部的相对簇号(从0开始) |
|
|||
|
|
| 日志文件 | 记录文件操作日志,崩溃后可恢复一致性 |
|
|||
|
|
| 文件加密 | 支持EFS加密文件系统 |
|
|||
|
|
| 文件压缩 | 支持透明压缩 |
|
|||
|
|
| 硬链接 | 多个文件名指向同一个MFT记录 |
|
|||
|
|
|
|||
|
|
**MFT结构**:每条MFT记录通常1KB,包含多个属性(文件名、时间戳、数据内容等)。小文件的数据直接存储在MFT记录中(驻留属性),无需额外数据块。
|
|||
|
|
|
|||
|
|
### 4. Ext2文件系统
|
|||
|
|
|
|||
|
|
Ext2是Linux的经典文件系统,采用**块组**组织磁盘空间。
|
|||
|
|
|
|||
|
|
#### 磁盘总体布局
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph LR
|
|||
|
|
subgraph "Ext2磁盘布局"
|
|||
|
|
A["引导块
|
|||
|
|
Block 0"] --> B["块组0"]
|
|||
|
|
B --> C["块组1"]
|
|||
|
|
C --> D["块组2"]
|
|||
|
|
D --> E["..."]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
style A fill:#ffcdd2
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 块组内部结构
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
subgraph "单个块组的结构"
|
|||
|
|
A["超级块 Super Block
|
|||
|
|
文件系统全局信息"] --> B["块组描述符表
|
|||
|
|
所有块组的描述信息"]
|
|||
|
|
B --> C["数据块位图
|
|||
|
|
记录哪些块已用"]
|
|||
|
|
C --> D["inode位图
|
|||
|
|
记录哪些inode已用"]
|
|||
|
|
D --> E["inode表
|
|||
|
|
所有inode的数组"]
|
|||
|
|
E --> F["数据块
|
|||
|
|
存放文件内容"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
style A fill:#ffcdd2
|
|||
|
|
style B fill:#ffcdd2
|
|||
|
|
style C fill:#fff3e0
|
|||
|
|
style D fill:#fff3e0
|
|||
|
|
style E fill:#e1f5fe
|
|||
|
|
style F fill:#e8f5e9
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**超级块**(Super Block):存储整个文件系统的元数据
|
|||
|
|
- 块大小、inode总数、块总数、空闲块数、空闲inode数
|
|||
|
|
- 挂载计数、上次检查时间等
|
|||
|
|
|
|||
|
|
**块组描述符**(Group Descriptor):描述每个块组的状态
|
|||
|
|
- 本块组的块位图位置、inode位图位置、inode表位置、空闲块数等
|
|||
|
|
|
|||
|
|
**块位图**(Block Bitmap):每个bit对应一个数据块,1=已用,0=空闲
|
|||
|
|
|
|||
|
|
**inode位图**(inode Bitmap):每个bit对应一个inode,1=已用,0=空闲
|
|||
|
|
|
|||
|
|
#### inode结构(128字节)
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
subgraph "inode 128字节"
|
|||
|
|
A["模式/权限 2B"] --> B["所有者ID 2B"]
|
|||
|
|
B --> C["文件大小 4B"]
|
|||
|
|
C --> D["时间戳 12B
|
|||
|
|
访问/修改/创建"]
|
|||
|
|
D --> E["链接计数 2B"]
|
|||
|
|
E --> F["数据块指针 60B"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph "15个地址项(60字节)"
|
|||
|
|
G["直接块指针 0~9
|
|||
|
|
10个 × 4B"]
|
|||
|
|
H["一次间接指针
|
|||
|
|
1个 × 4B"]
|
|||
|
|
I["二次间接指针
|
|||
|
|
1个 × 4B"]
|
|||
|
|
J["三次间接指针
|
|||
|
|
1个 × 4B"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
F --> G
|
|||
|
|
F --> H
|
|||
|
|
F --> I
|
|||
|
|
F --> J
|
|||
|
|
|
|||
|
|
style A fill:#e1f5fe
|
|||
|
|
style G fill:#e8f5e9
|
|||
|
|
style H fill:#fff3e0
|
|||
|
|
style I fill:#ffcdd2
|
|||
|
|
style J fill:#fce4ec
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 混合索引容量计算(重点)
|
|||
|
|
|
|||
|
|
设盘块大小为**4KB**,盘块号占**4字节**,inode有**13个地址项**(10直接 + 1一次间接 + 1二次间接 + 1三次间接):
|
|||
|
|
|
|||
|
|
每个盘块可存放盘块号个数:
|
|||
|
|
$$4096 \div 4 = 1024 \text{ 个指针}$$
|
|||
|
|
|
|||
|
|
| 索引级别 | 计算过程 | 容量 |
|
|||
|
|
|----------|----------|------|
|
|||
|
|
| 直接块(10个) | 10 x 4KB | **40KB** |
|
|||
|
|
| 一次间接 | 1024 x 4KB | **4MB** |
|
|||
|
|
| 二次间接 | 1024 x 1024 x 4KB | **4GB** |
|
|||
|
|
| 三次间接 | 1024 x 1024 x 1024 x 4KB | **4TB** |
|
|||
|
|
| **总计** | | **约4TB** |
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
subgraph "混合索引寻址过程"
|
|||
|
|
A["inode"] -->|"直接块指针0~9"| B["10个数据块
|
|||
|
|
共40KB"]
|
|||
|
|
A -->|"一次间接指针"| C["索引块1
|
|||
|
|
1024个指针"]
|
|||
|
|
C -->|"指针0~1023"| D["1024个数据块
|
|||
|
|
共4MB"]
|
|||
|
|
A -->|"二次间接指针"| E["二级索引块
|
|||
|
|
1024个指针"]
|
|||
|
|
E -->|"指针0~1023"| F["1024个一级索引块"]
|
|||
|
|
F -->|"每个指向1024个数据块"| G["1024x1024个数据块
|
|||
|
|
共4GB"]
|
|||
|
|
A -->|"三次间接指针"| H["三级索引块"]
|
|||
|
|
H --> I["1024个二级索引块"]
|
|||
|
|
I --> J["1024x1024个一级索引块"]
|
|||
|
|
J --> K["1024^3个数据块
|
|||
|
|
共4TB"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
style A fill:#ffcdd2
|
|||
|
|
style B fill:#e8f5e9
|
|||
|
|
style D fill:#e8f5e9
|
|||
|
|
style G fill:#e8f5e9
|
|||
|
|
style K fill:#e8f5e9
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**寻址示例**:假设要读取文件的第15000个字节(盘块大小4KB):
|
|||
|
|
- 逻辑块号 = 15000 / 4096 = 3(第4个块,索引为3)
|
|||
|
|
- 块内偏移 = 15000 % 4096 = 2660
|
|||
|
|
- 因为索引3 < 10,所以使用直接块指针[3]找到数据块
|
|||
|
|
- 在该数据块的偏移2660处读取数据
|
|||
|
|
|
|||
|
|
#### 目录项 ext2_dir_entry_2
|
|||
|
|
|
|||
|
|
目录在Ext2中也是文件,内容是一系列目录项:
|
|||
|
|
|
|||
|
|
| 字段 | 大小 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| inode号 | 4字节 | 指向的inode编号 |
|
|||
|
|
| rec_len | 2字节 | 本目录项总长度 |
|
|||
|
|
| name_len | 1字节 | 文件名长度 |
|
|||
|
|
| file_type | 1字节 | 文件类型(普通文件/目录/符号链接等) |
|
|||
|
|
| name | 变长 | 文件名(不超过255字节) |
|
|||
|
|
|
|||
|
|
### 5. HDFS
|
|||
|
|
|
|||
|
|
HDFS(Hadoop Distributed File System)是大数据领域的分布式文件系统。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
subgraph HDFS架构
|
|||
|
|
NN["名称节点 NameNode
|
|||
|
|
元数据管理
|
|||
|
|
文件→块映射
|
|||
|
|
块→数据节点映射"]
|
|||
|
|
DN1["数据节点 DataNode1
|
|||
|
|
块1, 块3"]
|
|||
|
|
DN2["数据节点 DataNode2
|
|||
|
|
块2, 块4"]
|
|||
|
|
DN3["数据节点 DataNode3
|
|||
|
|
块1副本, 块5"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
NN -->|"心跳/块报告"| DN1
|
|||
|
|
NN -->|"心跳/块报告"| DN2
|
|||
|
|
NN -->|"心跳/块报告"| DN3
|
|||
|
|
DN1 -->|"数据流"| CLIENT["客户端"]
|
|||
|
|
|
|||
|
|
style NN fill:#ffcdd2
|
|||
|
|
style DN1 fill:#e1f5fe
|
|||
|
|
style DN2 fill:#e1f5fe
|
|||
|
|
style DN3 fill:#e1f5fe
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**特点**:
|
|||
|
|
- 文件被分割为固定大小的块(默认128MB),分布在多个数据节点上
|
|||
|
|
- 每个块默认3副本,分布在不同机架
|
|||
|
|
- 名称节点管理元数据(内存中),数据节点存储实际数据
|
|||
|
|
- 适合大文件顺序读取,不适合小文件和随机写入
|
|||
|
|
|
|||
|
|
### 6. 空闲空间管理
|
|||
|
|
|
|||
|
|
#### 空闲表法
|
|||
|
|
|
|||
|
|
用一张表记录所有空闲区的起始块号和长度:
|
|||
|
|
|
|||
|
|
| 起始块号 | 空闲块数 |
|
|||
|
|
|----------|----------|
|
|||
|
|
| 2 | 3 |
|
|||
|
|
| 10 | 5 |
|
|||
|
|
| 20 | 2 |
|
|||
|
|
|
|||
|
|
适用于连续分配方式,适合少量空闲区的情况。
|
|||
|
|
|
|||
|
|
#### 空闲链表法
|
|||
|
|
|
|||
|
|
将所有空闲磁盘块用指针链接成一个链表。分配时从链头取,回收时插入链尾。缺点是指针占用空间,分配效率低。
|
|||
|
|
|
|||
|
|
#### 位示图法
|
|||
|
|
|
|||
|
|
用一个**位图(bitmap)**记录每个磁盘块的使用状态,1=已用,0=空闲(或反之)。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
位示图示例(每行16位):
|
|||
|
|
第0行: 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 → 块0~3已用
|
|||
|
|
第1行: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 → 块16~31空闲
|
|||
|
|
第2行: 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 → 块32~33已用
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**地址转换公式**:
|
|||
|
|
|
|||
|
|
设每行有 **j** 位(通常为字长,如16位、32位),则第 **n** 个磁盘块对应位示图中:
|
|||
|
|
|
|||
|
|
$$\text{行号 } i = n \div j$$
|
|||
|
|
$$\text{列号 } k = n \mod j$$
|
|||
|
|
|
|||
|
|
即:
|
|||
|
|
$$n = j \times i + k$$
|
|||
|
|
|
|||
|
|
**经典公式**(每行16位时):$n = 16 \times i + j$
|
|||
|
|
|
|||
|
|
**位示图法示例**:设磁盘共200个块,每行16位,位示图需要 200/16 = 13 行。
|
|||
|
|
|
|||
|
|
要找到第75个空闲块:$i = 75 \div 16 = 4$,$j = 75 \mod 16 = 11$,即第4行第11列。
|
|||
|
|
|
|||
|
|
#### 成组链接法(UNIX)
|
|||
|
|
|
|||
|
|
UNIX采用**成组链接法**管理空闲磁盘块,是空闲表法和空闲链表法的结合:
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
SB["超级块
|
|||
|
|
栈:存放当前组的空闲块号
|
|||
|
|
栈顶指针"] -->|"指向"| G1["空闲块组1
|
|||
|
|
栈底块存储下一组的信息"]
|
|||
|
|
G1 -->|"下一组指针"| G2["空闲块组2"]
|
|||
|
|
G2 -->|"下一组指针"| G3["空闲块组3"]
|
|||
|
|
G3 -->|"下一组指针"| G4["更多组..."]
|
|||
|
|
|
|||
|
|
style SB fill:#ffcdd2
|
|||
|
|
style G1 fill:#e8f5e9
|
|||
|
|
style G2 fill:#e8f5e9
|
|||
|
|
style G3 fill:#e8f5e9
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**分配过程**:
|
|||
|
|
1. 从超级块的栈中弹出一个空闲块号
|
|||
|
|
2. 如果栈中只剩一个元素(它是下一组的指针),先将该组信息读入超级块,再弹出
|
|||
|
|
3. 更新超级块
|
|||
|
|
|
|||
|
|
**回收过程**:
|
|||
|
|
1. 将回收的块号压入超级块的栈中
|
|||
|
|
2. 如果栈已满,将超级块中的栈信息写入回收块(成为新组),清空栈,将回收块号作为唯一元素
|
|||
|
|
|
|||
|
|
**优点**:分配和回收只需读写超级块(内存中),效率极高。
|
|||
|
|
|
|||
|
|
### 7. 磁盘IO优化
|
|||
|
|
|
|||
|
|
| 优化技术 | 原理 |
|
|||
|
|
|----------|------|
|
|||
|
|
| 磁盘高速缓存 | 在内存中开辟缓冲区缓存磁盘块,减少磁盘访问 |
|
|||
|
|
| 提前读 | 顺序读取时,预先把后续块读入缓存 |
|
|||
|
|
| 延迟写 | 先写入缓存,延迟到合适时机再写入磁盘 |
|
|||
|
|
| 虚拟盘 | 用内存模拟磁盘(RAM Disk),速度极快但断电丢失 |
|
|||
|
|
|
|||
|
|
### 8. RAID
|
|||
|
|
|
|||
|
|
RAID(Redundant Array of Independent Disks)通过多块磁盘组合提高性能和可靠性。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
subgraph RAID0 条带化
|
|||
|
|
A0["数据块1"] --> S0[磁盘0]
|
|||
|
|
A1["数据块2"] --> S1[磁盘1]
|
|||
|
|
A2["数据块3"] --> S0
|
|||
|
|
A3["数据块4"] --> S1
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph RAID1 镜像
|
|||
|
|
B0["数据"] --> M0[磁盘0]
|
|||
|
|
B0 --> M1[磁盘1 镜像]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph RAID5 分布式校验
|
|||
|
|
C0["数据A"] --> R0[磁盘0]
|
|||
|
|
C1["数据B"] --> R1[磁盘1]
|
|||
|
|
C2["P校验"] --> R2[磁盘2]
|
|||
|
|
C3["数据C"] --> R3[磁盘3]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
style S0 fill:#e1f5fe
|
|||
|
|
style S1 fill:#e1f5fe
|
|||
|
|
style M0 fill:#e8f5e9
|
|||
|
|
style M1 fill:#fff3e0
|
|||
|
|
style R0 fill:#e1f5fe
|
|||
|
|
style R1 fill:#e1f5fe
|
|||
|
|
style R2 fill:#ffcdd2
|
|||
|
|
style R3 fill:#e1f5fe
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| RAID级别 | 原理 | 冗余 | 最少磁盘 | 利用率 | 特点 |
|
|||
|
|
|----------|------|------|----------|--------|------|
|
|||
|
|
| RAID0 | 数据条带化分布 | 无 | 2 | 100% | 高性能,无容错 |
|
|||
|
|
| RAID1 | 数据完全镜像 | 100% | 2 | 50% | 高可靠,成本高 |
|
|||
|
|
| RAID3 | 位交叉+专用校验盘 | 1块校验盘 | 3 | (N-1)/N | 校验盘成瓶颈 |
|
|||
|
|
| RAID5 | 块交叉+分布式校验 | 分布式校验 | 3 | (N-1)/N | 性能与可靠性平衡 |
|
|||
|
|
|
|||
|
|
### 9. 磁盘容错
|
|||
|
|
|
|||
|
|
| 容错级别 | 技术 | 内容 |
|
|||
|
|
|----------|------|------|
|
|||
|
|
| SFT-I | 一级容错 | 双份目录和FAT表、热修复重定向(写入坏块时重定向到备用块) |
|
|||
|
|
| SFT-II | 二级容错 | 磁盘镜像(同一控制器两个磁盘)、磁盘双工(不同控制器两个磁盘) |
|
|||
|
|
| 集群容错 | 三级容错 | 多台服务器组成集群,一台故障其他接管 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 💻 动手实践
|
|||
|
|
|
|||
|
|
### 查看文件系统信息
|
|||
|
|
```bash
|
|||
|
|
# 查看磁盘使用情况
|
|||
|
|
df -h
|
|||
|
|
|
|||
|
|
# 查看inode使用情况
|
|||
|
|
df -i
|
|||
|
|
|
|||
|
|
# 查看文件系统类型和块大小
|
|||
|
|
tune2fs -l /dev/sda1 | grep -E "Block size|Inode count"
|
|||
|
|
|
|||
|
|
# 查看文件的块分配
|
|||
|
|
stat filename
|
|||
|
|
|
|||
|
|
# 查看文件的inode号
|
|||
|
|
ls -i filename
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔗 知识关联
|
|||
|
|
- 文件IO的read/write最终需要将数据写入 [[04_文件IO编程]] 中的物理磁盘
|
|||
|
|
- 磁盘调度算法在 [[17_IO系统]] 中有详细讲解
|
|||
|
|
- 文件系统是 [[13_存储管理基础]] 中存储管理的重要组成部分
|
|||
|
|
- 分页存储管理的思想与Ext2的块分配有相似之处,见 [[14_分页存储管理]]
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 思考题
|
|||
|
|
|
|||
|
|
1. **FAT表计算**:一个FAT16分区,每簇4KB,最多能管理多大的分区?为什么?
|
|||
|
|
2. **Ext2容量计算**:如果盘块大小为1KB(而非4KB),盘块号占4字节,那么混合索引支持的最大文件是多少?(提示:每个间接块只能放256个指针)
|
|||
|
|
3. **inode寻址**:给定盘块大小4KB,盘块号4字节,要读取文件偏移5GB处的数据,需要经过几级间接索引?
|
|||
|
|
4. **成组链接法**:如果超级块栈最多容纳100个空闲块号,那么分配第101个空闲块时会发生什么?
|
|||
|
|
5. **RAID选择**:一个视频监控系统需要大容量存储、持续写入、偶尔丢失可接受,应选择RAID几?为什么?
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📚 扩展阅读
|
|||
|
|
- 《计算机操作系统》(汤小丹)第5章:文件管理
|
|||
|
|
- 《操作系统概念》第11-12章:文件系统接口与实现
|
|||
|
|
- [ext2文件系统详解](https://www.nongnu.org/ext2-intro/)
|
|||
|
|
- [RAID级别详解](https://www.techtarget.com/searchstorage/definition/RAID)
|