四、存储系统与Cache:主存与辅存、Cache工作原理、地址映射方式、替换算法、虚拟存储器基础
存储系统是计算机体系结构的核心支柱之一。在国网计算机类考试中,本章不仅考查基本概念,更注重对Cache地址映射、替换算法以及虚拟存储器地址变换过程的深入理解。考生需要建立“容量-速度-成本”的权衡视角,掌握从辅存到主存、再从主存到Cache的多级存储层次如何协同工作。以下从五个核心模块展开讲解。
4.1 主存与辅存:存储层次的基础架构
4.1.1 核心原理:为什么需要多级存储
计算机系统对存储器的要求是“大容量、高速度、低成本”,但三者相互制约。SRAM速度最快但价格昂贵,DRAM容量较大但速度慢于SRAM,辅存(SSD/HDD)容量最大但速度最慢。因此现代计算机采用层次化存储结构:CPU寄存器→Cache→主存→辅存,每一层都作为下一层的缓存,利用局部性原理(时间局部性和空间局部性)提升整体性能。
| 存储层次 | 典型介质 | 容量范围 | 访问时间 | 价格/GB | CPU直接访问 |
|---|---|---|---|---|---|
| 寄存器 | 触发器 | 数百B | ~1ns | 极高 | 是 |
| Cache | SRAM | 32KB~数MB | 2~10ns | 高 | 是 |
| 主存 | DRAM | 4GB~64GB | 50~100ns | 中 | 是 |
| 辅存 | SSD/HDD | 512GB~数TB | 0.1~10ms | 低 | 否(需先调入主存) |
4.1.2 判断方法:主存与辅存的分工
- 主存(内存):CPU可直接通过地址总线访问,存放当前运行的程序和数据,断电后数据丢失(DRAM易失性)。
- 辅存(外存):CPU不能直接访问,必须通过DMA或中断方式将数据块调入主存后才能被CPU读取,断电后数据保留(非易失性)。
- 数据交换单位:主存与Cache之间以“块”(Block/Line)为单位,通常为16~128字节;主存与辅存之间以“页”(Page)或“段”为单位,通常为4KB~64KB。
4.1.3 案例拆解:程序运行时的数据流动
假设用户双击打开一个存放在SSD中的Word文档:
- CPU发出读请求,操作系统通过文件系统定位文档在SSD中的逻辑地址;
- SSD中的数据以页为单位(如4KB)通过DMA控制器拷贝到主存的用户缓冲区;
- CPU访问主存中的文档数据时,Cache控制器将数据块(如64B)从主存调入Cache;
- 后续CPU对同一文档区域的读写操作直接命中Cache,无需再次访问主存。
整个过程体现了“辅存→主存→Cache→寄存器”的逐级搬运,每一级都通过预取和缓存策略减少下一级的访问频率。
4.1.4 常见误区与复盘
误区2:混淆“主存容量”与“虚拟内存容量”。虚拟内存是主存+辅存的一部分(页面文件/交换分区)共同构成的逻辑空间,其容量可远超物理主存。
4.2 Cache工作原理:命中与缺失的判定
4.2.1 核心原理:Cache如何工作
Cache位于CPU与主存之间,由SRAM构成,保存主存中部分数据的副本。CPU访问存储器时,先检查Cache:
- 命中(Hit):所需数据在Cache中,直接返回,访问时间为Cache访问时间(如2ns)。
- 缺失(Miss):所需数据不在Cache中,需从主存读取该数据块(同时包含目标字),将其装入Cache,并返回给CPU。若Cache已满,则按替换算法淘汰一个旧块。
衡量Cache性能的关键指标是命中率H,平均访问时间公式为:
平均访问时间 = H × T_cache + (1-H) × (T_miss_penalty)
其中 T_miss_penalty = T_memory + T_cache_update
4.2.2 判断方法:Cache缺失的三种类型(3C模型)
| 缺失类型 | 产生原因 | 减少方法 |
|---|---|---|
| 强制缺失(Compulsory) | 首次访问某块数据,Cache中必然不存在 | 增大块大小(预取更多相邻数据) |
| 容量缺失(Capacity) | Cache容量不足,无法容纳程序工作集 | 增大Cache容量 |
| 冲突缺失(Conflict) | 多个地址映射到同一Cache行,互相淘汰 | 提高相联度(组相联/全相联) |
4.2.3 案例拆解:命中率计算
某程序访问序列(按块号):1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5。Cache容量为4块,采用LRU替换。计算命中率:
- 访问1(缺失,装入1)→ 访问2(缺失,装入2)→ 访问3(缺失,装入3)→ 访问4(缺失,装入4)
- 访问1(命中)→ 访问2(命中)→ 访问5(缺失,淘汰最久未用的3,装入5)
- 访问1(命中)→ 访问2(命中)→ 访问3(缺失,淘汰4,装入3)→ 访问4(缺失,淘汰5,装入4)→ 访问5(缺失,淘汰1,装入5)
共12次访问,命中4次,命中率 = 4/12 ≈ 33.3%。若采用FIFO,命中率可能不同,说明替换算法直接影响性能。
4.3 地址映射方式:主存块如何放入Cache
4.3.1 核心原理:三种映射方式
地址映射决定主存中的每个块可以放置到Cache的哪些位置。三种基本方式:
| 映射方式 | 主存块可存放位置 | 地址结构 | 优点 | 缺点 |
|---|---|---|---|---|
| 直接映射 | 唯一固定位置(Cache行号 = 主存块号 mod Cache行数) | 标记 + 行号 + 块内地址 | 硬件简单,查找快 | 冲突率高,Cache利用率低 |
| 全相联映射 | 任意Cache行 | 标记 + 块内地址 | 冲突率最低,利用率高 | 比较器成本高,查找慢 |
| 组相联映射 | 固定组内任意行(组号 = 主存块号 mod 组数) | 标记 + 组号 + 块内地址 | 折中方案,兼顾速度与命中率 | 硬件复杂度介于两者之间 |
4.3.2 判断方法:地址字段划分
设主存容量为2^m字节,Cache容量为2^c字节,块大小为2^b字节,则:
- 块内地址位数 = b
- Cache行数 = 2^(c-b)
- 直接映射:主存地址 = 标记(m-c) + 行号(c-b) + 块内地址(b)
- 组相联映射(每组2^r行):组数 = 2^(c-b-r),主存地址 = 标记(m-c+r) + 组号(c-b-r) + 块内地址(b)
4.3.3 案例拆解:地址划分计算
某计算机主存容量1GB(2^30字节),Cache容量64KB(2^16字节),块大小16B(2^4字节),采用4路组相联映射。计算各字段位数:
- 块内地址位数 = 4
- Cache行数 = 2^16 / 2^4 = 2^12 = 4096行
- 每组4行,组数 = 4096 / 4 = 1024 = 2^10,组号位数 = 10
- 标记位数 = 30 - 10 - 4 = 16位
主存地址格式:标记(16位) | 组号(10位) | 块内地址(4位)。CPU访问时,先根据组号定位到Cache中的某一组,再将该组内4个行的标记与主存地址的16位标记逐一比较,若匹配则命中。
4.3.4 常见误区与复盘
易错点:组相联映射中,组号的计算与直接映射的行号计算类似,但组内有多行可供选择,因此冲突率低于直接映射。
4.4 替换算法:Cache满时淘汰谁
4.4.1 核心原理:常见替换算法
- 随机法(RAND):随机选择一行淘汰,硬件最简单,但未利用局部性,命中率不稳定。
- 先进先出(FIFO):淘汰最早装入的行,实现简单,但可能淘汰正在频繁使用的“老”数据。
- LRU(近期最少使用):淘汰最长时间未被访问的行,利用时间局部性,命中率较高,但需要记录访问历史(计数器或栈)。
- LFU(最不经常使用):淘汰访问次数最少的行,需记录访问频次,可能保留“陈旧热点”数据。
4.4.2 判断方法:LRU的硬件实现
以4路组相联为例,每组内4行分别编号0~3,维护一个2位计数器(或状态位组合):
- 每次命中某行,将该行计数器置为最大值(如3),其他行计数器减1(若大于0);
- 每次缺失需要替换时,选择计数器值最小的行淘汰;
- 新装入的行计数器置为最大值。
这种“计数器法”是LRU的近似实现,实际处理器中常用“伪LRU”树形方法降低硬件开销。
4.4.3 案例拆解:LRU vs FIFO对比
访问序列:A, B, C, D, A, B, E, A, B, C, D, E,Cache容量3块。
| 步骤 | 访问 | LRU状态(最左为最久未用) | LRU是否命中 | FIFO状态 | FIFO是否命中 |
|---|---|---|---|---|---|
| 1 | A | A | 缺失 | A | 缺失 |
| 2 | B | A,B | 缺失 | A,B | 缺失 |
| 3 | C | A,B,C | 缺失 | A,B,C | 缺失 |
| 4 | D | B,C,D(淘汰A) | 缺失 | B,C,D(淘汰A) | 缺失 |
| 5 | A | C,D,A(淘汰B) | 缺失 | C,D,A(淘汰B) | 缺失 |
| 6 | B | D,A,B(淘汰C) | 缺失 | D,A,B(淘汰C) | 缺失 |
| 7 | E | A,B,E(淘汰D) | 缺失 | A,B,E(淘汰D) | 缺失 |
| 8 | A | B,E,A(命中A) | 命中 | A,B,E(命中A) | 命中 |
| 9 | B | E,A,B(命中B) | 命中 | A,B,E(命中B) | 命中 |
| 10 | C | A,B,C(淘汰E) | 缺失 | B,E,C(淘汰A) | 缺失 |
| 11 | D | B,C,D(淘汰A) | 缺失 | E,C,D(淘汰B) | 缺失 |
| 12 | E | C,D,E(淘汰B) | 缺失 | C,D,E(淘汰E? 实际E已在Cache中) | 命中 |
LRU命中3次,FIFO命中3次(本例巧合相同),但LRU通常更优。注意FIFO存在“Belady异常”——增加Cache容量反而降低命中率,而LRU不会。
4.5 虚拟存储器基础:主存与辅存的统一
4.5.1 核心原理:虚拟存储器的本质
虚拟存储器将主存与辅存统一编址,形成比物理主存大得多的逻辑地址空间。CPU访问的是虚拟地址(逻辑地址),由MMU通过页表/段表转换为物理地址。若页面不在主存中,则触发缺页中断,操作系统从辅存调入所需页面,并可能淘汰一个旧页面。
4.5.2 判断方法:页式虚拟存储器的地址变换
- 虚拟地址分为虚页号和页内偏移两部分;
- MMU查询TLB(快表)或页表,找到虚页号对应的物理页框号;
- 物理地址 = 物理页框号 × 页大小 + 页内偏移;
- 若TLB未命中,则访问主存中的页表;若页表项有效位为0,则缺页,转入操作系统处理。
| 概念 | 说明 | 典型值 |
|---|---|---|
| 页大小 | 虚拟地址与物理地址划分的最小单位 | 4KB / 2MB / 1GB |
| 页表项 | 记录虚页号→物理页框号映射、有效位、访问位、修改位 | 4~8字节/项 |
| TLB | 页表的Cache,缓存最近使用的页表项 | 64~1024项 |
| 缺页率 | 页面不在主存中的访问比例 | <0.01% |
4.5.3 案例拆解:缺页处理流程
某进程访问虚拟地址0x0000_3F2A(页大小4KB,页内偏移0x2A):
- 虚页号 = 0x0000_3F2A >> 12 = 0x3F(63号页);
- MMU查询TLB,未命中;
- 访问主存中的页表,页表项有效位=0,触发缺页中断;
- 操作系统检查页表项中的辅存地址,从SSD读取63号页到主存空闲页框(如页框号=0x8A);
- 更新页表项(有效位=1,物理页框号=0x8A),刷新TLB;
- 重新执行触发缺页的指令,此时TLB命中,完成地址变换。
整个过程对用户透明,但缺页处理代价极高(毫秒级),因此操作系统采用局部性预取和工作集模型减少缺页率。
4.5.4 常见误区与复盘
- 误区1:认为虚拟地址空间大小等于物理内存大小。实际上虚拟地址空间由CPU地址位数决定(如64位可达2^64字节),远大于物理内存。
- 误区2:混淆Cache缺失与缺页。Cache缺失由硬件处理(几个周期),缺页由操作系统软件处理(几百万个周期),两者不在同一时间量级。
- 误区3:忽略TLB的作用。TLB缺失不一定是缺页,可能只是页表项不在TLB中,但页表项在主存中有效,只需从主存加载页表项到TLB即可。