vault backup: 2026-06-13 23:46:22

This commit is contained in:
2026-06-13 23:46:22 +08:00
parent 9a3d58dd3b
commit 224c3dc574
33 changed files with 14997 additions and 0 deletions

View File

@@ -0,0 +1,398 @@
# 15. 段式存储管理
> **课程**: 操作系统 - 存储器管理
> **核心内容**: 分段引入原因、分段思想、地址结构、段表、地址变换、段页式存储管理
---
## 前置知识
- [[13_存储管理基础]] — 存储器层次结构、逻辑地址与物理地址
- [[14_分页存储管理]] — 分页思想、页表、地址变换
---
## 一、为什么需要分段
[[14_分页存储管理|分页]]虽然解决了碎片问题,但在以下场景中存在不足:
### 1. 信息共享不方便
分页按固定大小划分,不考虑程序的逻辑结构。如果要共享一段代码(如共享库函数),该代码可能跨越多个页面,其中某些页面还包含不需要共享的数据,导致共享粒度过粗。
```
分页视角(按固定大小切分,不考虑逻辑含义):
┌────────┐ ┌────────┐ ┌────────┐
│ 代码1 │ │ 代码2+ │ │ 数据 │ ← 一个逻辑模块跨了3个页
│ │ │ 常量 │ │ │ 共享时会把不相关的内容也共享了
└────────┘ └────────┘ └────────┘
```
### 2. 动态链接问题
动态链接需要在运行时将目标模块装入内存并链接。分页系统中,模块的装入和链接以页为单位,不够灵活。
### 3. 程序员视角需求
程序员编写程序时,自然地将代码划分为**代码段、数据段、堆栈段**等逻辑模块。分页完全打乱了这种逻辑结构。
> **核心需求**: 地址空间的划分应该按照**逻辑意义**进行,而非固定大小。这就是分段的思想。
---
## 二、分段的基本思想
分段按照程序的**逻辑结构**将地址空间划分为若干个段:
- 每个段有独立的**段名**和**段长**
- 每个段在内存中**连续存放**
- 不同段之间**不需要连续**
```mermaid
flowchart TB
subgraph VA["进程虚拟地址空间"]
direction TB
S0["主程序段 (段0)"]
S1["子程序段 (段1)"]
S2["数据段 (段2)"]
S3["栈段 (段3)"]
end
subgraph PM["物理内存"]
direction TB
M0["区域A"]
M1["区域B"]
M2["区域C"]
M3["区域D"]
M4["空闲"]
M5["区域E"]
end
S0 -->|"段表映射"| M5
S1 -->|"段表映射"| M0
S2 -->|"段表映射"| M2
S3 -->|"段表映射"| M3
style VA fill:#e3f2fd,stroke:#1976d2
style PM fill:#fff8e1,stroke:#f9a825
```
### 分段 vs 分页
| 对比项 | 分页 | 分段 |
|--------|------|------|
| **划分依据** | 固定大小(物理需要) | 逻辑意义(程序员视角) |
| **段/页大小** | 所有页等大 | 各段长度不同 |
| **地址空间** | 一维VA直接计算VPN和偏移 | **二维**(段号 + 段内偏移) |
| **碎片类型** | 内碎片(页内浪费) | 外碎片(段间空闲区) |
| **共享** | 以页为粒度,较粗 | 以段为粒度,符合逻辑 |
| **动态增长** | 不方便 | 方便(如堆、栈段可动态扩展) |
---
## 三、地址结构
分段系统的逻辑地址是**二维**的,由两部分组成:
```
逻辑地址 = (段号 S, 段内地址 d)
```
```
逻辑地址表示:
┌──────────────┬────────────────────┐
│ 段号 S │ 段内地址 d │
│ (高位部分) │ (低位部分) │
└──────────────┴────────────────────┘
注意: 不同于分页的VPN|VPO可以统一计算
分段中 d 的取值范围取决于段长,各段不同。
```
**地址表示示例**:
- 分页地址 `(0x1A8F)` → 可直接算出 VPN=6, VPO=0x28F
- 分段地址 `(2, 0x100)` → 段号=2偏移=0x100需要查段表才知道该段的基址和长度
---
## 四、段表
### 段表结构
每个进程拥有一张**段表**,段表以**段号**为索引,每项包含:
| 字段 | 含义 |
|------|------|
| **段号** | 段的编号(隐含在索引中) |
| **段长 (Limit)** | 该段的长度(字节数),用于越界检查 |
| **段基址 (Base)** | 该段在物理内存中的起始地址 |
```
段表示例:
┌──────┬────────────┬────────────────┐
│ 段号 │ 段长(Limit) │ 基址(Base) │
├──────┼────────────┼────────────────┤
│ 0 │ 0x2000 │ 0x4000 │ ← 主程序: 在内存0x4000处, 长8KB
│ 1 │ 0x1000 │ 0x8000 │ ← 子程序: 在内存0x8000处, 长4KB
│ 2 │ 0x3000 │ 0xB000 │ ← 数据段: 在内存0xB000处, 长12KB
│ 3 │ 0x1800 │ 0x2000 │ ← 栈段: 在内存0x2000处, 长6KB
└──────┴────────────┴────────────────┘
```
段表基址寄存器 **STBR** 指向段表在内存中的起始地址。
---
## 五、地址变换过程
### 变换流程
```mermaid
flowchart TD
A["逻辑地址 (S, d)"] --> B["用STBR找到段表基址"]
B --> C["定位段表项: 段表基址 + S × 段表项大小"]
C --> D{"d < Limit ?"}
D -->|"是"| E["物理地址 PA = Base + d"]
D -->|"否"| F["**越界中断**<br/>(Segmentation Fault)"]
E --> G["访问物理内存"]
style F fill:#ffcdd2,stroke:#c62828
style D fill:#fff3e0,stroke:#e65100
```
### 详细步骤
1. **提取段号和偏移**: 从逻辑地址中分离出段号 S 和段内地址 d
2. **查段表**: 用 STBR + S 定位到第 S 个段表项
3. **越界检查**: 比较 d 与 Limit
- 若 d >= Limit触发**越界中断**Segmentation Fault
- 若 d < Limit继续
4. **计算物理地址**: PA = Base + d
5. **访问内存**: 用物理地址访问实际内存
### 地址变换计算示例
> **例题**: 某分段系统,段表如下。求以下逻辑地址对应的物理地址:
> - (0, 0x1500)
> - (1, 0x2000)
> - (2, 0x0800)
**解题过程**:
| 逻辑地址 | S | d | Limit | 比较 | Base | PA = Base + d | 结果 |
|---------|---|---|-------|------|------|--------------|------|
| (0, 0x1500) | 0 | 0x1500 | 0x2000 | 0x1500 < 0x2000 ✓ | 0x4000 | 0x4000 + 0x1500 | **0x5500** |
| (1, 0x2000) | 1 | 0x2000 | 0x1000 | 0x2000 >= 0x1000 ✗ | — | — | **越界中断** |
| (2, 0x0800) | 2 | 0x0800 | 0x3000 | 0x0800 < 0x3000 ✓ | 0xB000 | 0xB000 + 0x0800 | **0xB800** |
---
## 六、分段的优缺点
### 优点
| 优点 | 说明 |
|------|------|
| **符合程序逻辑** | 按代码、数据、栈等逻辑单元组织,便于理解和管理 |
| **便于共享** | 以段为单位共享,粒度合理(如共享整个代码段) |
| **便于动态链接** | 可以按段为单位进行动态链接和装入 |
| **支持动态增长** | 堆、栈等段可以独立扩展,不影响其他段 |
| **保护自然** | 每段可设置独立的访问权限(代码段只读、数据段可读写等) |
### 缺点
| 缺点 | 说明 |
|------|------|
| **外碎片** | 段长不等,内存分配/回收后产生不连续的空闲区 |
| **段长限制** | 每段最大长度受地址结构限制 |
| **内存紧缩开销** | 消除外碎片需要移动段(类似动态分区的紧凑操作) |
---
## 七、段页式存储管理
### 基本思想
**段页式** = 分段 + 分页,兼具两者的优点:
- 先按**逻辑结构分段**(保留分段的优点:共享、保护、逻辑清晰)
- 再将每段**按固定大小分页**(保留分页的优点:消除外碎片)
```mermaid
flowchart TB
subgraph VA["虚拟地址空间"]
direction TB
S0["段0 (主程序)"]
S1["段1 (子程序)"]
S2["段2 (数据)"]
end
subgraph S0P["段0 内部分页"]
direction TB
P0["页0"]
P1["页1"]
P2["页2"]
end
subgraph PM["物理内存"]
direction TB
F0["页框0"]
F1["页框1"]
F2["页框2"]
F3["页框3"]
end
VA --> S0P
P0 --> F2
P1 --> F0
P2 --> F3
style VA fill:#e3f2fd,stroke:#1976d2
style S0P fill:#f3e5f5,stroke:#7b1fa2
style PM fill:#fff8e1,stroke:#f9a825
```
### 段页式地址结构
逻辑地址由**三部分**组成:
```
段页式逻辑地址:
┌──────────────┬──────────────┬──────────────┐
│ 段号 S │ 页号 P │ 页内偏移 d │
└──────────────┴──────────────┴──────────────┘
```
### 段页式地址变换
```mermaid
flowchart TD
A["逻辑地址 (S, P, d)"] --> B["1. 用STBR找到段表"]
B --> C["2. 用段号S查段表<br/>得到该段的页表基址和段长"]
C --> D{"P < 该段页数?"}
D -->|"是"| E["3. 用页号P查页表<br/>得到物理页框号 PPN"]
D -->|"否"| F["越界中断"]
E --> G["4. 物理地址 = PPN × 页大小 + d"]
G --> H["访问物理内存"]
style F fill:#ffcdd2,stroke:#c62828
```
### 段页式地址变换步骤
1. **查段表**: 用 STBR + S 找到第 S 个段表项,得到该段的**页表基址**
2. **查页表**: 用页表基址 + P 找到第 P 个页表项,得到 **PPN**
3. **拼接地址**: PA = PPN × 页大小 + d
> **注意**: 段页式需要 **3 次内存访问**(段表 + 页表 + 数据比纯分页多一次。TLB 的作用更加关键。
### 段页式地址变换计算示例
> **例题**: 某段页式系统,页面大小 4KB。段表如下求逻辑地址 (1, 2, 0x100) 的物理地址。
**段表**:
| 段号 | 页表基址 | 段长(页数) |
|------|---------|-----------|
| 0 | 0x8000 | 4 |
| 1 | 0xA000 | 6 |
| 2 | 0xC000 | 3 |
**段1的页表** (基址 0xA000):
| 页号 | PPN |
|------|-----|
| 0 | 5 |
| 1 | 2 |
| 2 | 8 |
| 3 | 1 |
| 4 | 7 |
| 5 | 3 |
**解题过程**:
1. S=1, P=2, d=0x100
2. 查段表段1的页表基址 = 0xA000段长 = 6 页
3. P=2 < 6合法
4. 查段1的页表P=2 对应 PPN=**8**
5. PA = 8 × 4096 + 0x100 = 0x8000 + 0x100 = **0x8100**
---
## 八、三种存储管理方式对比
| 对比项 | 纯分页 | 纯分段 | 段页式 |
|--------|--------|--------|--------|
| **划分依据** | 固定大小 | 逻辑结构 | 先逻辑后固定大小 |
| **地址维度** | 一维 | 二维 | 三维 |
| **碎片** | 内碎片 | 外碎片 | 内碎片 |
| **共享** | 以页为粒度 | 以段为粒度 | 以段为粒度 |
| **内存访问次数** | 2次页表+数据) | 2次段表+数据) | 3次段表+页表+数据) |
| **代表系统** | Linux | 早期Multics | Intel x8632位保护模式 |
---
## 九、小结
```mermaid
mindmap
root((段式存储管理))
分段引入
信息共享不便
动态链接需求
逻辑结构需求
分段思想
按逻辑分段
代码段/数据段/栈段
各段独立地址空间
地址结构
二维: 段号+偏移
不同于分页的一维
段表
段号/段长/基址
越界检查
STBR寄存器
地址变换
查段表→越界检查→基址+偏移
PA = Base + d
段页式
先分段再分页
三维地址: 段号+页号+偏移
兼具两者优点
```
---
## 思考题
1. **概念理解**: 分段和分页的根本区别是什么?为什么说分段的地址空间是二维的?
2. **地址变换**: 某分段系统有 4 个段,段表如下。求物理地址或判断是否越界:
- (0, 0x800) → ?
- (2, 0x5000) → ?
- (3, 0x2000) → ?
| 段号 | 段长 | 基址 |
|------|------|------|
| 0 | 0x1000 | 0x5000 |
| 1 | 0x2000 | 0x8000 |
| 2 | 0x4000 | 0xA000 |
| 3 | 0x3000 | 0x2000 |
3. **段页式**: 为什么段页式需要 3 次内存访问TLB 如何缓解这个问题?
4. **对比分析**: 在什么场景下分段优于分页?在什么场景下分页优于分段?
---
## 关联笔记
- [[13_存储管理基础]] — 存储器层次结构与地址空间基础
- [[14_分页存储管理]] — 分页思想、页表与地址变换
- [[16_虚拟存储器]] — 基于分段的虚拟存储器实现
---
**上一讲**: [[14_分页存储管理]]
**下一讲**: [[16_虚拟存储器]]