399 lines
12 KiB
Markdown
399 lines
12 KiB
Markdown
|
|
# 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 x86(32位保护模式) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 九、小结
|
|||
|
|
|
|||
|
|
```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_虚拟存储器]]
|