# 存储器层次结构
## 存储器分类
## 层次化存储器的基本结构
## 半导体随机存取存储器
- SRAM存储器
- DRAM存储器
- Flash存储器
## 主存储器
- DRAM芯片和内存条
- 多模块存储器
- 主存和CPU之间的连接
## 外部存储器
- 磁盘存储器
- 固态硬盘
## 高速缓冲存储器
- cache的基本原理
- cache和主存之间的映射方式
- cache中主存块的替换算法
- cache写策略
## 虚拟存储器
- 基本概念
- 页式虚拟存储器
- 段式虚拟存储器
- 段页式虚拟存储器
存储系统
1 - 存储系统
存储系统的层次结构
存储系统是计算机体系结构的核心组成部分之一,用于存储程序指令和数据,以支持处理器的运算需求。为了平衡速度、容量和成本,现代计算机采用了分层存储体系结构,从最快速但容量有限的 寄存器 到容量大但速度较慢的辅助存储设备,形成了存储层次结构。
如上图所示,越靠近金字塔上层的容量越小但速度越快,越靠近金字塔下层的容量越大但速度越慢。计算机的存储系统包含以下层次:
- 寄存器(Registers)
- 寄存器是位于处理器内部的最快速存储单元。
- 它们提供极快的数据访问速度,但其容量非常有限。
- 典型访问延迟:约 0.2~1 ns(通常只需 1 个 CPU 时钟周期)。
- 一级缓存(L1 Cache)
- L1 缓存位于处理器内部,比寄存器空间稍大,速度比 L2 和 L3 Cache 更快。
- 它通常分为 数据缓存(用于存储数据)和 指令缓存(用于存储指令)。
- 典型访问延迟:约 0.5~1.5 ns(约 2~5 个 CPU 时钟周期)。
- 二级缓存(L2 Cache)
- L2 缓存比 L1 缓存大,并且位于处理器和主内存之间。
- 它的速度比 L1 慢,但比主内存快。
- 典型访问延迟:约 2~5 ns(约 10~20 个 CPU 时钟周期)。
- 三级缓存(L3 Cache)
- 在某些系统中,还有 L3 缓存,这是一种更大但速度更慢的缓存。
- 它位于 L2 缓存和主内存之间,旨在进一步减少对主内存的访问。
- 多个 CPU 核心通常共享同一个 L3 Cache。
- 典型访问延迟:约 10~20 ns(约 30~70 个 CPU 时钟周期)。
- 主存(RAM)
- 就是我们常说的内存,比缓存慢,但比硬盘快得多,并且容量比缓存大。
- 主存 用于存储正在运行的程序和当前使用的数据。
- 典型访问延迟:约 50~100 ns(约 150~300 个 CPU 时钟周期)。
- 辅助存储(如硬盘驱动器或固态驱动器)
可以发现,相邻两层存储器之间通常都存在 数倍甚至数十倍 的速度差异。例如,从 L3 Cache 访问数据只需十几纳秒,而访问 主存 往往需要几十纳秒;如果数据需要从 SSD 或 HDD 中读取,则访问时间又会增加到微秒甚至毫秒级,相比 Cache 慢了数千到数百万倍。因此,如果 CPU 每次访问数据都直接访问主存或磁盘,将会有大量时间处于等待状态,计算能力无法得到充分发挥。
存储器层次结构的主要思想是:上一层存储器作为下一层存储器的高速缓存。当 CPU 要读取某个数据时,会优先在速度最快的存储器中查找:
- 先访问 Cache;
- 如果 Cache 中没有(发生 Cache Miss),则访问 主存;
- 如果 主存 中也没有,则访问磁盘,此时数据会先从磁盘调入 主存,再从 主存 调入 Cache,最后供 CPU 使用。
Cache—主存层 主要解决 CPU 和主存之间巨大的速度差异问题。主存和 Cache 之间的数据调动完全由 硬件自动完成,程序员通常无需关心数据何时进入或离开 Cache。
主存—辅存层 主要解决存储系统容量不足的问题。主存和 辅存 之间的数据调动由 硬件和操作系统共同完成(例如虚拟内存、分页机制),对于应用程序员来说也是透明的。程序只需按照统一的地址空间访问数据,而无需关心数据当前位于主存还是磁盘中。
RAM
半导体随机存储器的英文为 Semiconductor Random-Access Memory(RAM)。
首先说明这个名称中各部分的含义:
- 半导体:这种存储器使用硅等半导体材料制造,晶体管是构成半导体存储器的基本器件。
- 随机访问:可以通过地址直接访问存储器中的任意位置,并且访问不同位置所需的时间基本相同。它不同于磁带等需要按照顺序查找数据的存储设备。
随机存储器主要分为 SRAM 和 DRAM 两种。
SRAM
SRAM(Static RAM,静态随机存取存储器)使用触发器构成基本存储单元。一个 SRAM 存储单元通常由 4~6 个晶体管 组成,用于存储 1 bit 数据,如下图所示:
只要电源持续供应,触发器的状态就可以一直保持,因此 SRAM 不需要刷新。
SRAM 主要用于 CPU 的 Cache(L1/L2/L3)、寄存器文件以及嵌入式系统中的小容量高速存储器。
DRAM
DRAM(Dynamic RAM,动态随机存储器)使用 1 个晶体管(Transistor) 和 1 个电容器(Capacitor),即 1T1C 结构,构成基本存储单元。每个存储单元存储 1 bit 数据。
与 SRAM 使用触发器保存状态不同,DRAM 利用电容器中是否存有电荷表示数据。由于电容器会随着时间不断漏电,因此数据不能永久保持,必须定期进行 刷新(Refresh),重新恢复电容器中的电荷。
DRAM 具有 集成度高、成本低、容量大 等特点,因此主要用于计算机的 主存、显卡显存等大容量存储设备。
二维阵列与行列地址
DRAM 和 SRAM 在宏观上都采用 二维存储阵列 组织数据,并通过字线和位线访问存储单元。二者的主要区别在于所使用的 基本存储单元 不同。
DRAM 中:
- 每个 存储单元(Cell) 只能存储 1 bit 数据;
- 电容器(Capacitor) 用于保存电荷:
- 有电荷通常表示 1;
- 无电荷通常表示 0;
- 晶体管(Transistor) 相当于一个开关,由 字线(Word Line) 控制;
由于一个 Cell 只能保存 1 bit 数据,而计算机通常一次需要读写多个 bit,因此一个 可寻址地址(Addressable Location) 往往对应多个 Cell 共同组成。
例如,一个 4 bit 宽的 DRAM 芯片,每个可寻址地址对应 4 个 Cell,分别存储这 4 个 bit。为了实现这一点,DRAM 内部通常采用 多个位平面(Bit Plane) 并行工作的组织方式。
每个位平面都是一个独立的 二维 Cell 阵列,其中的每个 Cell 存储 1 bit 数据。所有位平面具有完全相同的行、列结构,并共享相同的 行地址(Row Address) 和 列地址(Column Address)。
当访问某个地址时,控制器利用相同的行地址和列地址,在所有位平面中同时选中对应位置的 Cell。每个位平面分别读出(或写入)1 bit 数据,最终组合成该地址对应的 4 bit 数据。
需要区分两个概念:
- Cell(存储单元):物理存储的最小单位,只能存储 1 bit。
- Address(可寻址地址):也可以叫做 memory location,指的是 CPU 或内存控制器访问 DRAM 的逻辑单位,一个地址可以对应多个 Cell(如 4 bit、8 bit、16 bit 等)。
DRAM 对外提供的是可寻址地址(Address)。
为了提高存储密度,DRAM 将每个 bit 的存储单元组织成二维 Cell 阵列。访问某个地址时,控制器首先将地址划分为 行地址(Row Address) 和 列地址(Column Address),利用这两部分地址在所有 Cell 阵列中同时选中相同位置的 Cell。
假设每个 Cell 阵列共有 行、 列,则:
- 行地址需要 位;
- 列地址需要 位;
因此,一个可寻址地址共需要
位。
这里的 行地址(Row Address) 和 列地址(Column Address) 是 DRAM 芯片内部 用于定位数据的位置,并不是 CPU 发出的主存地址。
CPU 访问的是整个主存空间中的 物理地址,而 内存控制器 会将该地址转换为 DRAM 内部使用的行地址和列地址,再完成数据访问。因此,教材中讨论 DRAM 时通常只关注 DRAM 内部的地址组织方式,而不涉及 CPU 地址到 DRAM 地址的映射过程。
一种典型的实现方式可以理解为由 4 个位平面(Bit Plane) 组成,每个位平面都是一个独立的 Cell 阵列,负责存储其中 1 bit 数据。
若每个位平面都组织为:
则:
- 每个位平面共有 个 Cell;
- 行地址需要 8 位;
- 列地址需要 8 位;
- 一个可寻址地址由 8 位行地址 和 8 位列地址 组成,共 16 位。
访问某个地址时,控制器首先根据地址得到对应的 行地址 和 列地址,然后利用相同的行地址和列地址,同时访问 4 个位平面中对应位置的 Cell。每个位平面分别读出(或写入)1 bit 数据,最终组合成该地址对应的 4 bit 数据。
因此,对于 64K × 4 bit 的 DRAM:
- 64K 表示共有 64K 个可寻址地址;
- 4 bit 表示每个地址对应 4 个位平面中的 4 个 Cell,共同组成该地址的数据。
行列地址复用
如果将完整地址一次性送入上述 DRAM 芯片,就需要设置 16 根地址引脚。
但是,DRAM 芯片通常容量较大。如果每一位地址都使用一根独立引脚,芯片所需的引脚数量会很多,从而增加封装面积和制造成本。
为了减少地址引脚数量,DRAM 通常采用 行列地址复用(Address Multiplexing):
使用同一组地址引脚,先传送行地址,再传送列地址。
地址访问过程如下:
- 内存控制器先在地址引脚上送入 行地址;
- 通过 RAS(Row Address Strobe,行地址选通信号) 将行地址锁存到 DRAM 内部;
- DRAM 根据行地址激活对应的行;
- 内存控制器再使用同一组地址引脚送入 列地址;
- 通过 CAS(Column Address Strobe,列地址选通信号) 将列地址锁存;
- DRAM 根据列地址,从已经激活的行中选择相应的数据。
因此,行地址和列地址虽然使用相同的引脚传输,但会在不同的时刻分别送入。
采用行列地址复用后,DRAM 所需的地址引脚数量约为:
当行地址位数和列地址位数相同时,只需要原来大约一半的地址引脚。
以前面的 64K × 4 bit DRAM 为例:
- 不采用地址复用:需要 16 根地址引脚;
- 采用地址复用:只需要 8 根地址引脚;
- 这 8 根地址引脚先传输 8 位行地址,再传输 8 位列地址。
DRAM 行列复用实例
假设有一个 16M × 4 bit 的 DRAM 芯片,共有:
个可寻址位置,每个地址可以读写 4 bit 数据。
如果将这些地址组织为:
由于:
因此:
- 行地址需要 12 位;
- 列地址需要 12 位;
- 完整地址需要 24 位。
如果不采用地址复用,需要 24 根地址引脚;采用行列地址复用后,只需要 12 根地址引脚。
这 12 根地址引脚首先传送行地址,随后再传送列地址。
SRAM 内部通常也会将存储单元组织成二维阵列,并使用 行译码器 和 列选择电路 访问具体的存储单元。因此,从内部结构来看,SRAM 同样存在行、列的划分。
但是,普通 SRAM 芯片一般 不采用 DRAM 式的行列地址分时复用。
SRAM 通常会在地址引脚上一次性接收完整地址:
- CPU 或内存控制器一次性送入完整地址;
- SRAM 内部将地址拆分为行地址和列地址;
- 行译码器和列选择电路同时完成地址译码;
- 直接读取或写入对应的数据。
也就是说,SRAM 和 DRAM 的区别不在于内部是否存在行列结构,而在于 外部地址的传输方式:
| 存储器 | 内部组织方式 | 外部地址传输方式 |
|---|---|---|
| DRAM | 二维阵列,分为行和列 | 通常先传行地址,再使用同一组引脚传列地址 |
| SRAM | 二维阵列,分为行和列 | 通常一次性传入完整地址,由芯片内部拆分和译码 |
SRAM 一般不采用行列地址复用,主要有以下原因:
- SRAM 的容量通常比 DRAM 小,所需地址引脚相对较少;
- SRAM 更强调访问速度,一次性输入完整地址可以减少地址传输和锁存步骤;
- SRAM 不需要 RAS、CAS 这样的行列地址锁存过程,控制方式更加直接。
因此:
DRAM 的行列地址复用主要是为了减少芯片引脚数量,但会增加地址传输步骤;SRAM 通常一次性接收完整地址,以获得更低的访问延迟。
刷新机制
由于 DRAM 使用 电容器 保存数据,而电容会不断漏电,因此即使 CPU 完全没有访问某个存储单元,其中保存的数据也会随着时间逐渐消失。
为了保证数据正确,内存控制器(Memory Controller) 必须在规定时间内,通常是几十毫秒内,保证 每一行 至少被刷新一次。
所谓 刷新(Refresh),本质上是读取一行存储单元中的数据,并利用感应放大器将这些数据重新写回,从而恢复电容器中的电荷。
由于 DRAM 的存储阵列按行和列组织,同一行上的所有存储单元共享一条字线,因此刷新操作也是 按行进行的。一次刷新会恢复整行存储单元中的电荷,而不是逐个存储单元进行刷新。
刷新期间,被刷新的存储体不能正常提供读写服务,因此不同的刷新策略会直接影响 DRAM 的访问性能。
刷新方式
DRAM 常见的刷新方式包括:
- 集中刷新(Burst Refresh):在一个刷新周期内,集中安排一段时间连续刷新所有行。控制简单,但连续刷新期间无法进行正常访问,会形成较长的停止访问时间。
- 分散刷新(Distributed Refresh):将刷新操作分散到各个 存储周期 中,使刷新操作与正常读写操作规律地交替进行。不会形成很长的集中停顿,但会频繁占用存储周期。
- 异步刷新(Asynchronous Refresh):在整个刷新周期内均匀安排刷新操作,每隔一定时间刷新一行。它避免了集中刷新的长时间停顿,同时又不要求每个存储周期都执行刷新,是集中刷新和分散刷新的一种折中方式。
三种刷新方式在时间轴上的 CPU 等待窗口对比如下图所示:
它们的特点如下:
| 特征 | 集中刷新 | 分散刷新 | 异步刷新 |
|---|---|---|---|
| 刷新方式 | 在一段时间内连续刷新所有行 | 刷新与正常访问逐周期交替 | 每隔一定时间刷新一行 |
| CPU 等待 | 等待时间长,但集中出现 | 每次等待时间短,但非常频繁 | 等待时间短,并且均匀分布 |
| 对性能影响 | 存在明显的停止访问时间 | 持续占用部分存储周期 | 通常比前两种方式更加均衡 |
| 控制复杂度 | 低 | 中 | 较高 |
| 典型特点 | 存在“死时间” | 没有长时间停顿,但刷新频繁 | 集中刷新与分散刷新的折中 |
SRAM 对比 DRAM
| 特性 | DRAM | SRAM |
|---|---|---|
| 存储单元 | 1 个晶体管 + 1 个电容器(1T1C) | 4~6 个晶体管组成的触发器 |
| 数据保持方式 | 使用电容器保存电荷 | 使用触发器保持状态 |
| 是否需要刷新 | 需要定期刷新 | 无需刷新 |
| 内部阵列 | 通常采用二维行列阵列 | 通常也采用二维行列阵列 |
| 外部地址输入 | 通常采用行列地址复用 | 通常一次性输入完整地址 |
| 访问速度 | 较慢 | 更快 |
| 集成度 | 高 | 较低 |
| 成本 | 低 | 高 |
| 容量 | 大 | 小 |
| 典型应用 | 主存、显存 | CPU Cache、寄存器文件 |
ROM
ROM(Read-Only Memory,只读存储器)是一类 非易失性(Non-Volatile) 存储器,断电后仍能保存数据,主要用于存放系统启动程序、固件(Firmware)以及其他长期不需要频繁修改的数据。
虽然名称叫做 只读存储器(Read-Only Memory),但这里的"只读"是相对于 正常工作状态 而言的。
- 在计算机运行过程中,CPU 可以 随机读取(Random Access) ROM 中任意地址的数据;
- 但一般不能像 RAM 一样随时写入数据。
- 某些现代 ROM(如 EPROM、EEPROM、Flash)虽然支持重新编程,但写入通常需要专门的硬件、电压或擦除操作,写入速度也远低于 RAM。
因此,现代教材中的 ROM 更准确地理解为 非易失性存储器,而不是绝对不能写入的存储器。
ROM 最典型的用途是存储 固件(Firmware)。固件是直接嵌入硬件中的底层程序,负责设备初始化和基本控制。例如:
- PC 主板中的 BIOS/UEFI
- 路由器、交换机中的启动程序
- 各种嵌入式设备(单片机、家电、汽车 ECU)的控制程序
ROM 对比 RAM
| 特征 | RAM(随机存取存储器) | ROM(只读存储器) |
|---|---|---|
| 易失性 | 易失性,断电后数据丢失 | 非易失性,断电后数据仍保留 |
| 读写能力 | 可随机读、随机写 | 可随机读,一般不能在运行过程中随机写 |
| 数据修改 | 可频繁修改 | 一般很少修改,需要专门方式写入 |
| 访问速度 | 较快 | 通常慢于 RAM |
| 用途 | 存放正在运行的程序和数据 | 存放固件、启动程序、固定数据 |
| 容量 | 一般较大 | 一般较小 |
需要注意:
RAM 和 ROM 都支持随机存取(Random Access),区别并不在于是否能够随机访问,而在于 是否能够方便地写入以及是否断电保存数据。
分类
主要了解下面几种 ROM。
MROM
MROM(Mask ROM,掩膜 ROM)是在芯片制造过程中直接写入数据的 ROM。
其内容在出厂后便无法修改,因此成本低、可靠性高,适合大批量生产。
PROM
PROM(Programmable ROM,可编程 ROM)允许用户在出厂后 写入一次数据。
一旦完成编程,就无法再次修改,因此又称 一次性可编程 ROM(OTP,One-Time Programmable)。
EPROM
EPROM(Erasable Programmable ROM,可擦除可编程 ROM)可以通过 紫外线照射 将整个芯片中的数据全部擦除,然后重新写入。
其特点是:
- 可以反复编程;
- 必须取下芯片并使用紫外线擦除;
- 擦除时间较长,操作较为繁琐;
- 支持随机读取。
早期 PC 的 BIOS 芯片曾大量采用 EPROM。
EEPROM
EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程 ROM)使用 电信号 完成擦除和写入,不再需要紫外线。
相比 EPROM:
- 不需要拆卸芯片;
- 可以在电路板上直接更新内容;
- 支持按字节(Byte)擦除和写入;
- 写入速度较慢,擦写次数有限。
Flash
Flash Memory(闪存) 是 EEPROM 的一种改进形式,也是现代最广泛使用的非易失性存储器。
与 EEPROM 相比:
- 按块(Block)擦除,因此速度更快;
- 存储密度更高;
- 成本更低;
- 擦写寿命有限(通常几千到几十万次)。
目前大量设备都采用 Flash 存储,例如:
- SSD(固态硬盘)
- U 盘
- SD 卡
- 手机存储
- BIOS/UEFI 芯片
可以,加一个 Note,这也是很多教材容易让初学者困惑的地方。
在现代计算机体系结构中,ROM(Read-Only Memory)已经不再表示"绝对不能写"的存储器,而是泛指非易失性(Non-Volatile Memory,NVM),即断电后仍能保存数据的存储器。
从历史上看,最早的 ROM 在制造完成后内容便固定,用户无法修改,因此称为 Read-Only Memory。后来出现了 PROM、EPROM、EEPROM,以及今天广泛使用的 Flash Memory,它们都能够重新写入数据,只是写入速度较慢、需要特殊的擦除操作(按字节或按块擦除),因此仍然沿用了 ROM 这一名称。
因此,Flash 在广义上属于 ROM(非易失性存储器),但它并不是严格意义上的"只读"存储器,而是一种可擦除、可重新编程的 ROM。现代 SSD、U 盘、SD 卡、手机存储以及 BIOS/UEFI 芯片,都是以 Flash 为存储介质。
CD-ROM
CD-ROM(Compact Disc Read-Only Memory,只读光盘)是一种光盘存储设备。
数据在生产过程中一次性写入,用户不能修改,只能读取,因此属于典型的只读存储介质。
其特点包括:
- 非易失性;
- 容量约 700 MB;
- 使用激光读取数据;
- 访问速度远低于半导体存储器。
需要注意,CD-ROM 属于外存(辅助存储器),而前面介绍的 MROM、PROM、EPROM、EEPROM、Flash 都属于 半导体 ROM。
- ROM 属于非易失性存储器,RAM 属于易失性存储器。
- ROM 和 RAM 都支持随机存取,不要把 ROM 理解成顺序访问。
- Flash Memory 属于 ROM,是现代应用最广泛的非易失性存储器。
- EPROM 使用紫外线擦除,EEPROM 使用电擦除,Flash 按块擦除。
2 - 多模块存储器
多模块存储器(Multi-module Memory)是指利用多个结构相同的存储模块(或存储芯片)共同构成一个主存系统。
采用多个存储模块主要有两类目的:
- 提高主存的访问性能
- 单体多字存储器
- 多体交叉存储器
- 高位交叉编址
- 低位交叉编址
- 扩大主存容量
- 位扩展
- 字扩展
- 字位扩展
虽然这两类技术都使用多个存储模块,但它们的设计目标完全不同:
- 单体多字存储器 和 多体交叉存储器 的目的是提高主存的带宽和吞吐率;
- 位扩展、字扩展和字位扩展 的目的是扩大主存的字长或容量。
因此,本节也将分别介绍这两类技术。
单体多字存储器
在介绍单体多字存储器之前,需要先明确一个概念。
通常情况下,一个地址只能对应一个存储字(Word)(这里假设采用 按字编址)。例如,一个字长为 32 位 的存储器:
| 地址 | 数据 | 地址 | 数据 | 地址 | 数据 | 地址 | 数据 |
|---|---|---|---|---|---|---|---|
| 0 | Word0 | 1 | Word1 | 2 | Word2 | 3 | Word3 |
CPU 每发出一个地址,只能读出 一个字(32 位)。
单体多字存储器 则是在一个地址下,同时存放多个连续的存储字。
假设每个字仍然为 m 位,但每个地址对应 n 个连续的字,则:
| 地址 | 数据 |
|---|---|
| 0 | Word0、Word1、Word2、Word3 |
| 1 | Word4、Word5、Word6、Word7 |
| 2 | Word8、Word9、Word10、Word11 |
例如,当 n=4 时,CPU 发出一次地址访问,就能够一次读出 4 个连续的字(共 4m 位)。
因此:
- 每个地址对应 n 个连续的字;
- 一个地址访问即可返回 n 个字;
- 数据总线宽度需要扩大为 n×m 位;
- 理论上,存储器带宽可以提高 n 倍。
这种方式本质上是扩大一次访问的数据宽度,而不是让多个存储模块并行处理不同的访问请求。
由于单体多字存储器一次返回的是 固定连续的 n 个字,因此它只能提高顺序访问时的数据传输效率,对于多个独立地址的访问无法提高并行性。
考试中一般只需了解其基本思想即可,真正的重点是 多体交叉存储器,它利用多个存储模块并行工作来提高主存的访问带宽。
多体交叉存储器
在 多体交叉存储器 的设计中,为了提高存储系统的并行性和带宽,常采用 交叉编址 的方式将主存划分为多个 存储体(memory bank)。根据地址在各存储体之间的分布方式,交叉编址又分为 高位交叉编址 和 低位交叉编址 两种。
高位交叉编址
在 高位交叉编址 中,地址的高位 用于选择 存储体,低位 表示在该存储体中的偏移地址。
例如:若系统有 4 个存储体,地址空间大小为 4n,则:
- 地址 0 ~ n−1 存储在 M0;
- 地址 n ~ 2n−1 存储在 M1;
- 地址 2n ~ 3n−1 存储在 M2;
- 地址 3n ~ 4n−1 存储在 M3。
也就是说,一整个存储体连续存储一段地址空间,相邻地址数据往往在同一个存储体中。如下图所示:
由于这种方式下,相邻数据集中在一个存储体中,多个存储体无法并行工作,而是 串行工作:
- 所有存储体共用一个 地址寄存器(AR) 和 数据寄存器(DR);
- 每次只能访问一个存储体,其它存储体处于空闲状态。
这种串行访问的结构较为简单,适用于对并行性能要求不高的场景,但 无法提升带宽或访问效率,无法发挥出多体结构的优势。
高位交叉编址 尽管名称中带有"交叉",但实际上地址在各存储体之间是按连续区间分布的,因此不少教材也称其为 高位连续编址。
国内《计算机组成原理》教材(如唐朔飞、白中英、王道等)通常都是这样分类:
多体存储器
- 高位交叉编址
- 低位交叉编址
这里的"交叉"实际上指的是:
多体存储器的一种编址方式
而不是说数据一定是交替存放的。
低位交叉编址
在 低位交叉编址 中,地址的低位 用于选择 存储体,高位 用于标识该存储体内的偏移地址。
以 4 个存储体为例,地址 0~3 分别对应 M0、M1、M2、M3,地址 4~7 也分别映射到 M0~M3,以此类推。这样就实现了 相邻地址分散存储在不同存储体中 的效果:
在这种方式下,多个存储体可以 并行工作,大大提高了访问效率。但为了支持并行,每个存储体 都需配备自己的地址寄存器和数据寄存器,如下图所示:
并行性
在 低位交叉编址 中,相邻地址的数据分布在不同的存储体中,因此多个存储体可以交替处理连续的访存请求。这种工作方式与 指令流水线 十分相似,可以显著提高主存的访问带宽。
要理解低位交叉存储器为什么能够实现这种并行访问,首先需要了解 存储周期 的概念。
存储周期 是指某个存储体从开始一次读/写操作,到能够再次接受下一次访问请求所需要的最短时间。
例如,一个存储体的存储周期为 40 ns,意味着:
- 第 0 ns 开始访问该存储体;
- 在接下来的 40 ns 内,该存储体一直处于忙碌状态,不能再次被访问;
- 只有到 40 ns 后,才能再次访问该存储体。
如果系统只有 一个存储体,那么 CPU 连续访问相邻地址时,就必须等待该存储体完成当前访问后才能开始下一次访问,因此连续两次访存之间的最短间隔就是 存储周期。
而在 低位交叉编址 中,连续地址会被轮流分配到不同的存储体。例如对于四体交叉存储器:
- 地址 0 → M0
- 地址 1 → M1
- 地址 2 → M2
- 地址 3 → M3
- 地址 4 → M0
- …
这样,当 M0 正在执行一次访存时,CPU 可以立即向 M1、M2、M3 发起新的访存请求,而无需等待 M0 完成。当再次轮到访问 M0 时,它通常已经完成了上一轮访问,因此不会发生冲突。
设主存共有 个存储体,每个存储体的存储周期为 ,则在理想情况下,系统可以实现:
也就是说:
- 为单个存储体的存储周期;
- 为存储体数量;
因此,整个主存系统可以平均每 秒连续输出一个字的数据,主存带宽相比单体存储器提高了约 倍。
这种并行访问之所以能够实现,是因为一次访存过程实际上可以 划分为多个阶段。为了便于分析,通常将一次读取操作抽象为三个阶段:
- :送地址和读命令(将地址送入存储器 AR)
- :存储体内部完成读操作(数据读入 DR,该阶段耗时最长,也是存储周期的主要组成部分)
- :将数据从 DR 传送给 CPU
由于不同存储体相互独立,当一个存储体仍处于 阶段时,CPU 已经可以开始向另一个存储体执行 。于是多个存储体的不同阶段便可以相互重叠执行,其执行过程与指令流水线十分相似。
假设 CPU 的时钟周期为 ,其中:
- 耗时 ;
- 耗时 ;
- 耗时 。
则对于四体交叉存储器,连续读取八个字的数据时,其流水线执行过程如下图所示:
可以看到,虽然每个存储体仍然需要较长的存储周期才能完成一次访问,但多个存储体的访问过程彼此交错、重叠执行,因此主存能够持续不断地向 CPU 输出数据,从而显著提高整体的吞吐率。
主存容量的扩展
在设计计算机存储系统时,单个存储芯片的容量和字长往往无法直接满足系统需求。例如,CPU 可能要求主存具有更大的存储容量,或者一次能够读写更宽的数据,而单个存储芯片只能提供有限的 存储字数 和 字长。因此,需要将多个存储芯片按照一定方式组合起来,构成满足要求的主存,这一过程称为 主存容量的扩展。
设某存储芯片具有:
- 存储字数:
- 字长:
则该存储芯片可表示为:
即共有 个存储单元,每个存储单元存放 数据。

根据扩展目标的不同,主存容量扩展主要分为三种方式:
- 位扩展:保持存储字数不变,扩展每个存储字的位数(字长)。
- 字扩展:保持字长不变,扩展存储字数(地址空间)。
- 字位扩展:同时扩展字长和存储字数。
位扩展法

字扩展法

字位扩展法

主存扩展和多体交叉存储器有什么关系
多体交叉存储器 和 主存扩展 都需要使用多个存储模块(或存储芯片),因此都会涉及 高位编址 和 低位编址 的地址分配方式。但两者的设计目标完全不同:
- 多体交叉存储器:通过将地址分布到多个存储体,提高主存的访问带宽和吞吐率。
- 主存扩展:通过连接多个存储芯片,扩大主存的容量或字长。
由于地址映射方式相同,因此主存扩展中也可以借用前面介绍的 高位编址 和 低位编址 来理解:
- 位扩展 采用 低位编址。多个存储芯片并行工作,共同组成一个更宽的数据字。
- 字扩展 采用 高位编址。高位地址用于选择存储芯片,低位地址作为芯片内部地址。
- 字位扩展 同时进行位扩展和字扩展,因此同时采用高位和低位编址。
位扩展
位扩展 是指:在 存储字数不变 的前提下,增加每个存储字的 位数,也就是扩展存储器的 字长。
例如,要用若干片 的存储芯片构成 的存储器:
- 原芯片:共有 个存储单元,每个单元 。
- 目标存储器:仍然有 个存储单元,但每个单元变为 。
因此,存储字数没有变化,只是每个存储字从 扩展到 ,这就是 位扩展。
需要的芯片数量为:
也就是说,需要 4 片 的芯片并行工作,共同组成一个 的存储器。
由于是 位扩展,所以 4 片芯片在逻辑上对应同一个地址范围。CPU 每访问一个地址,4 片芯片都要同时被选中:
- 4 片芯片的 片选信号连接在一起,并处于常有效状态;
- 4 片芯片的 地址线连接相同的地址信号;
- 每片芯片提供 数据中的 。
因此,一次访问时:
- 第 1 片芯片提供 ;
- 第 2 片芯片提供 ;
- 第 3 片芯片提供 ;
- 第 4 片芯片提供 。
这样,4 片芯片并行输出,就能共同组成一个 的存储字。
需要注意的是,如果目标存储器按 字节编址,那么 的存储器总容量为:
所以 CPU 需要 根地址线,即 。
但是,每片 的芯片内部只有 个存储单元,因此只需要:
根地址线。
由于目标存储器每个存储字为 ,CPU 地址中的低两位 用来表示一个 32 位字内部的 4 个字节偏移,因此不接入芯片地址端。
所以,芯片实际使用的地址线是:
也就是说:
- :用于选择芯片内部的 个存储单元;
- :表示字内字节偏移,不参与芯片内部寻址;
- 4 片芯片并行工作,共同扩展数据位宽。
需要注意的是,位扩展只是多个芯片共同组成同一个存储字,并不是一次返回多个连续的存储字,因此与单体多字存储器不同。CPU 一次访问仍然只读出一个 Word,只不过这个 Word 的位数变宽了。
字扩展
字扩展 是指:在 每个存储字位数不变 的前提下,增加存储器的 存储字数,也就是扩展存储器的地址范围。
例如,要用若干片 的存储芯片构成 的存储器:
- 原芯片:共有 个存储单元,每个单元 。
- 目标存储器:共有 个存储单元,每个单元仍然是 。
因此,存储字长没有变化,只是存储字数从 扩展到 ,这就是 字扩展。
需要的芯片数量为:
也就是说,需要 4 片 的芯片分别负责不同的地址范围。
目标存储器共有 个地址单元,因此需要:
根地址线,即 。
每片 芯片内部有 个存储单元,因此每片芯片内部寻址只需要:
根地址线。
所以:
- 低 14 位地址线 接到每片芯片的地址端,用于芯片内部寻址;
- 高 2 位地址线 接到片选译码器,用于选择当前访问哪一片芯片。
由于 4 片芯片分别对应 4 个连续的 地址空间,所以高两位地址可以区分这 4 片芯片:
| 高位地址 | 被选中的芯片 | 地址范围 |
|---|---|---|
| 第 1 片 | ||
| 第 2 片 | ||
| 第 3 片 | ||
| 第 4 片 |
因此,在 字扩展 中:
- 数据线位数不变,仍然是 ;
- 地址范围扩大;
- 同一时刻只有一片芯片被片选信号选中;
- 高位地址线通常用于产生片选信号。
字位扩展
字位扩展 是指:同时扩展存储器的 存储字数 和 存储字长。
也就是说,目标存储器相比原存储芯片:
- 地址范围更大;
- 每个存储字的位数也更宽。
例如,要用若干片 的存储芯片构成 的存储器:
- 原芯片:共有 个存储单元,每个单元 。
- 目标存储器:共有 个存储单元,每个单元 。
因此,这里既要把存储字数从 扩展到 ,又要把字长从 扩展到 ,所以属于 字位扩展。
需要的芯片数量为:
可以把这 4 片芯片分成两组:
- 每组 2 片芯片并行工作,用来把字长从 扩展到 ;
- 共 2 组芯片,用来把存储字数从 扩展到 。
也就是说:
其中:
- 一个 用于 位扩展;
- 一个 用于 字扩展。
目标存储器容量为:
如果按 字节编址,则 CPU 需要:
根地址线,即 。
每片 芯片内部有 个存储单元,因此每片芯片内部寻址需要 14 根地址线。
由于目标存储器每个存储字为 ,所以最低位地址线 用来表示一个 16 位字内部的字节偏移,不接入芯片地址端。
因此,芯片内部地址线使用:
高位地址线 用来区分两组芯片,产生片选信号:
| 高位地址 | 被选中的芯片组 | 地址范围 |
|---|---|---|
| 第 1 组 | 前 个 16 位字 | |
| 第 2 组 | 后 个 16 位字 |
每组内部的两片芯片并行工作:
- 一片提供 ;
- 一片提供 。
因此,在 字位扩展 中:
- 通过并联芯片的数据线实现 位扩展;
- 通过高位地址线和片选译码实现 字扩展;
- 同一时刻只选中一个芯片组;
- 被选中的芯片组内部,多个芯片并行工作,共同组成更宽的数据字。
3 - 外存
机械硬盘
机械硬盘(Hard Disk Drive, HDD)是一种使用磁性存储介质来存储和读取数据的非易失性存储设备,广泛应用于计算机和数据存储系统中。
存储区域
为了理解机械硬盘是如何存储数据的,需要先了解硬盘的物理结构以及几个基本概念:
- 盘片(Platter):盘片是机械硬盘中用于存储数据的圆形磁性介质,通常由铝合金或玻璃制成,表面覆盖有磁性材料。一个机械硬盘通常包含多个盘片,这些盘片固定在同一根主轴(Spindle)上,以相同的速度高速旋转(如 5400 RPM、7200 RPM 或更高)。
- 盘面(Surface):每个盘片都有 上下两个盘面,每个盘面都可以独立存储数据,因此每个盘面都会配备一个对应的 磁头(Head)。例如,一个拥有 3 个盘片的硬盘共有 6 个盘面,因此通常也会配备 6 个磁头。
- 磁头(Head):磁头安装在读写臂(Actuator Arm)的末端,用于读取和写入盘面上的数据。所有磁头固定在同一个执行机构上,因此它们会同步移动到相同的半径位置,但只能选择其中一个磁头进行读写。
- 磁道(Track):磁道是盘面上的一个同心圆环。每个盘面都由许多同心圆组成,每个同心圆就是一条磁道,数据便存储在这些磁道上。
- 柱面(Cylinder):柱面是所有盘面上 半径相同的磁道 的集合。由于所有磁头始终同步移动,因此当磁头移动到某一个半径时,所有盘面都会同时到达对应的磁道,这些磁道共同构成一个柱面。
- 扇区(Sector):扇区是磁盘上的最小物理存储单元。每条磁道都会被划分成多个扇区,每个扇区可以存储一定数量的数据,通常为 512 字节 或 4 KB(Advanced Format)。操作系统和磁盘控制器通常以扇区作为读写数据的基本单位。
机械硬盘的数据组织层次可以总结为:
硬盘
└──盘片(Platter)
└──盘面(Surface)
└──磁道(Track)
└──扇区(Sector) ← 最小物理读写单位(512B 或 4KB)
CHS 地址
当磁盘驱动器访问磁盘中的数据时,需要根据 CHS 地址(Cylinder-Head-Sector Address,柱面 - 磁头 - 扇区地址) 定位到目标扇区。
CHS 地址由 柱面号(Cylinder Number)、磁头号(Head Number) 和 扇区号(Sector Number) 三个字段组成,它们共同唯一标识磁盘上的一个物理扇区:
柱面号(Cylinder Number):目标扇区所在的柱面
若需要对所有柱面进行二进制编码,则至少需要:磁头号(Head Number):目标扇区所在的盘面(对应具体磁头)
若需要对所有盘面进行二进制编码,则至少需要:扇区号(Sector Number):目标扇区在目标磁道上的编号
若需要对所有扇区进行二进制编码,则至少需要:
一个磁盘通常由多个盘片组成,每个盘片有两个盘面,每个盘面对应一个磁头。每个盘面划分为多个同心圆磁道,每条磁道又划分为若干个扇区。
访问数据时,磁头首先移动到目标柱面,然后选择对应盘面的磁头,待目标扇区旋转到磁头下方后,即可开始读取或写入数据。
示例
假设一个磁盘有 1000 个柱面、4 个盘面、32 个扇区/磁道:
- 柱面号位数: 位
- 磁头号位数: 位
- 扇区号位数: 位
- CHS 地址总位数: 位
因此,一个 CHS 地址需要 17 位 才能唯一表示该磁盘上的任意一个扇区。
例如,C = 500、H = 2、S = 15 表示第 500 个柱面、第 2 个盘面上的第 15 个扇区。磁盘驱动器根据该 CHS 地址即可精确定位到对应的物理扇区。
磁盘性能指标
平均存取时间
平均存取时间是磁盘完成一次读写操作的平均耗时,由 寻道时间(Seek Time)、旋转延迟(Rotational Latency)和 传输时间(Read/Write Time)这三部分构成:
- 寻道时间:磁头从当前位置移动到目标磁道所需的时间,通常与磁头移动距离和致动器性能有关。
- 旋转延迟:盘片旋转使目标扇区到达磁头下方所需的时间,通常为盘片旋转半圈的平均时间,取决于转速。
- 传输时间:读取或写入目标扇区数据的实际时间,与扇区大小和数据传输率相关。
平均存取时间的计算公式为:
数据传输率
数据传输率 表示磁盘每秒向主机传输数据的字节数,反映硬盘的读写速度。假设磁盘转速为 传/秒,每条磁道容量为 字节,则数据传输率 为:
是否每条磁道的扇区数量都一致?
早期 CHS 时代,为了简化控制器设计,每个磁道通常划分为相同数量的扇区。
后来出现了 Zone Bit Recording(ZBR) 技术,由于外圈磁道周长比内圈更长:
外圈磁道可以容纳更多扇区; 内圈磁道容纳较少扇区。
RAID
RAID 全称为 独立磁盘冗余阵列(Redundant Arrar of Independent Disks)
由于磁盘存储介质数据的可靠性容易受到环境影响,而发生数据错误的代价非常大,因此需要考虑存储的容灾与恢复。
RAID 将多个独立的物理磁盘组成一个独立的逻辑盘,数据在多个物理盘上分割交叉存储、并行访问,具有更好的存储性能、可靠性和安全性。
RAID 的实现涉及以下核心技术:
- 磁盘镜像:将相同数据写入多块磁盘,提高可靠性。典型如 RAID1。
- 条带化:将数据分段交叉存储在多个磁盘上,提升性能。典型如 RAID0,但不具备容错能力。
- 奇偶校验:通过冗余校验位在部分磁盘损坏时重建数据,兼顾可靠性与存储效率。用于 RAID3/5/6。
- Cache 机制:提高读写性能,但本身不增加可靠性,除非使用掉电保护的写缓存。
常见 RAID 等级如 RAID1(镜像)、RAID5(条带化 + 奇偶校验)、**RAID6(双重奇偶校验)**等在性能与容错能力之间做出不同权衡。
固态硬盘

固态硬盘(Solid State Drive, SSD)是一种使用闪存(NAND Flash)作为存储介质的非易失性存储设备,与传统机械硬盘(HDD)相比,SSD 在性能、耐用性和能效等方面具有显著优势。SSD 具备以下特点:
- 无机械部件:SSD 没有旋转盘片、磁头或机械臂等移动部件,它使用闪存存储芯片来存储数据。这意味着它不会受到机械故障的威胁,具有更高的耐用性。
- 更快的读写速度:SSD 的读写速度远远超过传统的机械硬盘,因为数据可以立即访问,无需等待盘片旋转和磁头寻道。这使得计算机启动更快,应用程序响应更迅速。
- 低访问时间:由于 没有机械延迟,SSD 的访问时间极低,通常在微秒级别。这有助于加快文件读取和数据检索。
- 长寿命:虽然每个存储单元有写入次数的限制,但现代 SSD 使用错误检查和纠正(ECC)技术,以延长其寿命,并且通常拥有较长的保修期。
- 无碎片化:SSD 的数据存储方式基于闪存单元,读取速度不受数据物理位置的影响,因此不会像 HDD 那样因数据碎片化而降低性能。
- 局限性:
- 容量限制:SSD 的高容量型号(例如 4TB 以上)价格昂贵,而 HDD 在大容量存储上更具优势。
- 写入寿命有限:尽管现代技术已大幅延长寿命,但重度写入场景下仍需关注寿命问题。
机械和固态硬盘对比
以下是从多个维度对 SSD 和 HDD 进行的详细对比:
| 特性 | SSD(固态硬盘) | HDD(机械硬盘) |
|---|---|---|
| 存储介质 | 闪存(NAND Flash) | 旋转盘片 + 磁头 |
| 机械部件 | 无,纯电子存储 | 有,盘片、磁头、机械臂等 |
| 读写速度 | 极快(500 MB/s 至 7000 MB/s,视接口而定) | 较慢(100-200 MB/s) |
| 随机访问时间 | 极低(0.1 毫秒以下) | 较高(5-10 毫秒) |
| 耐用性 | 抗震抗摔,无机械故障风险 | 易受震动、摔落影响,机械故障风险较高 |
| 噪音 | 无噪音 | 有盘片旋转和磁头移动的噪音 |
| 重量与体积 | 轻薄,适合移动设备 | 较重,体积较大 |
| 碎片化问题 | 无需碎片整理 | 需定期碎片整理以维持性能 |
| 适用场景 | 系统盘、高性能计算、移动设备 | 大容量存储、备份、成本敏感场景 |
4 - Cache
这个没必要多说了,408 中绝对的重点,每年都必考而且占分值很多,cache 和 虚拟页式存储器 在 计算机组成原理/操作系统 中的地位就是 耶路撒冷 在西方的地位。
一整章都是重点,映射方式、地址结构、写策略等都务必 深入 掌握。
cache 原理
缓存 是一种临时存储数据的硬件或软件组件,旨在加快后续对该数据的访问速度。当您请求数据时,计算机会先检查 缓存 中是否存在该数据。如果存在(称为 “缓存命中”,Cache Hit),则可以直接从 缓存 中获取数据,而无需访问内存,从而节省时间和资源。如果 缓存 中不存在该数据(称为 “缓存未命中”,Cache Miss),则需要从内存获取数据,并将其存储在 缓存 中,以备将来使用。
cache 工作原理
缓存(cache)是计算机系统中的一种用于加速数据访问的技术,其原理是在高速存储介质中暂时存储常用数据,以便更快地满足后续的访问请求。
缓存 对于程序执行的加速主要来自于 计算机程序 的 时间局部性(Temperal Locality)和 空间局部性(Spatial Locality):
时间局部性
时间局部性指的就是 “刚刚用过的数据,很可能很快又会被用到。
这种特性常见于 循环结构和频繁访问的变量,例如循环中的计数器或者经常读取的配置值:
int sum = 0;
int arr[1000];
// 初始化数组
// ....
// 访问同一个变量 i 很多次
for (int i = 0; i < 1000; i++) {
sum += arr[i]; // 访问 arr[i]
sum += arr[i]; // 再次访问 arr[i]
}
在 缓存 设计中,利用 时间局部性 意味着一旦数据被加载到 缓存 中,它应该在那里保留一段时间,因为很可能很快会再次需要它。
空间局部性
如果一个数据项被访问,那么存储在其附近的数据项也很可能在不久的将来被访问。
这种特性在数组遍历或结构体访问时尤为明显,因为这些数据元素通常在内存中是连续存储的。
利用 空间局部性 的 缓存 设计会在访问一个数据项时,同时把它附近的数据也加载到 缓存 中,因为这些数据很可能在接下来的操作中被用到。
cache 概念
- cache 行(cache line):cache 行 中包含 各种标记字段(flag)和 数据(cache 块)
- 缓存块(cache 块):cache 中的一块存储空间,是与 主存 进行数据交换的基本单元
- 主存块:主存 中的一块存储空间,主存块 的大小一般与 cache 块 一致
- 块内偏移:某一个地址在块内的偏移,找到对应的块后,通过块内偏移找到该地址的具体位置
- 块大小:用于判断块内偏移的位数,比如 ,所以 1KB 的块对应的块内偏移位数为 10
缓存块
Cache block(缓存块),是计算机系统中用于存储的最小数据单元,它是 缓存 中的一个固定大小的数据块。每个 缓存块 包含一定数量的字节或字(通常是 2 的幂次方个字节),并用于存储从 主存(或更低级别的 缓存)中加载的数据。
cache block 的目的是为了方便对于 主存(main memory)数据的 缓存,主存块 的大小与 缓存块 大小一致,这样就可以将 缓存块 和 主存块 对应起来。
cache 中的存储空间可以被分为若干个 cache 块,主存 也可以被分为若干个 主存块,主存 和 cache 间的数据置换是以 块 为基本单位的。
这可以和页式内存进行类比,虚拟内存和物理内存都被分为若干个页面,物理内存空间的置换以 页面 为基本单位。
缓存块的大小?
Cache block 的大小在不同计算机体系结构中可以有所不同,通常以字节(bytes)或字(words)为单位来表示。典型的 缓存块 大小可以是 32 字节、64 字节、128 字节等。较大的 缓存块 可以容纳更多的数据,提高了数据的局部性,但在某些情况下,较小的 缓存块 可能更适合,特别是对于小规模的数据访问。
cache 和主存映射方式
Cache 的容量远小于主存,而主存中的任意一个数据块在程序运行过程中,都有可能被调入 Cache。 因此,体系结构必须回答一个核心问题:
当某个主存块需要进入 Cache 时,它可以(或应该)放到 Cache 的哪个位置?
这个从 主存块 → Cache 块 的对应规则,就称为 映射方式(Mapping)。
从抽象角度看,映射本质上定义了三件事:
- 可放置性:
一个主存块,允许放入 Cache 的哪些位置? - 唯一性或灵活性:
是只能放到一个固定位置,还是可以放到多个位置,甚至任意位置? - 硬件代价与性能权衡:
放得越自由,命中率越高,但查找与比较逻辑越复杂; 放得越受限,硬件越简单,但冲突失效(conflict miss)越频繁。
不同的映射方式,本质上就是在 命中率、访问速度、硬件复杂度 三者之间做不同取舍。
按照“一个主存块能映射到多少个 Cache 块”这一自由度的不同,常见的映射方式可以分为:
- 直接映射(Direct Mapped):只能映射到 唯一一个 Cache 块
- 全相联映射(Fully Associative):可以映射到 任意一个 Cache 块
- 组相联映射(Set Associative):只能映射到 某一组中的任意一个 Cache 块
下面我们从最简单、硬件代价最低的直接映射开始分析。
直接映射
在 直接映射(directed mapped)缓存 中,每个 主存块 只能映射到缓存中的 一个特定缓存块。这意味着每个 主存块只有一个缓存块可以存储它。
主存块号 映射到缓存块号的计算公式为:
其中:
- 为主存块号(从 0 开始编号),
- 为缓存中的总块数。
直接映射例子
假设我们有一个 256KB 的缓存,其中每个缓存块是 64B,对于直接映射的 cache:
- 缓存 被分为
256KB / 64B = 4096个 缓存块。 - 内存 中的数据可以直接映射到这 4096 个位置中的其中一个,比如第 10000 个 主存块 映射到
10000 % 4096 = 1808个 缓存块。 - 当一个新的数据块需要被加载时,它会替换掉当前映射到该位置的数据块,不管缓存的其他位置是否为空。
全相联映射
在 全相联缓存(full associative)中,主存 中的任何块可以映射到缓存中的 任意缓存块。
其映射关系可表示为:
或更形式化地描述为:
其中 为缓存总行数。
由于这种映射关系不是唯一的,而是任意的,所以在根据物理地址去访问 cache 的时候,需要通过 遍历所有 cache 行 来判断是否命中。
如果 cache 的所有行都是满的,新的数据会根据某种 替换策略 来替换 cache 中的某一个 cache 块。
全相联映射例子
假设我们有一个 256KB 的缓存,其中每个缓存块是 64B,对于全相联映射的 cache:
- 缓存被分为
256KB / 64B = 4096个 缓存块。 - 全相联缓存中的每个主存块可以放置在任何缓存块中,即第
0到第4095个缓存块都可以存储该主存块。 - 当缓存满时,基于某种 替换策略 替换掉
4096个 缓存块 中的某一个。
组相联映射
组相联缓存(set associative 或 group associative)是 直接映射缓存 和 全相联缓存 之间的一种 折中方案。它将缓存块分为多个 组,每个组包含多个缓存块。主存块可以 映射到组中的任意一个缓存块。
当我们说一个缓存是 N 路组相连 的,意味着缓存被分为多个 组,每个 组 有 N 个 缓存块(N 路)。这样,当一个内存地址被映射到一个特定的组时,它可以放在该 组的任何一个缓冲块(一路)上。
如果一个组是满的,新的数据会根据某种 替换策略 来替换组中的一个 缓存块。
N 路组相联表示一个 组中有 N 个 cache 块,而不是 cache 中一共有 N 个组。
组相联映射中,主存块地址到缓存组索引的计算公式为:
其中:
- 缓存总块数
- 表示 路组相联
组相联映射例子
假设我们有一个 256KB 的 缓存,其中每个 缓存块 是 64B,我们希望有 4 路组相联的组织。
- 这意味着 缓存 被分为
256KB / 64B = 4096个 缓存块。 - 因为是 4 路组相联,所以这些块被进一步组织为
4096 / 4 = 1024个 组。 - 每个 组 包含 4 个位置(即 4 路),任何内存地址映射到这个 组 的时候,可以放在这四个位置中的任何一个。
- 比如第 10000 个 主存块 位于第
10000 % 1024 = 784个 组,可能对应组内的任何一个 缓存块。
硬件结构
下图展示的是一个 二路组相联 Cache 的结构示意图。
访问时,物理地址中的 组号(index) 用于定位到 Cache 中的某一 组。该 组 包含两个 Cache 块,每个块有 valid 位 和 tag 字段。
地址中的 tag 会同时送入两个 比较器,分别与组内两个块的 tag 进行匹配,并结合 valid 位 判断是否命中。
如果命中,选择器(multiplexer)根据比较结果,从两个块中选出正确的数据输出给处理器。若都未命中,则访问 主存。
🔧 比较器与选择器的作用- 比较器(Comparator):用于判断 Cache 块 中的 tag 是否与当前地址匹配,决定是否命中;
- 选择器(Multiplexer):在多个块中有可能命中的情况下,负责根据比较结果选出正确的数据路径。
在其他映射方式中比较器和选择器的个数是多少
全相联 Cache
- 没有 index 字段,所有 Cache 行 都可能是目标;
- 地址的 tag 需要与 每一行 进行比较 ⇒ 需要 一个比较器对应一行;
- 最终由 多输入选择器 从所有行中选出命中的那一行。
➡️ 优点:命中率高
➡️ 缺点:比较器数量多,硬件复杂,延迟高
直接映射 Cache
- index 字段直接决定数据应该位于哪一行;
- 只需比较该行的 tag ⇒ 仅需一个比较器;
- 无需选择器,命中即用,否则直接访问 主存。
➡️ 优点:硬件简单,速度快
➡️ 缺点:容易发生冲突,命中率低
小结表:
| 映射方式 | 比较器个数 | 是否需要选择器 | 硬件复杂度 | 命中率 |
|---|---|---|---|---|
| 直接映射 | 1 | 否 | 低 | 低 |
| 组相联(n 路) | n | 是 | 中 | 中等 |
| 全相联 | Cache 行数个 | 是 | 高 | 高 |
映射方式对比
假设 cache 有 M 个 cache 块,对于块号为 k 的 主存块:
- 直接映射:被映射到块号
k % M的 cache 块 - 全相连映射:可能被映射到任意一个 cache 块
- 组相连映射:对于 m 路组相连,被映射到组号为
k % (M / m)的 cache 组 中的任意一个 cache 块
| 特点 | 直接映射缓存 | 全相联缓存 | 组相联缓存 |
|---|---|---|---|
| 主存块到缓存块的映射关系 | 固定的 | 任意的 | 组内任意 |
| 硬件复杂度 | 较低 | 高 | 介于两者之间 |
| 查找速度 | 快 | 相对较慢 | 介于两者之间 |
| 成本 | 低 | 高 | 介于两者之间 |
| 性能优点 | 简单、低成本 | 无缓存冲突、高性能 | 较低的缓存冲突,性能适中 |
关联度
Cache 关联度(associativity)描述的是一块 主存地址 可以被映射到 缓存 中多少个不同的位置(cache lines)。
根据上面提及的 映射方式对比 可知关联度对比:
全相联 > 组相联 > 直接映射
更高的关联度意味着 缓存冲突 减少,命中率 提高。但是另一方面,硬件开销(比较器、替换策略复杂度)和访问延迟增加。所以关联度选择需权衡性能与成本。
cache 地址结构
当给定一个 物理地址 时,Cache 的访问过程可以抽象为三个连续的问题:
- 这个地址属于主存中的哪一个块?
- 这个主存块在 Cache 中可能出现在哪些位置?
- 这些位置中是否真的缓存了该主存块?
逻辑划分
因此,从 “硬件判定流程” 的角度,物理地址在逻辑上可划分为以下三部分:
块内地址
- 作用: 确定访问数据在一个 主存块 / cache 块 内的具体偏移
- 位数:
- 说明: 这一部分 只用于块内寻址,与映射方式无关
Cache 块匹配字段
- 作用: 用于 缩小搜索范围,确定该主存块可能被缓存在哪些 cache 块中
- 含义因映射方式而异:
- 字段含义:Cache 块号(Cache Block Index)
- 作用: 一个主存块 只能映射到唯一的一个 cache 块
- 位数:
- 字段含义:无
- 作用: 一个主存块 可能被缓存到任意一个 cache 块
- 位数:
- 说明: 必须 并行比较所有 cache 块的 tag
- 字段含义:组号(Set Index)
- 作用: 一个主存块 只能映射到某一组内的若干 cache 块
- 位数:
总结一下三种映射方式的块匹配字段的计算方法:
标记
- 作用: 在已确定的候选 cache 块(或某一组)中, 通过比较 tag 判断是否真正命中
- 位数:
物理地址对应
具体而言,给定一个物理地址,访问 Cache 时的各个字段的对应方式如下图所示:
其中 块内偏移、cache 块号(直接映射)、cache 组号(组相联映射)的位数可以直接根据 cache 的参数计算出来,Tag 字段的位数需要通过物理地址的位数减去其他字段的位数来得到。
cache 存储结构
cache 存储的内容大体上来说可以分为 数据 和 元数据 这两个部分:
- 数据部分:即 cache 块(cache block),缓存了某个主存块的内容
- 元数据部分:对 cache 访问的过程进行控制
cache 的存储结构可以理解为一张表:
其中字段的含义与 页表 近似,下面列出了:
- 有效位(valid):
- 该 cache 行 是否存储有缓存数据,位数为 1 位。
- 标记(tag):
- 根据物理地址中的 tag 字段与该字段匹配,以判断是否命中,位数按照 cache 地址结构 进行计算。
- 脏位(dirty):
- 访问位(reference):
- 用于记录访问信息,服务于 块替换算法,其位数取决于替换算法。
- 如果采用 LRU 替换算法,则 访问位 的位数为 。
- 数据块(block):
- 缓存的数据块,为 主存块 的一个副本。
cache 的存储结构依照具体的题目,在某些题目中 脏位、访问位 不用考虑,如果需要计算 cache 的容量,需要注意这一点。
cache 中的块替换
块替换算法适用于 全相联映射 和 组相联映射,因为在这两种组织方式中,同一主存块可能被 多个 cache 块 中的任意一个所缓存;因此当需要把新块写入时必须决定把哪一个已有的 cache 块淘汰。而在 直接映射方式 中,主存块只能对应唯一的 一个 cache 块,如果发生冲突,直接用新块覆盖该 cache 块即可,无需额外的替换算法。
替换过程
cache 块替换与命中判定过程(以组相联/全相联为例):
- 确定候选集合 根据地址映射方式,确定对应的 cache 组(组相联)或整个 cache(全相联)作为候选块集合。
- 命中判定
在候选块中:
- 仅对 valid=1 的块进行 tag 比较;
- 若存在某块
valid=1 且 tag 匹配,则发生 cache 命中,访问结束。
- 缺失处理(cache miss)
若未发生命中:
- 若候选块中存在
valid=0的块,则选择其中一个空闲块,将主存块加载到该块中,并设置valid=1、更新 tag; - 若所有候选块均为
valid=1,则根据 块替换算法(如 LRU、FIFO、随机等)选择一个块进行淘汰,并写入新主存块。
- 若候选块中存在
替换算法
当 CPU 访问某个物理地址而在 cache 中未命中时,需要把该地址所在的 主存块 调入 cache。如果该 主存块 映射到的 cache 块(即同一路径或同一个集合)已经全部占满,就必须在这些已占用的 cache 块 中挑选一个进行替换。常用的替换策略有 FIFO(先进先出)、LRU(最近最少使用)和 LFU(最不经常使用)等。
这套思路与操作系统中的页面置换算法本质相同,详情请参见 页面置换算法。
cache 写策略
因为 cache 实际上存储的是主存的一个小副本,所以对于写操作,就需要考虑两者间的数据一致性的问题。
cache 的写策略代表当我们对某个物理地址上的数据进行写入时,应该如何写入对应的存储单元,以及如何协调 cache 和 主存之间的 数据一致性,写策略按照地址查询是否命中 cache 可以分为四种方式。
命中时
如果某次地址查询命中 cache,可以使用如下策略:
- 直写法
(Write Through):
- 每次写操作都会同时更新缓存和主存。
- 这种写策略是 同步的,每次更新 缓存 时要同步地更新 主存。
- 回写法
(Write Back):
- 当数据被修改时,它首先被缓存在 cache 中,只有当 cache 块被替换时才写入对应的主存块。
- 这种写策略是异步的,并不是写入 cache 后立马就要写入主存,可以多次写入 cache 后在另一个时刻再将cache 块写入主存。
如果使用了回写法的话,就需要在 cache 中设置一个脏位。脏位用于记录这个 cache 块是否被写入过,如果被写入过,当这个 cache 块被替换时, 就需要写入到主存中。
可以看到,这种策略将多次 cache 写入合并为一个主存写入,对于写操作比较频繁的场景,其实很大幅度地提升了效率。
未命中时
如果 没有命中 cache,也有如下策略:
- 写分配法
(Write Allocate):
- 物理地址对应主存块被 加载 到 cache 块中(先执行一次对应主存块的读操作),然后更新 cache 块
- 非写分配法
(Not Write Allocate):
- 不加载 主存块至 cache 中,直接更新主存块,只有当执行读操作时才将主存块加载进入 cache 块
策略的组合
命中 和 未命中 的方法常常通过如下方式一起使用:
- 直写法(write-through)和 非写分配法(not-write-allocate)通常会一起使用,适用于那些写操作不频繁或者写操作不太可能访问同一数据的情况。
- 回写法(write-back)和 写分配法(write-allocate)通常会一起使用,适用于那些写操作频繁的情况。
方法的组合方式很容易被混淆,可以通过如下方式记忆:
- 直写法 和 非写分配法 都倾向于 主存 操作(写入 主存)。
- 回写法 和 写分配法 都倾向于 cache 操作(写入 cache)。
5 - 虚拟存储器
虚拟存储器 是一种 计算机内存管理技术,它在 物理内存 和 磁盘存储 之间创建了一个抽象的、扩展的内存空间,以提供更大的可用内存容量。
设计核心 在于将 逻辑地址空间 与 物理内存 进行 解耦。程序在 编译和运行 时所看到的是一个连续、完整的虚拟地址空间,而实际上这些地址并不直接对应物理内存中的位置,而是通过 页表和TLB 进行映射。
物理内存
物理内存(Physical Memory)指的是计算机系统中的实际硬件内存,即随机存取存储器(RAM)。
物理内存 是计算机直接用于存储和操作数据的地方,所有进程的数据和代码实际上都是存储在 物理内存 上。
当访问物理内存时,必须使用 物理地址,物理地址就是 物理内存 中每个存储单元的唯一编号
虚拟内存
虚拟内存(Virtual Memory)是一种计算机系统内存管理技术,它使得进程可以认为自己拥有一个 连续且独立的内存空间,即使实际上 物理内存 可能不够用或者是分散的。
进程使用 虚拟地址 来访问内存中的数据和指令,而不需要了解 物理内存 的详细情况。进程使用虚拟地址去访问 虚拟内存,
当进程访问 虚拟内存 时,操作系统 会将 虚拟地址 转化为物理地址, 进而根据物理地址去访问 物理内存。
页式虚拟存储器
为了更灵活地管理内存,操作系统采用 页式虚拟存储器 的方式,将 虚拟地址空间 和 物理地址空间 都划分为大小固定的 页(Page)。程序运行时,并不需要将整个虚拟地址空间都加载到 物理内存 中,而是按需将部分 页面 载入内存,其余的页面保存在硬盘上。
当 CPU 发出一条内存访问指令时,地址转换机构 会找到对应的物理页框。如果发现该页面尚未加载到内存,就会触发 缺页中断。缺页中断交由操作系统内核处理,内核会判断该页面是否在磁盘的交换区或程序映像文件中,如果存在,就将其调入内存。如果内存已满,还需要根据 页面置换算法(如最近最少使用 LRU 或时钟算法)选择一个合适的页面换出到磁盘,再将新页面调入。整个过程对应用程序是透明的,它只会感知到一次访问延迟,而不会意识到内存与磁盘之间的交换。
这种机制不仅让 有限的物理内存 可以支持 更大规模的虚拟地址空间,还实现了多进程之间的 隔离与保护。每个进程都有自己的页表,彼此之间的虚拟地址不会直接冲突,从而避免了进程间的非法访问。同时,页式虚拟存储器还为实现内存共享提供了可能,比如多个进程可以将不同的虚拟页映射到同一个物理页框,用于共享代码段或数据。
页面划分和地址结构
在 页式虚拟存储器 中,虚拟内存空间 被划分为一个个 虚拟页面(VP,Virtual Page),物理内存空间 被划分为一个个 物理页面(PP,Physical Page)。
虚拟地址(VA, Virutal Address)被划分为 虚拟页面号(VPN,Virutal Page Number)和 页内偏移(Offset)这两个字段。
在使用 虚拟地址 去访问 虚拟内存 时,我们可以根据 虚拟页面号 找到该地址所在的 虚拟页面,在找到 虚拟页面 后,我们可以根据 页内偏移 找到该地址在 虚拟页面 内的偏移大小。
同理, 物理地址 (PA, Physical Address)被划分为 物理页面号(PPN,Physical Page Number)和 页内偏移(Offset)这两个字段。
在使用 物理地址 去访问 物理内存 时,我们可以根据 物理页面号 找到该地址所在的 物理页面,在找到 物理页面 后,我们可以根据 页内偏移 找到该地址在 物理页面 内的偏移大小。
需要注意和区分一下以下几个名词,它们具有相同的含义:
虚拟页面(Virutal Page)= 逻辑页面(Logical Page)
页框(Frame) = 物理页面(Physical Page)
地址翻译机构
CPU 中的 内存管理单元(Memory Management Unit,MMU)是计算机体系结构的重要组成部分,它负责 虚拟内存 到 物理内存 的地址映射和内存访问的控制。MMU 的主要功能包括:
- 地址转换:MMU 负责将程序使用的 虚拟地址 转换为对应的 物理地址。
- 地址保护:MMU 实施内存保护策略,以确保不同的程序或进程无法越界访问彼此的内存空间。
- 内存访问权限:MMU 根据地址映射和保护位(在页表或段表中定义)来控制内存访问权限,包括读、写、执行等。
地址翻译是硬件还是软件完成的?
很多同学会困惑: 页表是操作系统维护的,而地址翻译要查页表;但 MMU 又是硬件结构,那地址翻译到底是硬件还是软件完成的?
关键点在于:操作系统“提供规则和数据”,硬件“执行翻译过程”。
首先,页表并不是操作系统可以随意设计的数据结构。 页表的层级、每一项的位含义(如有效位、权限位、物理页号等),都由 CPU 架构规范(如 x86、ARM)严格定义。操作系统只是按照该规范,在内存中构造符合格式要求的页表。
其次,真正的地址翻译过程由硬件完成。 CPU 中的 MMU(Memory Management Unit) 在执行指令时,会自动根据当前页表基址寄存器(如 CR3),按架构规定的流程遍历页表(或通过 TLB 命中),将虚拟地址转换为物理地址。 这个过程对软件是透明的,不需要操作系统逐条参与。
因此可以总结为:
- 页表的创建与维护:由操作系统负责(软件)
- 地址翻译的执行:由 MMU 完成(硬件)
页表
页表(page table)是操作系统维护的一张表,用于将 虚拟地址 转化为 物理地址,每个运行的进程都有自己 页表。
进程的 页表 存储在其内存空间中的 内核空间 中,详见进程内存空间。
在进程执行时,MMU 使用当前活动进程的 页表 来执行地址转换。
单级页表
页表 的功能是将 虚拟页号 转换为 物理页号,进而实现地址翻译。
对于 单级页表 而言,只需访问一次 页表 即可实现 页面号 的翻译过程。
单级页表结构
一个典型的 单级页表 结构如下图所示:
页表 中的每一行叫做 页表项(PTE, page table entry),页表项 可包含如下内容:
- 虚拟页框号(VPN,Virtual Page Number):对于 单级页表 而言,VPN 并不需要实际存储在 页表 的字段中,其隐性地作为 页表项 的下标进行存储。
- 物理页框号(PPN,Physical Page Number):当前 VPN 所对应的 物理页号。
- 有效位(Valid Bit):用于指示虚拟页 是否有效。若为 1,表示可用于地址转换;若为 0,表示无效,会导致错误。
- 修改位(Dirty Bit):用于指示虚拟页的内容 是否已被修改。若为 1,可能需要写回到磁盘或其他非易失性存储介质。
- 访问位(Accessed Bit):用于指示虚拟页 是否已被访问。若为 1,对页面置换算法有帮助。
- 保护位(Protection Bits):用于指定虚拟页的 访问权限,例如读取、写入或执行权限。
- 缓存位(Caching Bits):用于指示是否允许将虚拟页的内容 缓存在高速缓存中。
页表存储在进程内存空间的什么区域
- 页表存储在物理内存中:页表(无论是单级还是多级)最终都是一些数据结构,它们必须存储在物理内存(RAM) 中,CPU的内存管理单元(MMU) 才能直接读取它们进行地址转换。
- 内核的虚拟地址空间中:操作系统内核也有自己的虚拟地址空间。它会将物理内存中的这些页表,映射到自己的内核虚拟地址空间的一个特定区域。这样,操作系统内核代码(例如处理缺页异常、执行换入换出的代码)就可以通过虚拟地址方便地访问和修改任何进程的页表。
需要注意的是, 页表项 的具体结构可以因不同的计算机体系结构和操作系统而异,在做题目时根据题目的具体指示进行判断。
这里主要关注 虚拟页框号、物理页框号以及 有效位 即可,这是大多数 单级页表 中都会包含的内容。
对于 页表项 中的其他位,主要管制 修改位 和 访问位,保护位 和 缓存位 不太会考察。
单级页表地址翻译
虚拟地址和物理地址格式 如下:
- 虚拟地址(Virutal Address)分为两个部分:
- 虚拟页框号(VPN, Virtual Page Number):当前地址所在的页框在 虚拟内存 对应的所有页框中的下标
- 页内偏移(VPO, Virutal Page Offset):地址在页面内的偏移
- 物理地址(Physical Address)同样分为两个部分:
- 物理页框号(PPN, Physical Page Number):当前地址所在的页框在 物理内存 对应的所有页框中的下标
- 页内偏移(PPO, Physical Page Offset):地址在页面内的偏移
其中 虚拟地址和物理地址的偏移量是相同的(VPO = PPO = offset),页面大小 =
虚拟页面个数 = ,物理页面个数 =
单级页表地址翻译过程 如下:
PPO 和 VPO 的内容一致,所以地址翻译主要在于通过将 VPN 转化为 PPN。VPN 的值为对应的 页表项(PTE)在 页表 中的下标。找到对应的 页表项 后,判断 Valid 字段是否为 1:
- 若为 0,会发生 缺页中断
- 若为 1,读取其中的 PPN,完成地址翻译
单级页表地址翻译实例
假设 虚拟内存 为 16 MB,物理内存 为 1 MB,页面大小为 4 KB,则翻译虚拟地址 0x321654
- VPO 和 PPO 的位数为 12(
),地址对应的 page offset 为
0x654 - 虚拟地址 的总位数为 24( )
- 物理地址 的总位数为 20( )
- VPN 的位数为 24 − 12 = 12,虚拟地址 对应的 VPN 为
0x321 - PPN 的位数为 20 − 12 = 8,
- 虚拟地址 格式为
| VPN (12bits) | VPO (12bits) | - 物理地址 格式为
| PPN ( 8bits) | PPO (12bits) | - VPN 为
0x321,找到 页表 的第0x321个 页表项,判断其中的 valid 字段是否为 1;若为 0,则调用缺页中断;若为 1,则找到其中的 PPN,并使用 PPN 和 PPO 组成 物理地址。
多级页表
单级页表会有什么问题?
在 单级页表 中,为了管理大型 虚拟地址空间,需要创建庞大的 页表,其中包含大量 页表项,这会导致 页表 本身占用大量内存。
假如我们有一个 32 位 4GB 的 虚拟地址空间、4KB 的页面和一个 4 字节 的 PTE,那么我们将需要一个 4 MB 的 页面表 始终驻留在内存中,即使应用程序只引用 虚拟地址空间 的一小块。
多级页表结构
在 多级页表 中,虚拟页号(VPN)被分割为多个字段,假设被分割为 k 个字段的话,第 k 个字段对应的页表中的查询内容为 PPN,前 k‑1 个字段对应的页表中的查询内容为下一级页表的位置。
如果当前 多级页表 对应的一级页表有 个 PTE,将 VPN 分割为 k 个长度相同的子字段后,每个子字段对应的页表的 PTE 个数为 。
其中,第一层有 个页表,第二层最多有 个页表在内存中存在, ,第 k 层最多有 个页表在内存中存在。
多级页表是如何节省内存的?
只有一级页表需要始终在主存中,对于其他层次的页表,可以 按需分配;如果使用到的,就在内存中创建对应的页表结构,如果未使用到,就不需要为其分配内存。这代表了巨大的潜在节省,因为典型程序的 4GB 虚拟地址空间 中的大部分都是未分配的。
对于 k 级页表而言,前 k‑1 级页表中 PTE 存储的关键字段都是下一级页表的位置,如果其中某个 PTE 的 有效位 为 0,那么操作系统无需为该 页表项 对应的下一级页表分配内存空间。
进程切换和页表管理
MMU 进行地址翻译的时候需要使用到页表,但是每个进程的页表不同,MMU 怎么知道当前进程的页表在物理内存的哪个位置呢?
其实操作系统是这样管理不同进程的页表的:
- 每个进程都有一个关键的内核数据结构,通常叫做 PCB 进程控制块。
- PCB 中保存了一个关键信息:页表基址寄存器 应该加载的物理地址。这个物理地址指向该进程 顶级页表 在物理内存中的起始位置。
- 当操作系统进行进程 上下文切换 时,它会:
- 保存当前进程的CPU状态到其PCB。
- 将下一个进程的PCB中保存的页表基址物理地址加载到CR3寄存器中。
- 恢复下一个进程的CPU状态。
- 一旦页表基址存储器被切换,MMU 接下来进行地址转换时,就会使用新进程的页表。这就是不同进程拥有不同虚拟地址空间映射的根本机制。
PTBR
PTBR(Page Table Base Register,页表基址寄存器) 是处理器中用于 指向当前进程顶级页表物理地址 的专用寄存器。
- 它保存的是 页表在物理内存中的起始地址,而不是虚拟地址
- MMU 在进行地址翻译时,会 从 PTBR 出发逐级查页表
- 每个进程都有自己的页表,但同一时刻只有一个 PTBR 生效
PTBR 决定了“当前 CPU 看到的是哪一个进程的虚拟地址空间”
与进程切换的关系:
- 进程切换 = 切换 PTBR
- 操作系统在上下文切换时,把新进程页表的物理地址加载进 PTBR(如 CR3)
- 之后 MMU 的所有地址翻译都会基于新进程的页表
TLB
TLB(Translation Lookaside Buffer)是 CPU 内存管理单元(MMU)中的一种 高速缓存,用于加速 虚拟地址(VA)到 物理地址(PA)的地址转换过程。TLB 存储了最近用过的 虚拟地址 到 物理地址 的映射,以减少每次内存访问时的地址翻译延迟。
由于 页表 存储在内存中,所以每一次通过 页表 的地址翻译过程都至少需要一次访存,开销仍然比较大。为降低开销,TLB 应运而生。与 Cache 类似,你可以从概念上将 TLB 理解成一个硬件结构,因离 CPU 更近,其访问速度更快。
存储结构
TLB 是一个硬件结构,但从逻辑上可以将其理解为一张表。
与 cache 存储结构 类似,TLB 由许多 表项(TLB Entry)构成,每一个 表项 包含多个字段,其中 tag 和 PPN 是必须的,其他字段是可选的。
TLB 和 cache 对比
TLB 和 页表 的关系类似于 cache 与 主存 的关系,TLB 和 cache 都是硬件结构,只是作用场景不同。TLB 与 Cache 的区别如下表所示:
| TLB | Cache | |
|---|---|---|
| 存储的是什么? | 物理页面号(PPN) | 主存块 |
| 使用什么去查找? | 虚拟页面号(VPN) | 主存块号 |
| 在何种场景下使用? | 将 虚拟地址 翻译为 物理地址 时 | 访问 物理地址 时 |
TLB 中也包含三种映射方式:直接映射、全相联映射、组相联映射。
在访问 TLB 时,虚拟地址 中的 虚拟页号(VPN)被按照映射方式进行不同的切分。在访问 cache 时,物理地址 中的 主存块号 被按照映射方式进行不同的切分。
两者的对比如下所示:
在 直接映射 中,通过 TLB 行号去对应行的 表项 进行查询。
在 组相联映射 中,通过 TLB 组号去对应组进行查询(遍历组中的所有 TLB 表项)。
在 全相联映射 中,遍历 TLB 中的所有 表项。
地址结构
当我们通过 TLB 进行 虚拟页号(VPN)→ 物理页号(PPN) 的翻译时,访问 TLB 需要使用到 VPN。
从逻辑上而言,VPN 可以被分为 标记 和 匹配字段 这两个部分:
- 标记(TLBT, 即 TLB Tag):与 TLB 中的 tag 进行对比,判断是否命中 TLB 表项。
- 匹配字段(Match Field),通过该字段判断 VPN 可能被哪些 TLB 表项所缓存,这里 TLB 与 Cache 类似,同样具有三种映射方式,每一种映射方式的 匹配字段 位数不同:
- 直接映射:匹配字段为 TLB 表项编号,即行编号(TLB entry index),其位数为
- 全相联映射:没有匹配字段,即匹配字段位数为 0,因为每一个 VPN 都可能被任何一个 TLB 行所缓存
- 组相联映射:匹配字段为组号(TLB group index),位数为
使用 TLB 进行地址翻译的过程如下:
- 给定一个 虚拟地址(VA),从中提取出 虚拟页号(VPN)
- 根据映射方式从 VPN 中提取出 标记(TLBT)和 匹配字段
- 根据 匹配字段 从 TLB 的相应表项中依次查找,命中时应满足如下条件:
- 有效位(valid)为 1
- 该表项中的 TLBT 与 VPN 中的 tag 字段相同
- 若命中某个表项,则通过 TLB 完成 VPN → PPN 的翻译
- 若未命中,则通过 页表 完成翻译
请求页式管理
请求页式管理(Demand Paging)是一种计算机操作系统中的内存管理技术,它允许进程在需要时才将 页面(或者说 虚拟内存 中的数据块)加载到 物理内存 中,而不是一次性将整个进程加载到内存中:
- 进程开始运行时,并 不把所有页装入内存
- 访问到某一页时才触发:
- 缺页异常(page fault)
- 从磁盘调入该页
- 是一种 懒加载(lazy loading)
我们平时说“页式虚拟存储”,默认指的基本都是 “请求页式管理”。与请求方式相对应的,叫做预调页机制,也就是说:
在进程运行前,提前装入一部分或全部页面。
但是在考研语境和实践中,都是指的是请求页式管理。
页面错误
当进程尝试访问一个 虚拟页面,但该页面当前未加载到 物理内存 中时,会触发 页面错误(page fault)。此时,操作系统会将相应的页面从磁盘加载到 物理内存,进行 页面替换。
当然,上述说法中沿用了教材中的叙述方式:
缺页 → 从磁盘调入 → 进行页面替换
这是一个 简化模型。
实际上,page fault 的本质 是:
CPU 访问虚拟地址
页表项 valid = 0 触发异常
由内核修复映射
至于修复方式:
- 建立映射
- 分配物理页
- 读磁盘
都可能。
如何判断访问的页面是否在物理内存中呢?
通过查询 TLB 和 页表(见 单级页表)以判断某个 虚拟地址 对应的页面是否在内存中。
从 虚拟地址(VA)中提取出 虚拟页号(VPN),再查询 TLB 和 页表 中是否存在包含 VPN 的记录。如果都不存在,则说明内存中不存在与 VPN 对应的 物理页面。
页面替换
页面替换 时包含两种情况:
- 物理内存 中存在 空闲页面(未被任何进程使用的页面)。
- 进程 中不存在任何 空闲页面,即所有页面都被进程使用了。
如果存在 空闲页面,当一个进程出现 缺页中断 时,直接使用 空闲页面 即可。
如果 物理内存 已满,操作系统需要选择一个页面来替换。通常,操作系统会选择一个 不再需要的页面 进行替换,这个决策基于 使用的页面置换算法。
如果操作系统中配置了 交换分区(swap area),被置换的页面会被写入 交换分区;当该页面再次被需要时,页面会从 交换分区 中加载回 物理内存。
交换分区交换分区 是硬盘上专门划出的一个区域,用来作为系统内存的“扩展”。当 物理内存 使用接近或达到上限时,操作系统会把暂时不用的内存页(比如不活跃的后台程序)移到 交换分区,从而释放出 物理内存。
访存过程
对于进程而言,它自己可见的就是 虚拟地址(VA),当进程访问 虚拟地址 时,实际上是对某个 物理地址 进行访问。
访存 主要包含两大过程:
- 地址翻译:VPN → PPN,即由 虚拟地址(VA)得到 物理地址(PA)的过程。
- 根据 物理地址(PA)去 cache 或 主存 中读取数据。
需要熟练掌握这两大过程中的各种细节,考试中常将这些知识点组合考察。
虚拟地址翻译过程
操作系统经过如下步骤将 虚拟地址 转为 物理地址:
- 从 虚拟地址(VA)中提取 VPN(虚拟页号)。
- 根据 VPN 去访问 TLB。
- 命中 TLB:从 TLB 中读取相应的 物理页号(PPN)。
- 未命中 TLB:读取 页表 后更新 TLB。
- 命中页表:从 页表 中读取相应的 PPN,并更新 TLB 表项。
- 未命中页表:触发 缺页中断,从内存中找一个页面以加载 物理页面
- 存在 空闲页面,直接使用 空闲页面,使用其 PPN 更新 页表。
- 不存在 空闲页面,选择一个进程的页面进行置换。
- 组装 物理页号(PPN)和 页内偏移(PPO)得到 物理地址。
上述过程可以由如下的流程图表示:
缺页中断
当 CPU 访问一个虚拟地址时,MMU 会根据 VPN 查找对应的 页表项(PTE)。
如果发现该页表项的 Valid 位为 0,说明该虚拟页面当前没有映射到物理内存(可能尚未分配,也可能已经被换出到磁盘),CPU 无法继续完成地址翻译,于是会触发 缺页中断(Page Fault)。
到底是叫 缺页中断 还是 缺页异常?
其实从语义的角度来说,缺页异常更加准确,参考 中断和异常的概念。
缺页中断其实是一个教材翻译的历史遗留问题,国内很多经典教材(尤其是《计算机组成原理》《操作系统》的一些版本)沿用了「中断」作为统称,把:
- 外部中断(Interrupt)
- 内部中断(Exception、Trap、Fault)
都称为 中断,所以有了缺页中断的说法,所以不要过分纠结这两个命名。
上文中提到的 缺页中断 过程忽略了一些细节,详细过程如下:
- 触发缺页中断:
- 当 CPU 尝试访问一个 虚拟内存页面,但该页面未加载到 物理内存(或页面无效)时,MMU 检测到页面缺失,触发 缺页中断。
- 判断缺页原因:
- 页面未分配、页面在磁盘(换出)、非法访问(越界或权限不足)。
- 若为 非法访问,操作系统会终止程序(如段错误,Segmentation Fault)。
- 判断是否有空闲页面:
- 若有 空闲页面,操作系统会分配一个新的 物理页面。
- 若 物理内存 不足,可能会通过页面置换算法(如 LRU)选择一个现有页面换出到磁盘,腾出空间。
- 加载页面内容:
- 若 页面未分配,操作系统会分配一个新的 物理页面。
- 若 物理内存 不足,同上使用页面置换算法。
- 更新页表:添加一条新的页面映射表项。
- 恢复执行:缺页中断 处理完成后,操作系统恢复被中断的程序上下文。CPU 重新执行引发缺页的指令,此时页面已可用,程序继续正常运行。
可以通过以下流程图理解缺页中断的过程:
物理地址访存过程
访存 过程常与 地址翻译 过程一起考察。进程访问一个 虚拟地址 时,首先完成 地址翻译 得到 物理地址,随后使用该 物理地址 访问内存。
使用 物理地址 访问内存时首先判断是否命中 cache;若未命中,则访问 内存,随后更新 cache。在使用 主存块 更新 cache 块 的过程中,还要根据 cache 与 主存 的映射方式决定替换哪个 cache 块,详细过程如下图所示。
虚拟地址翻译和访存过程总结
如果将 地址翻译过程 与 访存过程 放在一起,一个 虚拟地址 访问内存的大致流程为(? 表示不一定会发生):
虚拟地址 → TLB → (页表)? → (缺页中断)? → 物理地址 → Cache → (内存)? → (更新 Cache)?