# 第4章 存储器

> 📖 本章介绍存储器的分类、工作原理和层次结构，是课程的重点和难点。
> 🎯 重点理解存储层次、主存组成、Cache和虚拟存储器

---

## 📋 本章目录

- [[#4.1 概述]]
- [[#4.2 主存储器]]
- [[#4.3 高速缓冲存储器]]
- [[#4.4 虚拟存储器]]
- [[#本章小结]]

---

## 4.1 概述

### 4.1.1 存储器分类

```mermaid
graph TB
    A[存储器分类] --> B[按存储介质]
    A --> C[按存取方式]
    A --> D[按作用]

    B --> B1[半导体存储器]
    B --> B2[磁表面存储器]
    B --> B3[光盘存储器]

    C --> C1[随机存储器RAM]
    C --> C2[只读存储器ROM]
    C --> C3[串行访问存储器]

    D --> D1[主存储器]
    D --> D2[辅助存储器]
    D --> D3[缓冲存储器Cache]
```

#### 按存储介质分类

| 类型 | 特点 | 示例 |
|------|------|------|
| 半导体存储器 | 体积小、功耗低、速度快、易失性 | SRAM、DRAM |
| 磁表面存储器 | 非易失、容量大、速度慢 | 磁盘、磁带 |
| 光盘存储器 | 非易失、容量大、可移动 | CD、DVD |

#### 按存取方式分类

| 类型 | 特点 | 应用 |
|------|------|------|
| **RAM** | 随机读写、存取时间与位置无关 | 主存 |
| **ROM** | 只读不写（或受限写入） | 固件、BIOS |
| **顺序存取** | 按物理顺序寻找（如磁带） | 磁带 |
| **直接存取** | 先直接定位区域，再顺序寻找（如磁盘） | 磁盘 |

#### RAM和ROM的细分

```mermaid
graph LR
    RAM[随机存储器RAM] --> SRAM[静态RAM<br/>触发器原理]
    RAM --> DRAM[动态RAM<br/>电容充放电]

    ROM[只读存储器ROM] --> MROM[掩模ROM]
    ROM --> PROM[可编程ROM]
    ROM --> EPROM[可擦除PROM]
    ROM --> EEPROM[电擦除PROM]
    ROM --> Flash[闪速存储器]
```

### 4.1.2 存储器的层次结构

> [!important] 核心问题
> 速度、容量、价格三者不可兼得，需要通过层次结构解决矛盾。

#### 存储器性能指标关系

```mermaid
graph TB
    subgraph "存储器层次结构"
        REG["寄存器<br/>速度最快、容量最小、价格最高"] --> CACHE["Cache<br/>高速缓冲存储器"]
        CACHE --> MEM["主存储器<br/>主存"]
        MEM --> DISK["磁盘<br/>辅助存储器"]
        DISK --> TAPE["磁带<br/>辅助存储器"]
    end

    style REG fill:#ff9999
    style CACHE fill:#ffcc99
    style MEM fill:#ffff99
    style DISK fill:#99ff99
    style TAPE fill:#9999ff
```

| 层次 | 速度 | 容量 | 价格 | 位置 |
|------|------|------|------|------|
| 寄存器 | 最快 | 最小 | 最高 | CPU内部 |
| Cache | 很快 | 小 | 高 | CPU内部/外部 |
| 主存 | 较快 | 中等 | 中等 | 主板 |
| 磁盘 | 慢 | 大 | 低 | 外部设备 |
| 磁带 | 最慢 | 最大 | 最低 | 外部设备 |

#### 两个存储层次

```mermaid
graph TB
    subgraph "缓存-主存层次"
        CPU1[CPU] <--> |"速度接近Cache"| CACHE[Cache]
        CACHE <--> |"容量接近主存"| MEM1[主存]
    end

    subgraph "主存-辅存层次"
        MEM2[主存] <--> |"速度接近主存"| DISK[辅存]
        CPU2[CPU] -.-> |"不直接访问"| DISK
    end

    style CPU1 fill:#ff9999
    style CPU2 fill:#ff9999
    style CACHE fill:#ffcc99
    style MEM1 fill:#ffff99
    style MEM2 fill:#ffff99
    style DISK fill:#99ff99
```

**缓存-主存层次**：
- 解决CPU和主存**速度不匹配**问题
- 数据调动由**硬件**自动完成
- 对程序员**透明**

**主存-辅存层次**：
- 解决存储系统**容量**问题
- 数据调动由**硬件+操作系统**完成
- 对程序员**透明**

---

## 4.2 主存储器

### 4.2.1 主存的基本组成

```mermaid
graph TB
    subgraph "主存结构"
        MAR[MAR<br/>地址寄存器] --> |"地址"| DEC[译码器]
        DEC --> |"选择信号"| MEM[存储体]
        MEM <--> |"数据"| MDR[MDR<br/>数据寄存器]
        MDR <--> |"数据"| DBUS[数据总线]
        MAR <--> |"地址"| ABUS[地址总线]
        CTRL[控制电路] --> |"读写控制"| MEM
    end

    style MAR fill:#ff9999
    style MDR fill:#99ccff
    style MEM fill:#99ff99
    style DEC fill:#ffcc99
```

**工作过程**：
1. **读操作**：CPU将地址送MAR → 地址总线 → 译码器选中单元 → 读出数据到MDR → 数据总线
2. **写操作**：CPU将地址送MAR，数据送MDR → 发写命令 → 数据写入选中单元

### 4.2.2 主存中存储单元地址的分配

#### 字节寻址与字寻址

```mermaid
graph TB
    subgraph "IBM 370（32位字长）"
        A1["地址0"] --> B1["字节0"]
        A2["地址1"] --> B2["字节1"]
        A3["地址2"] --> B3["字节2"]
        A4["地址3"] --> B4["字节3"]
        A5["地址4（字地址）"] --> B5["下一个字"]
    end

    subgraph "PDP-11（16位字长）"
        C1["地址0（字地址）"] --> D1["字节0"]
        C2["地址1"] --> D2["字节1"]
        C3["地址2（字地址）"] --> D3["下一个字"]
    end
```

> [!note] 地址表示
> - IBM 370：字地址 = 高位字节地址（4的倍数）
> - PDP-11：字地址 = 低位字节地址（2的倍数）

### 4.2.3 主存的技术指标

#### 1. 存储容量

$$存储容量 = 存储单元个数 \times 存储字长$$

**字节表示**：
$$存储容量 = 存储单元个数 \times 存储字长 / 8$$

> [!example] 示例
> 24位地址线，按字节寻址：
> - 寻址范围：$2^{24} = 16M$ 个字节
> - 若字长32位：按字寻址范围 = $16M / 4 = 4M$ 个字

#### 2. 存储速度

| 指标 | 定义 | 关系 |
|------|------|------|
| **存取时间** | 启动一次操作到完成的时间 | 读出时间或写入时间 |
| **存取周期** | 连续两次操作的最小间隔 | 存取周期 > 存取时间 |
| **存储器带宽** | 单位时间存取信息量 | 带宽 = 存储字长 / 存取周期 |

**提高带宽的措施**：
1. 缩短存取周期
2. 增加存储字长
3. 增加存储体

### 4.2.4 半导体存储芯片

#### 芯片基本结构

```mermaid
graph LR
    subgraph "存储芯片"
        ADDR[地址线] --> DEC[译码驱动]
        DEC --> MEM[存储矩阵]
        MEM <--> RW[读/写电路]
        RW <--> DATA[数据线]
        CS[片选线] --> DEC
        RW_CTRL[读写控制线] --> RW
    end
```

**容量计算**：
$$芯片容量 = 2^{地址线位数} \times 数据线位数$$

> [!example] 示例
> - 10根地址线，4根数据线：$2^{10} \times 4 = 4K$ 位
> - 14根地址线，1根数据线：$2^{14} \times 1 = 16K$ 位

#### 译码驱动方式

| 方式 | 特点 | 适用场景 |
|------|------|----------|
| **线选法** | 一根字线直接选中一个单元 | 容量小的芯片 |
| **重合法** | X、Y两个方向译码，交叉点选中 | 大容量芯片 |

```mermaid
graph TB
    subgraph "线选法（16×1字节）"
        A1["A3A2A1A0 = 1111"] --> B1["第15根字线选中"]
        B1 --> C1["8位数据读出"]
    end

    subgraph "重合法（1K×1位）"
        A2["地址译码"] --> B2["X方向32根线"]
        A2 --> C2["Y方向32根线"]
        B2 --> D2["交叉点选中1位"]
        C2 --> D2
    end
```

### 4.2.5 随机存取存储器（RAM）

#### 静态RAM（SRAM）

**基本单元电路**：6个MOS管组成的触发器

**特点**：
- 用触发器原理寄存信息
- 速度快，不需要刷新
- 集成度低，功耗大
- 用于Cache

**典型芯片**：Intel 2114（1K×4位）

#### 动态RAM（DRAM）

**基本单元电路**：1个MOS管 + 电容

**特点**：
- 用电容充放电原理寄存信息
- 需要定期**刷新**
- 集成度高，功耗小
- 用于主存

**典型芯片**：Intel 4116（16K×1位）

**刷新方式**：

| 方式 | 特点 | 效率 |
|------|------|------|
| 集中刷新 | 一段时间集中刷新所有行 | 有死区 |
| 分散刷新 | 每行刷新分散到各周期 | 无死区，效率低 |
| 异步刷新 | 各行刷新均匀分散 | 折中方案 |

#### SRAM vs DRAM 对比

| 特性 | SRAM | DRAM |
|------|------|------|
| 存储原理 | 触发器 | 电容 |
| 速度 | 快 | 较慢 |
| 集成度 | 低 | 高 |
| 功耗 | 大 | 小 |
| 刷新 | 不需要 | 需要 |
| 价格 | 高 | 低 |
| 应用 | Cache | 主存 |

### 4.2.6 只读存储器（ROM）

| 类型 | 特点 | 应用 |
|------|------|------|
| **MROM** | 厂家掩模制作，不可更改 | 批量生产 |
| **PROM** | 用户可编程一次 | 小批量 |
| **EPROM** | 紫外线擦除，可重复编程 | 开发调试 |
| **EEPROM** | 电擦除，可重复编程 | 参数存储 |
| **Flash** | 电擦除，速度快 | U盘、SSD |

### 4.2.7 存储器与CPU的连接

#### 存储器容量扩展

**三种扩展方式**：

```mermaid
graph TB
    subgraph "位扩展"
        A1["芯片1（8位）"] --> C1["组合（16位）"]
        A2["芯片2（8位）"] --> C1
    end

    subgraph "字扩展"
        B1["芯片1（1K×8）"] --> D1["组合（2K×8）"]
        B2["芯片2（1K×8）"] --> D1
    end

    subgraph "字位同时扩展"
        E1["芯片1"] --> F1["组合"]
        E2["芯片2"] --> F1
        E3["芯片3"] --> F1
        E4["芯片4"] --> F1
    end
```

| 扩展方式 | 连接方法 | 应用场景 |
|----------|----------|----------|
| **位扩展** | 地址线、控制线并联，数据线串联 | 增加字长 |
| **字扩展** | 地址线、数据线、控制线并联，片选线用高位地址 | 增加字数 |
| **字位扩展** | 先位扩展，再字扩展 | 大容量存储器 |

#### 存储器与CPU连接示例

```mermaid
graph TB
    subgraph "CPU"
        MAR[MAR] --> |"地址"| ADDR[地址总线]
        MDR[MDR] <--> |"数据"| DATA[数据总线]
        CTRL[控制信号] --> |"读写"| RW[读写控制]
    end

    subgraph "存储器"
        ADDR --> |"地址"| CHIP1[芯片1]
        ADDR --> |"地址"| CHIP2[芯片2]
        CHIP1 <--> |"数据"| DATA
        CHIP2 <--> |"数据"| DATA
        CS1[片选1] --> CHIP1
        CS2[片选2] --> CHIP2
        RW --> CHIP1
        RW --> CHIP2
    end
```

### 4.2.8 存储器的校验

#### 奇偶校验

**原理**：增加1位校验位，使数据中1的个数为奇数（奇校验）或偶数（偶校验）。

**特点**：
- 简单，只能检测奇数位错误
- 不能纠错

#### 海明码（Hamming Code）

**原理**：在数据位中插入多个校验位，通过校验位的组合定位错误位。

**校验位数r满足**：$2^r \geq m + r + 1$（m为数据位数）

**特点**：
- 可以检测并纠正1位错误
- 可以检测2位错误

> [!example] 示例
> 8位数据需要4位校验码：
> - 总位数：8 + 4 = 12位
> - 校验位位置：1, 2, 4, 8
> - 可纠正1位错误

---

## 4.3 高速缓冲存储器（Cache）

### 4.3.1 Cache的基本原理

> [!important] 为什么需要Cache
> CPU速度远快于主存，Cache作为中间缓冲，解决速度不匹配问题。

```mermaid
graph LR
    CPU[CPU] <--> |"速度匹配"| CACHE[Cache]
    CACHE <--> |"容量大"| MEM[主存]

    style CPU fill:#ff9999
    style CACHE fill:#ffcc99
    style MEM fill:#99ff99
```

**工作原理**：
1. CPU访问数据时，先查Cache
2. **命中**：直接从Cache读取（快）
3. **未命中**：从主存读取，同时调入Cache

### 4.3.2 Cache命中率

**命中率**：
$$H = \frac{N_c}{N_c + N_m}$$

- $N_c$：Cache访问次数
- $N_m$：主存访问次数

**平均访问时间**：
$$T_a = HT_c + (1-H)T_m$$

**访问效率**：
$$e = \frac{T_c}{T_a} = \frac{1}{H + (1-H)r}$$

其中 $r = T_m / T_c$（主存与Cache访问时间之比）

> [!example] 示例
> Cache命中率95%，Cache访问时间10ns，主存访问时间100ns：
> $$T_a = 0.95 \times 10 + 0.05 \times 100 = 14.5ns$$
> $$e = \frac{10}{14.5} = 69\%$$

### 4.3.3 Cache地址映射

> [!info] 问题
> 主存块如何放入Cache？需要地址映射。

#### 三种映射方式

##### 1. 直接映射（Direct Mapping）

**规则**：每个主存块只能映射到Cache的固定行。

**映射公式**：$i = j \mod m$
- i：Cache行号
- j：主存块号
- m：Cache行数

```mermaid
graph LR
    subgraph "直接映射"
        M0[主存块0] --> C0[Cache行0]
        M1[主存块1] --> C1[Cache行1]
        Mm[主存块m] --> Cm[Cache行m]
        Mm1[主存块m+1] --> C0
    end
```

**地址结构**：

| 标记（Tag） | Cache行号（Index） | 块内地址（Offset） |
|-------------|-------------------|-------------------|

**优点**：实现简单，查找速度快
**缺点**：灵活性差，命中率低

##### 2. 全相联映射（Fully Associative Mapping）

**规则**：主存块可以映射到Cache的任意行。

```mermaid
graph LR
    subgraph "全相联映射"
        M0[主存块0] --> |"任意行"| CACHE[Cache]
        M1[主存块1] --> |"任意行"| CACHE
        Mn[主存块n] --> |"任意行"| CACHE
    end
```

**地址结构**：

| 标记（Tag） | 块内地址（Offset） |
|-------------|-------------------|

**优点**：灵活性好，命中率高
**缺点**：查找速度慢，硬件成本高

##### 3. 组相联映射（Set Associative Mapping）

**规则**：将Cache分组，主存块映射到固定组，组内任意行。

**映射公式**：$q = j \mod u$
- q：Cache组号
- j：主存块号
- u：Cache组数

```mermaid
graph LR
    subgraph "组相联映射"
        M0[主存块0] --> G0[Cache组0]
        M1[主存块1] --> G1[Cache组1]
        Mu[主存块u] --> Gu[Cache组u]
        Mu1[主存块u+1] --> G0
    end
```

**地址结构**：

| 标记（Tag） | 组号（Index） | 块内地址（Offset） |
|-------------|--------------|-------------------|

**优点**：折中方案，兼顾速度和命中率
**应用**：现代Cache常用2路、4路、8路组相联

#### 三种映射方式对比

| 方式 | 映射规则 | 灵活性 | 命中率 | 硬件复杂度 |
|------|----------|--------|--------|------------|
| 直接映射 | 固定行 | 差 | 低 | 简单 |
| 全相联映射 | 任意行 | 好 | 高 | 复杂 |
| 组相联映射 | 固定组，任意行 | 中 | 中 | 中等 |

### 4.3.4 Cache替换算法

> [!info] 问题
> Cache满时，新块调入需要替换旧块，如何选择？

#### 常用替换算法

| 算法 | 原则 | 优点 | 缺点 |
|------|------|------|------|
| **FIFO** | 先进先出 | 实现简单 | 不考虑访问频率 |
| **LRU** | 最近最少使用 | 命中率高 | 实现复杂 |
| **LFU** | 最不经常使用 | 考虑频率 | 需要计数器 |
| **随机替换** | 随机选择 | 实现简单 | 命中率不稳定 |

**LRU算法实现**：
- 为每行设置计数器
- 每次访问，命中行计数器清零，其他行加1
- 替换计数器值最大的行

### 4.3.5 Cache写策略

> [!info] 问题
> Cache数据修改后，如何与主存保持一致？

#### 写策略

| 策略 | 方法 | 优点 | 缺点 |
|------|------|------|------|
| **写直达** | 同时写Cache和主存 | 数据一致性好 | 速度慢 |
| **写回法** | 只写Cache，替换时写回主存 | 速度快 | 数据可能不一致 |

```mermaid
graph TB
    subgraph "写直达"
        CPU1[CPU] --> |"写"| CACHE1[Cache]
        CPU1 --> |"同时写"| MEM1[主存]
    end

    subgraph "写回法"
        CPU2[CPU] --> |"写"| CACHE2[Cache]
        CACHE2 --> |"替换时写回"| MEM2[主存]
    end
```

### 4.3.6 Cache性能分析

**加速比**：
$$加速比 = \frac{T_m}{T_a} = \frac{r}{1 + (r-1)H}$$

**Cache容量选择**：
- 太小：命中率低
- 太大：成本高，速度可能下降
- 通常：几十KB到几MB

---

## 4.4 虚拟存储器

### 4.4.1 虚拟存储器的概念

> [!info] 定义
> 虚拟存储器是一个逻辑模型，程序员看到的地址空间远大于实际主存。

**特点**：
- **虚拟地址**（逻辑地址）：程序员使用的地址
- **物理地址**（实地址）：主存实际地址
- 地址转换由硬件和OS自动完成

```mermaid
graph TB
    subgraph "虚拟存储器"
        VA[虚拟地址空间<br/>很大] --> |"地址转换"| PA[物理地址空间<br/>较小]
        PA --> MEM[主存]
        MEM --> DISK[辅存]
    end
```

### 4.4.2 页式虚拟存储器

**基本思想**：
- 虚拟地址空间划分为固定大小的**页**
- 主存划分为相同大小的**页框**
- 通过**页表**建立映射关系

**地址结构**：

| 虚页号 | 页内地址 |
|--------|----------|

**页表项**：

| 虚页号 | 实页号 | 有效位 | 修改位 |
|--------|--------|--------|--------|

**地址转换过程**：
1. 从虚拟地址取出虚页号
2. 查页表得到实页号
3. 实页号 + 页内地址 = 物理地址

### 4.4.3 TLB（快表）

> [!info] 问题
> 页表在主存中，每次访问都要查页表，速度慢。

**解决方案**：在CPU中设置TLB（Translation Lookaside Buffer）

**特点**：
- 高速小容量存储器
- 存放最近使用的页表项
- 命中率通常 > 99%

**访问流程**：
1. 先查TLB
2. **TLB命中**：直接得到物理地址（快）
3. **TLB未命中**：查页表，更新TLB

---

## 📝 本章小结

### 核心概念

1. **存储层次**：寄存器 → Cache → 主存 → 辅存
2. **主存组成**：存储体 + MAR + MDR + 译码驱动 + 读写电路
3. **RAM分类**：SRAM（快，用于Cache）、DRAM（慢，用于主存）
4. **Cache映射**：直接映射、全相联映射、组相联映射
5. **虚拟存储器**：虚拟地址 → 物理地址的转换

### 关键公式

- **存储容量** = 存储单元个数 × 存储字长
- **Cache命中率** $H = N_c / (N_c + N_m)$
- **平均访问时间** $T_a = HT_c + (1-H)T_m$
- **访问效率** $e = T_c / T_a$

### 重点图示

> [!summary] 必须掌握的图
> 1. 存储器层次结构图
> 2. 主存基本组成框图
> 3. Cache三种映射方式示意图
> 4. Cache访问流程图

---

## 🧪 自测练习

### 概念题

1. 说明存储器层次结构的必要性。
2. 比较SRAM和DRAM的特点。
3. 解释Cache三种映射方式的区别。

### 计算题

1. 某系统Cache命中率90%，Cache访问时间20ns，主存访问时间200ns，求平均访问时间和访问效率。
2. 24位地址线，按字节寻址，存储字长32位，求存储容量。

### 分析题

1. 设计一个16K×8位的存储器，使用4K×4位的芯片。
2. 说明Cache写策略的选择依据。

---

## 🔗 相关链接

- [[03_系统总线]] - 上一章
- [[05_输入输出系统]] - 下一章
- [[08_CPU结构与功能]] - CPU访问存储器
- [[06_运算方法]] - 数据表示

---

*本章难度：⭐⭐⭐⭐⭐ 困难*
*重要程度：⭐⭐⭐⭐⭐ 核心重点*