存储系统
18 套卷子里,除 2022 和 2026 外的 16 年都有至少一道以 Cache 或虚存为主线的综合题,是整张卷子上单点收益最高的一支(见知识点优先级)。
知识框架概要
这一支的全部考点挂在两条链上。先把这两条链背成骨架,再做题——每一小问都只是骨架上的某一个节点。
一、地址翻译链:VA → PA 的实质是 VPN → PPN,页内偏移原样抄下来
VA = 虚页号 VPN ‖ 页内偏移—— 拆分点只由页大小决定,见 页面划分和地址结构VPN → TLB—— 一个装页表项的小 Cache,全相联或组相联;命中直接拿到 PPN,见 TLBVPN → 页表—— 只在 TLB 缺失时走;多级则先页目录再页表,见 多级页表PA = 页框号 PPN ‖ 页内偏移(偏移位不变,这是 TLB 与 Cache 各种「能不能提前索引」结论的唯一根据)
二、Cache 访问链:PA → 数据,靠三段地址定位
PA = 标记 Tag ‖ 组号 Index ‖ 块内地址 Offset—— 见 cache 地址结构- 组号选中唯一一组 —— 直接映射是「组内一行」,n 路组相联是「组内 n 行」,全相联只有一组,见 cache 和主存映射方式
- 组内逐路比 Tag 并查有效位 —— 命中按 Offset 取字;缺失访主存调入整块
- 缺失后两件事:替换算法(LRU / FIFO / 随机)与写策略(全写 + 非写分配 / 写回 + 写分配),见 替换算法、cache 写策略
两条链串起来就是一次完整访存,草稿上默画一遍,做题时只需往上填数:
虚拟地址 ─┬─ 虚页号 ─→ TLB ─命中→ 页框号
│ └缺失→ 页表(多级先查页目录)─→ 页框号
│ └ 有效位 0 → 缺页异常 → 磁盘
└─ 页内偏移 ──────────────────┐
↓
物理地址 = 页框号 ‖ 页内偏移
├─ 标记 + 组号 ─→ Cache ─命中→ 送 CPU
└─ 块内地址 └缺失→ 访主存,调入整块
完整时序见 访存过程。
解题流程模板
这三步有严格先后:位数没算完就去画链、链没画完就去套公式,是这一支最主要的失分方式。
第一步:所有容量换算成位数
| 题干给的量 | 直接推出 | 关系 |
|---|---|---|
| 页大小 | 页内偏移位数 | log₂(页大小) |
| 主存块大小 | 块内地址位数 | log₂(块大小) |
| Cache 数据区容量、路数、块大小 | 组数、组号位数 | 组数 = 容量 ÷ (路数 × 块大小) |
| 物理地址位数 | 标记位数 | 物理地址位数 − 组号位数 − 块内地址位数 |
| 虚拟地址位数 | 虚页号位数 | 虚拟地址位数 − 页内偏移位数 |
| 页表基址、页表项大小 | 页表项地址 | 基址 + 表项号 × 表项大小 |
这张表填完,「占几位」「在哪一组」「页表项地址是多少」这几类小问就已经有答案了。会失分的情况通常是没填完就急着算下一问。
第二步:把链实例化
在草稿上重画那条链,把第一步算出的位数标在每一段上:虚页号几位、页内偏移几位、标记几位、组号几位。每个小问只落在这条链的一个位置上,先定位再计算,不要先套公式。
第三步:按问法对号入座
| 小问的问法 | 落在链上哪一层 | 固定动作 |
|---|---|---|
| 某虚拟地址的物理地址是多少 | 页表 | 页框号 × 页大小 + 页内偏移;位数对齐时直接拼接 |
| 页目录项 / 页表项的地址 | 页表 | 基址 + 表项号 × 表项大小,虚地址和物理地址各算一次 |
| 在 Cache 的哪一组 / 哪一行 | Cache | 取物理地址里的组号位段,不要用整个地址去除 |
| 命中率 / 缺失率 | Cache | 一块装 k 个元素,顺序遍历的命中率 = (k−1)/k |
| 平均访问时间 | Cache | 命中时间 + 缺失率 × 缺失损失 |
| 缺页次数 | 页表 | 无置换时按首次访问页数算;有置换时按完整访问序列模拟 |
| 按行还是按列遍历更快 | Cache | 只比较访问步长与块大小 |
页表题的核心动作永远是同一组「两次乘加」:先用表项号定位页表项,再用页框号拼出物理地址。2013#46、2020#46 和 2024#45 是同一套题,其中 2024 年多绕一层——页表自己也放在某个页里,也要被翻译一次。
两种变形
以下“虚拟地址直接索引 Cache”讨论以题目明确采用 VIPT(Virtually Indexed, Physically Tagged)模型为前提;未说明时仍应按题设给出的访问顺序判断。
用虚拟地址直接查 Cache 的条件只有一句:组号位数 + 块内地址位数 ≤ 页内偏移位数。满足时这些位在虚拟地址和物理地址中完全相同,不必等地址转换结束就能开始索引 Cache。2011#44、2016#45 和 2019#46 靠它推理,2025#43 直接问「VA 中哪些位可以作为 Cache 索引」。
题干给了汇编或 C 代码,是把地址藏起来的写法,固定三步:
- 从代码里取数组首地址和元素大小,算出目标元素的虚拟地址;
- 把这个虚拟地址送进上面那条链;
- 遇到「哪种遍历顺序更好」,只比较步长和块大小。
历年题索引
框架和流程是通用的,下面这张表只用来查「某一年把题绕在了链上的哪一层」,练完一年再回上面对一遍流程。
| 真题 | 主线 | 这一年特别问了什么 |
|---|---|---|
| 2009#46 | 请求分页 + TLB + LRU | 依次访问三个虚地址各需多少时间 |
| 2010#44 | Cache 直接映射 | 行遍历与列遍历的命中率谁更高 |
| 2010#46 | 分页 + FIFO / CLOCK | 同一个虚地址在两种置换算法下的物理地址 |
| 2011#44 | 页表 + TLB 四路组相联 + Cache | 用物理地址访问 Cache 时的字段划分 |
| 2012#43 | Cache 缺失率 + 主存带宽 | 由每秒缺失次数反推最低主存带宽 |
| 2012#45 | 驻留集 + 空闲页框链表 | 该置换策略是否适合时间局部性好的程序 |
| 2013#43 | Cache 块大小 + 突发传送 | 读一个主存块需几次突发传送 |
| 2013#46 | 二级页表 | 页目录号与页表索引的表达式 |
| 2014#45 | 指令 Cache + 缺页 + TLB | 哪条指令溢出、读磁盘与查 TLB 各几次 |
| 2015#46 | 二级页表 | 页目录加页表一共占多少页 |
| 2016#45 | TLB 全相联 + Cache 二路组相联 | 为什么 Cache 可以直写而页面用回写 |
| 2017#45 | 二级分页 + 机器指令 | 取第 1 条指令时访问页目录与页表的第几项 |
| 2018#44 | TLB + Cache 回写 | TLB 用 SRAM 还是 DRAM、有效位的作用 |
| 2018#45 | 二级页表 + 改进型 CLOCK | 由页目录号、页号、偏移反向合成虚拟地址 |
| 2019#46 | 分页 + 指令 Cache 四路组相联 | call 指令只可能在哪一组命中 |
| 2020#44 | Cache 八路组相联 + 直写 | 每行的 Tag 与 LRU 各几位、有没有修改位 |
| 2020#46 | 二级页表 + 二维数组 | 页目录项与页表项的物理地址 |
| 2021#44 | TLB 二路组相联 + LRU | 虚地址增到 32 位后表项要增加几位 |
| 2023#43 | 请求调页 + Cache 四路组相联 | 缺页次数与发生缺页的页故障地址 |
| 2024#44 | 页式管理 + 汇编 | a[i] 所在页号、数组至少占几页 |
| 2024#45 | 页表项定位 | 页表所在页的页号及其页表项 |
| 2025#43 | Cache 八路组相联 + 缺页 | 哪些 VA 位可作索引、平均访问时间 |
| 2025#46 | 进程虚拟地址空间分区 | PCB、ptr、字符串常量各在哪个区域 |
易错清单
- 组数忘了除路数。八路组相联的组数是「容量 ÷ 路数 ÷ 块大小」,少除一次路数,后面所有位数都错。
- 按字编址和按字节编址混用。这只决定主存单元宽度,不能由它推寄存器宽度。
- 不检查条件就用虚拟地址索引 Cache。见上面那一句不等式。
- 把“首次装入”当成所有缺页题的规则。无置换时缺页次数才等于首次访问页数;出现 FIFO、LRU、CLOCK 或明确的置换后,必须逐次模拟,缺页次数可能超过数据占用页数。
- 漏掉 TLB / Cache 初始为空造成的强制缺失。
- 二级页表只做了一次乘加。定位页表项和拼物理地址是两步,不能合并。