第三部分:存储器层次结构
3.1 存储器概述
存储器的分类
-
按存储介质分类
- 半导体存储器:如内存(DRAM)、Cache(SRAM)、ROM。速度快,易失(RAM)或非易失(ROM)。
- 磁表面存储器:如硬盘、磁带。非易失,容量大,速度慢。
- 光存储器:如CD、DVD、蓝光。非易失,速度慢。
-
按存取方式分类
- 随机存取存储器(RAM):访问任意地址时间相同(内存、Cache)。
- 只读存储器(ROM):只能读不能写(或需要特殊方式写)。
- 串行存取存储器:如磁带,顺序访问。
- 直接存取存储器:如硬盘,先定位到磁道再顺序查找扇区。
-
按信息可保存性分类
- 易失性存储器:断电后信息丢失(RAM)。
- 非易失性存储器:断电后信息保持(ROM、硬盘(SSD)、闪存)。
存储系统的层次结构原理
计算机采用多级存储体系,目的是在速度、容量、成本之间取得平衡。从上到下:
- 寄存器(CPU内部,最快,容量最小,成本最高)
- 高速缓冲存储器(Cache,SRAM)
- 主存储器(内存,DRAM)
- 辅助存储器(硬盘、固态盘等)
存储层次类比:可以把存储系统想象成"办公桌-书架-档案室-仓库"系统。
- 寄存器 = 桌面上正在用的笔和纸(随手可取,但空间极小)
- Cache = 桌面的笔筒和小抽屉(常用工具放这里,拿取很快)
- 主存 = 办公室的书架(存储量大一些,但需要起身去拿)
- 辅存 = 走廊尽头的档案室(容量巨大,但需要走很久) 这个类比帮助理解:为什么需要多级存储?因为没有任何一种存储介质能同时做到"又快又大又便宜"。
各级存储器速度对比(以具体数字帮助理解差距):
| 存储层次 | 典型容量 | 典型访问时间 | 相对差距 |
|---|---|---|---|
| 寄存器 | 几十~几百字节 | ~0.3ns | 1x |
| L1 Cache | 32~64KB | ~1ns | ~3x |
| L2 Cache | 256~512KB | ~4ns | ~13x |
| L3 Cache | 8~32MB | ~15ns | ~50x |
| 主存(DRAM) | 8~64GB | ~80ns | ~267x |
| SSD | 256GB~2TB | ~10us(10000ns) | ~33000x |
| 机械硬盘 | 1~8TB | ~10ms(10^7ns) | ~33000000x |
这个表格说明:CPU访问寄存器和访问硬盘的速度差距高达数千万倍。如果没有Cache,CPU大部分时间都在等待数据。
局部性原理(存储层次能够有效工作的基础)
- 时间局部性:如果一个信息被访问,那么不久很可能再次访问它(如循环体)。
- 空间局部性:如果一个信息被访问,那么它附近的信息也可能很快被访问(如数组顺序访问)。
利用局部性,可以把当前常用的数据和指令从慢速存储器复制到快速存储器中,CPU大部分时间访问快速存储器,从而获得接近快速存储器的速度和接近大容量存储器的容量。
局部性原理的具体例子:
// 时间局部性的典型例子:循环
for (int i = 0; i < 1000; i++) {
sum += a[i]; // sum变量在每次循环中都被访问,体现了时间局部性
}
// 空间局部性的典型例子:数组顺序访问
// a[0], a[1], a[2], ... 在内存中是连续存放的,体现了空间局部性
反例(缺乏局部性):
// 跳跃访问破坏空间局部性
for (int i = 0; i < 1000; i++) {
sum += a[random_index[i]]; // 随机访问,无法利用空间局部性
}
💡 记忆技巧:
- 时间局部性 = "刚用过,马上又用"(循环中的循环变量)
- 空间局部性 = "用了这个,旁边的也快用了"(数组的顺序访问)
- 存储层次原则:"速度越快越贵越小,越靠近CPU"
3.2 主存储器(内存)
主存是CPU直接寻址和访问的存储器。考研需要掌握DRAM/SRAM的区别、主存芯片结构、容量扩展方法。
SRAM与DRAM
| 特性 | SRAM(静态RAM) | DRAM(动态RAM) |
|---|---|---|
| 存储单元 | 触发器(6个晶体管) | 电容(1个晶体管+1个电容) |
| 是否需要刷新 | 不需要 | 需要(电容电荷会泄漏,约2ms刷新一次) |
| 速度 | 快(几纳秒) | 慢(几十纳秒) |
| 集成度 | 低(占用面积大) | 高(容量大) |
| 功耗 | 较高 | 较低 |
| 成本 | 高 | 低 |
| 用途 | Cache | 主存 |
💡 记忆技巧:SRAM中的"S"可以联想为"Speed(速度快)",DRAM中的"D"可以联想为"Dynamic(需要动态刷新)"。"S"也是"贵"(贵所以只能做Cache量做小),"D"也是"大"(大容量所以做主存)。
SRAM vs DRAM 对比:SRAM像白炽灯——通电就亮,不需要"刷新";DRAM像手电筒——需要时不时按一下(刷新)保持亮度。
只读存储器(ROM)
- 掩膜ROM:出厂时写入,不可更改。
- PROM:可编程一次(用专用设备烧写)。
- EPROM:紫外线擦除,电编程,可多次。
- EEPROM:电擦除,可字节级修改。
- Flash Memory(闪存):块级擦除,广泛用于U盘、SSD。
主存芯片的结构 一个存储器芯片由存储阵列、地址译码器、读写控制电路、数据缓冲器等组成。关键参数:
- 地址线数量:若地址线为n条,则存储单元数量为2^n(每个单元通常存储1位,但也可多位)。
- 数据线数量:每次并行读/写的位数(如x1位、x4位、x8位)。
- 片选信号(CS或CE):用于选择芯片,高电平或低电平有效。
- 读写控制(WE、OE):WE=0写,WE=1读(通常与OE配合)。
主存容量的扩展 当单个芯片的容量或数据宽度不够时,需要将多个芯片组合。
-
位扩展(增加数据宽度)
- 例如:使用8片 1Kx1位的芯片,组成 1Kx8位的存储器。
- 方法:所有芯片的地址线、片选信号、读写控制并联,数据线各自独立(分别接数据总线的不同位)。
-
字扩展(增加地址范围/容量)
- 例如:使用2片 1Kx8位的芯片,组成 2Kx8位的存储器。
- 方法:地址线的高位通过译码器产生片选信号,分别选中不同芯片;低位地址线并联到所有芯片;数据线并联。
-
字位扩展(同时增加容量和位宽)
- 例如:用 1Kx4位的芯片组成 2Kx8位的存储器。
- 先进行位扩展(两片1Kx4组成1Kx8),再进行字扩展(两组这样的1Kx8组成2Kx8)。
主存扩展完整示例: 题目:用 4Kx4位的SRAM芯片,组成 8Kx8位的存储器,需要多少片芯片?如何连接?
解:
- 先位扩展:每组2片4Kx4位组成4Kx8位(数据线由4位扩到8位)。
- 再字扩展:需要2组4Kx8位组成8Kx8位(地址范围扩大1倍)。
- 总芯片数 = 2 x 2 = 4片。
连接方式:
- 地址线:A12~A0(2^13=8K)连接到所有芯片。其中A12用于字扩展的片选译码。
- 第1组(地址 0000~1FFF):A12=0时,通过译码器选中该组。
- 第2组(地址 2000~3FFF):A12=1时,选中该组。
- 每组内部,两片芯片的地址线并联,片选信号并联,数据线各自接D0
D3和D4D7。
⚠️ 易错点:
- 地址线数量 vs 地址范围:n条地址线可以寻址2^n个存储单元,每个存储单元不一定是一个字节(可以是1位、4位等)。
- 位扩展时数据线是"并联还是独立"?——数据线独立(各接总线的不同位),地址线并联(共享地址)。
- 字扩展时数据线是"并联还是独立"?——数据线并联(共享数据总线),地址线高位通过译码器产生片选。
主存与CPU的连接
- 需要确定地址总线的位数、数据总线的位数。
- 分配地址空间:将CPU地址空间的一部分分配给主存芯片,其余可能用于I/O或保留。
- 地址译码:用门电路或译码器(如3-8译码器)产生片选信号。
- 时序配合:CPU的读/写控制信号(如MEMR、MEMW)与存储芯片的OE、WE连接。
3.3 高速缓冲存储器(Cache)
Cache是介于CPU和主存之间的小容量快速存储器(SRAM),用于缓解CPU与主存的速度差异。
Cache的基本原理
- CPU访问主存时,先检查Cache。若数据在Cache中(命中),直接从Cache读取(快);若不在(缺失),则从主存读取一个块(通常几十字节)装入Cache,然后CPU再从Cache读取。
- 块(Block/Line):Cache和主存之间交换的最小单位,通常包含多个字节。
- 命中率 = 命中次数 / 总访问次数,缺失率 = 1 - 命中率。
- 平均访问时间 = 命中时间 + 缺失率 x 缺失开销(从主存读一个块的时间)。
Cache性能计算示例: 假设Cache命中时间为1ns,缺失开销为100ns(从主存读取一个块),缺失率为5%,求平均访问时间。 平均访问时间 = 1ns + 0.05 x 100ns = 1ns + 5ns = 6ns 如果不使用Cache,每次访问都要100ns,使用Cache后平均访问时间降到6ns,性能提升约16.7倍。
另一个示例: 若缺失率降低到1%,则平均访问时间 = 1ns + 0.01 x 100ns = 2ns(性能再提升3倍)。 这说明:Cache的性能主要由命中率决定,提高命中率是Cache设计的核心目标。
Cache的映射方式(决定主存块如何放入Cache)
-
直接映射
- 每个主存块只能映射到Cache中唯一固定的行。
- 映射公式:Cache行号 = 主存块号 mod Cache行数。
- 优点:硬件简单,访问速度快。缺点:冲突率高,容易频繁替换。
-
全相联映射
- 主存块可以映射到Cache中任意一行。
- 优点:冲突率最低。缺点:硬件复杂,查找速度慢(需要比较所有行的标记)。
-
组相联映射(折中方案)
- 将Cache分成若干组,每组包含若干行。主存块先映射到某一组(直接映射),然后在组内任意放置(全相联)。
- 例如:2路组相联,每组2行。
- 考研常考:n路组相联,需要理解如何根据主存地址计算组号、标记。
直接映射计算示例: 某Cache有16行(行号0~15),每行存放一个16字节的块。主存地址为32位,问主存块号100映射到Cache的哪一行? Cache行号 = 100 mod 16 = 4(第4行)。
组相联映射完整示例: 某Cache有64行,采用4路组相联(每组4行),每行32字节。主存地址为32位,求:标记、组号、块内偏移各占多少位?
解:
- 块内偏移:每行32字节 = 2^5,所以偏移占5位。
- 组数:64行 / 4路 = 16组 = 2^4,所以组号占4位。
- 标记:32 - 4(组号) - 5(偏移) = 23位。
所以主存地址格式为:标记(23位) + 组号(4位) + 块内偏移(5位)。
Cache的结构 每行(Cache行)包含:
- 有效位(valid):表示该行是否包含有效数据(初始为0)。
- 标记(tag):用于判断该行存放的是哪个主存块(高位地址)。
- 数据块(block):实际存储的数据(多个字节)。
Cache的读操作(以组相联为例)
- 将主存地址分解为:标记(Tag)、组索引(Index)、块内偏移(Offset)。
- 根据Index找到对应的Cache组。
- 在组内并行比较所有行的标记是否与Tag相等且有效位为1。
- 若相等且有效,命中,根据偏移取出数据。
- 若不命中,从主存读取整个块装入Cache的一行(如果该组已满,则按替换算法淘汰一行)。
Cache的替换算法(当需要装入新块且组内无空闲行时)
- 随机法:随机选一行替换。
- FIFO(先进先出):替换最早装入的那一行。
- LRU(最近最少使用):替换最长时间未被访问的行。考研常见,需要理解计数器或栈实现思路。
- LFU(最不经常使用):替换访问次数最少的一行(较少考)。
LRU替换算法示例: 一个2路组相联Cache(每组2行),初始为空。某组被访问的主存块序列为:A, B, C, A, B, C,使用LRU替换算法,问最终该组中存放的是哪些块?
| 访问 | 行0 | 行1 | 替换操作 |
|---|---|---|---|
| A | A(新) | 空 | A装入行0 |
| B | A(旧) | B(新) | B装入行1 |
| C | A(旧) | C(新) | 替换B(C是新访问) |
| A | A(新) | C(旧) | A命中,更新为最新 |
| B | B(新) | C(旧) | 替换A(A是最近最少使用) |
| C | B(旧) | C(新) | C命中 |
最终:行0=B,行1=C(或B, C都在)
Cache的写策略(当CPU写数据时)
写操作比读操作复杂,因为要保证主存和Cache的一致性。
-
写直达(Write Through):同时写Cache和主存。优点:简单,主存始终最新;缺点:慢(每次写都要访问慢速主存)。
-
写回(Write Back):只写Cache,当该行被替换时才写回主存。需增加一个 脏位(dirty bit) 标记该行是否被修改过。优点:快,减少主存写次数;缺点:实现复杂。
-
写分配(Write Allocate):写缺失时,先将缺失块从主存读到Cache,再写Cache(通常与写回配合)。
-
非写分配(No-Write Allocate):写缺失时,直接写主存,不调入Cache(通常与写直达配合)。
考研中常考:写直达+非写分配;写回+写分配。
写策略类比:
- 写直达 = "每次记笔记都同时抄到正式笔记本上"(多花时间但保证正式本最新)
- 写回 = "先写在草稿纸上,最后再整理到正式笔记本"(快但需要记得最终整理)
多级Cache 现代CPU有L1、L2、L3 Cache。L1最快最小,通常分为指令Cache和数据Cache(哈佛结构)。L2、L3更大更慢。平均访问时间公式可扩展。
多级Cache性能示例: 假设L1 Cache命中时间=1ns,命中率=90%;L2 Cache命中时间=10ns,命中率=95%;主存访问时间=100ns。求平均访问时间。
解:
- L1缺失后,访问L2,L2命中的情况:L1缺失率=10%,L2命中率=95%
- L2也缺失后,访问主存:L2缺失率=5%
- 平均访问时间 = 1ns + 0.10 x (10ns + 0.05 x 100ns)
- = 1ns + 0.10 x (10ns + 5ns) = 1ns + 0.10 x 15ns = 1ns + 1.5ns = 2.5ns
💡 记忆技巧:
- 三种映射方式的对比:"直接映射像固定座位(冲突多),全相联像自由选座(冲突少但找座慢),组相联是折中"
- LRU替换口诀:"找最久没用的替换"
- 写策略组合:"写直达+非写分配"是一对,"写回+写分配"是一对,两者通常配对使用。
⚠️ 易错点:
- Cache容量和"行大小"的区别:Cache容量 = 行数 x 每行字节数。不要混淆。
- 直接映射中,主存块号 mod Cache行数,这个mod的结果是行号,不是块内地址。
- 组相联中"路数"的含义:2路组相联 = 每组2行,不是组数为2。
3.4 虚拟存储器
虚拟存储器是操作系统与硬件配合实现的"内存扩充"技术,让每个程序认为自己拥有独立的连续地址空间(虚拟地址),而实际数据可能存放在主存或硬盘上。考研中,这部分与操作系统课程有交叉,但组成原理主要关注地址转换和TLB。
基本概念
- 虚拟地址(逻辑地址):程序使用的地址。
- 物理地址:主存中的实际地址。
- 页:虚拟地址空间和物理地址空间都被划分为固定大小的块(通常4KB)。
- 页表:记录虚拟页到物理页框的映射关系,以及访问权限、有效位等。
虚拟存储器类比:可以把虚拟存储器想象成"酒店房间号"系统。
- 程序使用的是"房间号"(虚拟地址),如808号房。
- 实际上808号房可能在酒店的8层(物理地址),也可能客人还没到(不在主存中,在硬盘上)。
- 前台(MMU+页表)负责把"房间号"映射到实际的房间位置。
- 如果客人还没入住(缺页),就需要从"候补名单"(硬盘)调入房间(内存)。
- TLB就像前台桌上的"常用房间映射快查表",不用每次都去翻大册子(内存中的页表)。
页式虚拟存储器的地址转换
- CPU发出虚拟地址,分为虚拟页号(VPN)和页内偏移。
- 用VPN索引页表,得到物理页框号(PPN)。
- 物理地址 = (PPN << 页内偏移位数) + 偏移。
地址转换示例: 假设页面大小为4KB(2^12=4096字节),页内偏移占12位。某程序访问虚拟地址 0x12345678。
- 虚拟页号 VPN = 0x12345(高20位)
- 页内偏移 = 0x678(低12位)
- 通过页表查得物理页框号 PPN = 0xABCD
- 物理地址 = (0xABCD << 12) + 0x678 = 0xABCD000 + 0x678 = 0xABCD678
快表(TLB,Translation Lookaside Buffer)
- 页表存放在主存中,每次地址转换需要访问主存(慢)。TLB是CPU内部的一个小Cache,用于存放最近使用的页表项。
- 工作过程:CPU先查TLB,若命中(TLB hit),快速得到物理地址;若未命中(TLB miss),则访问主存中的页表,并将该页表项装入TLB(可能替换旧项)。
TLB与Cache的关系(408常见综合题): 在一条指令的执行过程中,数据可能经过以下路径:
- CPU发出虚拟地址
- 查TLB,将虚拟地址转换为物理地址(TLB命中则快,不命中则查页表)
- 用物理地址查Cache(Cache命中则快,不命中则查主存)
- 若Cache和TLB都不命中,则需要访问主存2次(一次查页表,一次读数据)
缺页处理
- 如果页表项的有效位为0(该页不在主存中),则触发缺页异常,操作系统从硬盘读取该页到主存,更新页表,然后重新执行指令。
段式与段页式(简要了解)
- 段式:按逻辑结构(代码段、数据段、堆栈段)划分,每段大小可变。地址转换需段表(基址+限长)。
- 段页式:先分段,每段内再分页。
考研中,组成原理主要考页式虚拟存储器的地址转换、TLB、有效位等。
跨学科联系(操作系统):虚拟存储器是组成原理和操作系统高度交叉的内容。在操作系统中你会学到:
- 页面置换算法(LRU、FIFO、Clock等)
- 页表的具体实现(多级页表、反向页表)
- 缺页中断的处理流程
- 虚拟内存管理的性能分析(有效访问时间公式)
3.5 存储器与CPU的连接(回顾与补充)
前面已讲主存扩展和连接,这里补充一个完整例子:
假设CPU有16位地址总线(A15A0)和8位数据总线,需要用 8Kx8位的RAM芯片组成 32Kx8位的存储器,并给出地址分配(例如 0x00000x7FFF 为RAM)。
- 需4片芯片(32K/8K=4)。
- 使用2-4译码器,用高2位地址(A15、A14)产生片选信号。
- 低13位地址(A12~A0)直接连接到所有芯片的地址引脚(8K=2^13)。
- 数据总线、读写控制并联。
地址空间分配:
| 芯片编号 | A15 | A14 | 地址范围 | 片选条件 |
|---|---|---|---|---|
| 芯片0 | 0 | 0 | 0x0000~0x1FFF | A15=0, A14=0 |
| 芯片1 | 0 | 1 | 0x2000~0x3FFF | A15=0, A14=1 |
| 芯片2 | 1 | 0 | 0x4000~0x5FFF | A15=1, A14=0 |
| 芯片3 | 1 | 1 | 0x6000~0x7FFF | A15=1, A14=1 |
连接图示说明:
A15, A14 -> 2-4译码器的输入
译码器输出 Y0Y3 -> 分别接芯片03的CS引脚
A12A0 -> 所有芯片的地址引脚
D7D0 -> 所有芯片的数据引脚
WE -> 所有芯片的WE引脚
📌 408考点提示
考查形式:
- 选择题高频:Cache映射方式(给定地址求组号/标记/块内偏移)、主存扩展计算芯片数、SRAM/DRAM对比。
- 综合题常考:Cache的性能计算(平均访问时间)、Cache和TLB的联合工作过程。
- 简答/设计题:主存扩展的电路连接(画图或描述连接方式)。
常见命题模式:
- 给定主存地址位、Cache参数(行数、路数、块大小),求地址划分,计算Cache容量。
- 给定命中率、命中时间、缺失开销,计算平均访问时间。
- 给定程序访问序列,分析Cache命中情况(用LRU或FIFO替换)。
- 主存芯片扩展题:给芯片规格和目标规格,要求计算芯片数和画出连接图。
学生常犯错误:
- Cache地址划分时弄混组号和标记的位数。记住:先确定块内偏移(由块大小决定),再确定组号(由组数决定),剩下的位数是标记。
- 平均访问时间公式忘记乘以缺失率(只算了缺失开销,忘了缺失率这个权重)。
- LRU替换时维护访问顺序出错(常见于2路组相联的判断)。
- 主存扩展时地址线的连接弄错(位扩展和字扩展的地址线连接方式不同)。
备考建议:
- Cache地址划分必须熟练掌握,建议做10道以上练习巩固。
- 熟记平均访问时间 = 命中时间 + 缺失率 x 缺失开销,这个公式在408中反复出现。
- LRU替换算法务必动手模拟3~5个例子,理解"最近最少使用"的准确含义。
- 主存扩展题最关键的是判断先位扩展还是先字扩展,以及片选信号的产生方式。
第三部分小结
| 主题 | 核心要点 |
|---|---|
| 存储层次 | 寄存器->Cache(SRAM)->主存(DRAM)->辅存;局部性原理 |
| 主存扩展 | 位扩展、字扩展、字位扩展;地址译码 |
| Cache映射 | 直接、全相联、组相联;标记、索引、偏移 |
| Cache替换 | FIFO、LRU、随机;写策略(写直达/写回,写分配/非写分配) |
| 虚拟存储器 | 页式管理,虚拟地址->物理地址转换,页表,TLB,缺页异常 |