计算机系统 III 硬件第三章:存储层次结构

计算机系统 III 硬件第三章:存储层次结构

周四 9月 25 2025 Course
9003 字 · 33 分钟

[迁移说明] 本文最初发布于 blog.zzw4257.cn,现已迁移并在本站进行结构化整理与增强。

第三章:存储层次结构 (Memory Hierarchy)

§3.1 引言 (Introduction)

存储器的种类

计算机系统中的存储部件多种多样,可以从物理实现和在层次结构中的位置进行分类:

  • 寄存器 (Register): CPU内部,速度最快,容量最小。
  • 高速缓存 (Cache): 位于CPU和主存之间,用于弥补它们之间的速度差异。
  • 主存储器 (Memory): 通常指DRAM,程序和数据运行时存放的地方。
  • 辅助存储器/外存 (Storage): 如磁盘、SSD,用于长期存储。

历史与技术类型:

  • 机械存储器: 声波/扭矩波延迟线存储器, 磁鼓存储器, 磁芯存储器。
  • 电子存储器:
    • SRAM (静态随机访问存储器): 速度快,用于Cache。
    • DRAM (动态随机访问存储器): 密度高,成本低,用于主存。
      • SDRAM (同步动态随机访问存储器)
      • DDR SDRAM (双倍数据速率同步动态随机访问存储器)
    • Flash (闪存): 非易失性,用于SSD、U盘等。
    • ROM (只读存储器):
      • PROM (可编程只读存储器)
      • EPROM (可擦除可编程只读存储器)
      • EEPROM (电可擦除可编程只读存储器,Flash属于此类)
  • 光学存储器: CD, DVD等。

局部性原理 (Principle of Locality)

程序访问存储器时表现出的特性,是存储层次结构有效性的基础。

  • 时间局部性 (Temporal locality): 如果一个数据项被访问,那么它在不久的将来很可能被再次访问。
    • 例子: 你刚放到桌上看的一本书,很可能马上还需要再看一遍。
  • 空间局部性 (Spatial locality): 如果一个数据项被访问,那么与它地址相近的数据项也很可能在不久的将来被访问。
    • 例子: 阅读书本时,通常会按顺序阅读相邻的页面。

存储层次结构 (Memory Hierarchy)

📖 什么是存储层次结构?
利用局部性原理,通过组织多级存储器(速度、容量、成本各不相同)来提供一个看起来既大又快的存储系统。

  • 目标: 以最快技术的速度提供访问,同时拥有最便宜技术所能提供的大容量。

典型层次 (从快/小/贵 到 慢/大/便宜):

  1. CPU 寄存器 (Registers): ~数百字节, ~皮秒(ps)级访问速度。
  2. L1 Cache (一级缓存): 通常分离为指令缓存(I-Cache)和数据缓存(D-Cache)。~几十KB, ~纳秒(ns)级。
  3. L2 Cache (二级缓存): ~几百KB到几MB, ~几纳秒到十几纳秒。
  4. L3 Cache (三级缓存): (常见于现代多核CPU) ~几MB到几十MB, ~几十纳秒。
  5. 主存储器 (Main Memory - DRAM): ~GB到TB级别, ~几十到几百纳秒。
  6. 辅助存储器 (Secondary Storage):
    • SSD (固态硬盘 - Flash): ~TB级别, ~几十到几百微秒(μs)。
    • HDD (机械硬盘 - Magnetic Disk): ~TB到几十TB, ~毫秒(ms)级。
    • 磁带 (Tape): 大容量备份。

(幻灯片P11-P13展示了个人移动设备、笔记本/台式机、服务器的不同存储层次配置示例)

什么是 Cache?

📋 Cache 的定义

  • 广义定义 (韦氏词典): 一个用于隐藏或存储东西的安全场所。
  • 计算机体系结构中: 一种小而快速的存储,用于改进对慢速存储器的平均访问时间。一旦地址离开处理器,遇到的最高或第一级存储层次结构。通过缓冲来重用常用项。
  • 核心思想: 几乎计算机系统中的所有东西都可以看作是一种 Cache!
    • 寄存器是变量的 “Cache” (软件管理)。
    • L1 Cache 是 L2 Cache 的 Cache。
    • L2 Cache 是主存的 Cache。
    • 主存是磁盘的 Cache (虚拟内存)。
    • TLB 是页表的 Cache。
    • 分支预测缓冲是预测信息的 “Cache”。

Cache 的基本操作

  • 命中 (Hit): 处理器在 Cache 中找到了请求的数据项。
    • 命中率 (Hit Rate): 命中次数 / 总访问次数。
    • 命中时间 (Hit Time): 访问 Cache 并获取数据所需的时间。
  • 缺失 (Miss): 处理器在 Cache 中未找到请求的数据项。
    • 缺失率 (Miss Rate): 1 - Hit Rate。
    • 缺失代价 (Miss Penalty): 从下一级存储器获取数据块并送到 Cache,再送到 CPU 所需的额外时间。
  • 块/行 (Block/Line): Cache 与下一级存储器之间数据传输的最小单位。当发生缺失时,包含请求字的一个固定大小的数据集合会从主存加载到 Cache 中。

§3.2 技术趋势与存储层次结构 (Technology Trend and Memory Hierarchy)

处理器-存储器性能差距 (Processor-Memory Performance Gap)

⚠️ 性能鸿沟 自1980年以来,处理器性能的提升速度远超存储器性能的提升速度 (如幻灯片P25图所示),这使得存储系统成为整体性能的瓶颈。存储层次结构是弥补这一差距的关键技术。

现代 DRAM 技术

  • SRAM (静态随机访问存储器): 速度快,功耗高,密度低,用于 Cache。
  • DRAM (动态随机访问存储器): 速度相对慢,功耗低,密度高,用于主存。需要定期刷新。
  • SDRAM (同步DRAM): 与系统时钟同步。
  • DDR SDRAM (双倍数据速率SDRAM): 在时钟的上升沿和下降沿都传输数据。
  • HBM (高带宽内存 - High Bandwidth Memory): 通过堆叠 DRAM 芯片和宽接口实现极高带宽,常用于 GPU。
  • Intel Optane DC Persistent Memory (DCPMM): 基于3D XPoint技术的持久内存,兼具DRAM的速度和非易失性存储的特性。

Cache 的缺失类型 (Causes of Cache Misses - 3C模型)

  1. 强制性缺失 (Compulsory Miss / Cold Start Miss): 第一次访问某个块时发生的缺失。不可避免,只能通过预取等方式尝试缓解。
  2. 容量缺失 (Capacity Miss): Cache 容纳不下程序工作集中的所有块,导致某些块被替换出去后又被访问。可以通过增大 Cache 容量来减少。
  3. 冲突缺失 (Conflict Miss / Collision Miss): 在直接映射或组相联 Cache 中,多个不同的内存块映射到同一个 Cache 位置(组),导致它们相互替换而产生的缺失。可以通过提高相联度或更好的哈希函数来减少。

不同计算机类型对存储层次结构的关注点

  • 台式计算机: 主要运行单个用户的单个应用,更关注存储层次结构的平均延迟
  • 服务器: 可能有数百用户同时运行数十个应用,更关注内存带宽
  • 嵌入式计算机:
    • 实时应用:关注最坏情况性能而非平均性能。
    • 功耗和电池寿命是主要考虑因素。
    • 存储层次结构的保护作用可能减弱(运行单一应用,简单OS)。
    • 主存通常很小,可能没有磁盘存储。

Cache 相关术语 (P35 列举了36个)

(已在之前章节的术语表中有所覆盖,这里不再赘述,但需熟悉)


§3.3 Cache 设计的四个基本问题 (Four Questions for Cache Designers)

ℹ️ Cache 设计核心问题 Cache 的设计需要回答以下四个关键问题,这些问题不仅适用于 CPU Cache,也适用于操作系统、文件系统等其他使用缓存概念的场景。

Q1: 块放置 (Block Placement) - 一个块可以放在 Cache/主存的哪个位置?

  • 直接映射 (Direct Mapped):
    • 每个内存块只能映射到 Cache 中的一个特定位置。
    • 映射关系通常是 (块地址) mod (Cache中的块数)
    • 优点: 实现简单,判断块位置和查找快。
    • 缺点: 冲突率高,如果多个常用块映射到同一位置,会导致“抖动”(thrashing)。
  • 全相联 (Fully Associative):
    • 内存块可以放置在 Cache 中的任何位置。
    • 优点: 块放置灵活,冲突率最低。
    • 缺点: 查找复杂,需要比较所有 Cache 行的标记,硬件成本高,速度慢。
  • 组相联 (Set Associative):
    • Cache 被划分为若干个组 (Set),每个内存块可以映射到特定组中的任何一个位置。
    • 映射关系通常是 (块地址) mod (Cache中的组数)
    • 一个组包含 N 个块,则称为 N-路组相联 (N-way set associative)。
    • 是直接映射和全相联的折中:
      • 1-路组相联 = 直接映射。
      • M-路组相联 (M为Cache总块数) = 全相联。
    • 为什么用中间位做索引 (Why Index With the Middle Bits?) (P47): 如果使用高位地址作为组索引,连续的内存块可能会映射到同一个 Cache 组,降低空间局部性的利用效率。使用中间位作为索引可以使连续的内存块更好地分布到不同的组。
    • 相联度越高, Cache 空间利用率越高,块冲突概率越低,失效率越低,但硬件复杂度和功耗也相应增加。大多数现代 Cache 的相联度 n ≤ 4 或 8。

Q2: 块识别 (Block Identification) - 如何找到 Cache/主存中的块?

  • 地址标记 (Address Tag): 每个 Cache 块都有一个地址标记,存储了该块对应的主存地址的高位部分。
  • 有效位 (Valid Bit): 每个 Cache 块通常有一个有效位,指示该块中的内容是否有效。
  • 查找过程:
    1. 使用物理地址的索引 (Index) 部分选择 Cache 中的一个组 (对于组相联和直接映射)。
    2. 并行比较该组内所有块的标记 (Tag) 与物理地址的标记部分。
    3. 如果标记匹配且有效位为1,则 Cache 命中 (Hit)
    4. 使用物理地址的块内偏移 (Byte Offset / Block Offset) 部分从命中的块中选择所需的字节/字。

物理地址的格式: [ 标记 (Tag) | 索引 (Index) | 块内偏移 (Byte Offset) ]

  • 索引位数: log2(组数) (组相联) 或 log2(总块数) (直接映射)。
  • 块内偏移位数: log2(块大小,以字节为单位)
  • 标记位数: 总地址位数 - 索引位数 - 块内偏移位数

(P54-P59 详细图解了直接映射、组相联、全相联 Cache 的行匹配和字选择过程,以及具体示例)

Q3: 块替换 (Block Replacement) - Cache/主存发生缺失时,替换哪个块?

  • 直接映射: 只有一个可替换的块,无需选择。
  • 组相联和全相联: 当一个组已满且发生缺失时,需要选择一个块进行替换。
  • 常用替换策略:
    • 随机 (Random): 随机选择一个块替换。实现简单,但可能替换掉即将被访问的块。
    • 最近最少使用 (LRU - Least-Recently Used): 选择组内最长时间未被访问的块。基于时间局部性原理,假设最近访问的块更可能再次被访问。硬件实现复杂,需要额外位来跟踪访问顺序。
      • LRU的硬件实现 (比较对法 - Comparison Pair Method P87-P92): 对于p个块的组,每对块(A,B)用一个触发器T_AB记录访问顺序 (如T_AB=1表示A比B近访问)。通过组合逻辑判断哪个块是LRU。硬件开销随块数p的增加而显著增长(触发器数p(p-1)/2,与门数p,与门输入p-1)。
    • 先进先出 (FIFO - First In, First Out): 选择最早进入组的块。实现简单,但可能替换掉仍然常用的块。
    • 最优替换 (OPT / MIN / Belady’s Algorithm): 替换将来最长时间内不会被访问的块。理论上最佳,但无法实际实现,因为需要预知未来。常用于性能评估的基准。
  • 堆栈替换算法 (Stack Replacement Algorithm P80):
    • 如果一个替换算法满足:对于任意时刻 t,大小为 n 的 Cache 中包含的块集合 B_t(n) 总是大小为 n+1 的 Cache 中包含的块集合 B_t(n+1) 的子集,则称其为堆栈替换算法。
    • LRU 是堆栈替换算法。 这意味着对于LRU,增加Cache大小(块数)总是能提高或保持命中率。
    • FIFO 不是堆栈替换算法 (Belady’s Anomaly P86): 存在某些访问序列,增加FIFO Cache的块数反而导致命中率下降。

(P70, P75-P79, P81-P86 包含替换策略的详细示例和命中率分析)

Q4: 写策略 (Write Strategy) - 发生写操作时会怎样? (P3-P10)

  • 当数据写入 Cache 时 (发生 Store 操作),是否也同时写入主存?

    • 写直通 (Write-Through):
      • 数据同时写入 Cache 和主存。
      • 优点: Cache 和主存始终保持一致。读缺失不会导致额外的写回操作。实现相对简单。
      • 缺点: 每次写操作都需要访问较慢的主存,可能导致写停顿 (Write Stall)——CPU 必须等待写操作完成。
      • 控制位:通常只需要一个有效位。
      • 写缓冲器 (Write Buffer): 为缓解写停顿,CPU可以将写数据放入写缓冲器,然后继续执行,由缓冲器在后台将数据写入主存。写缓冲器本身是一个小型 Cache。如果写操作频繁导致缓冲器已满,仍会发生停顿。
    • 写回 (Write-Back / Copy-Back):
      • 数据仅写入 Cache。被修改的 Cache 块标记为“脏”(Dirty Bit)。
      • 当脏块被替换时,才将其内容写回主存。
      • 优点: 写操作以 Cache 速度进行。如果一个块在被替换前多次写入,只需要一次写回主存,显著减少主存带宽需求。
      • 缺点: Cache 和主存可能不一致。实现更复杂,需要脏位。读缺失时若替换脏块,需要先写回脏块再读入新块,增加缺失代价。
  • 写缺失 (Write Misses) - 当写操作的目标块不在 Cache 中时:

    • 写分配 (Write Allocate):
      • 在写操作之前,先将目标块从主存加载到 Cache 中(类似于读缺失处理),然后再执行写操作(此时变为写命中)。
      • 通常与写回策略一起使用。
    • 非写分配 (No-Write Allocate / Write Around):
      • 数据块仅写入主存,不加载到 Cache 中。
      • 通常与写直通策略一起使用。

(P7 展示了写直通+非写分配 和 写回+写分配 的流程图) (P8-P9 练习题分析了两种分配策略下的命中/缺失次数)


§3.4 存储系统性能 (Memory System Performance)

CPU 执行时间与存储停顿

CPU 执行时间 = (CPU 时钟周期 + 存储器停顿周期) × 时钟周期时间

存储器停顿周期 = IC × 每条指令的访存次数 × 缺失率 × 缺失代价 (IC = Instruction Count 指令数)

CPUtime = IC × (CPIExecution + (每指令访存次数 × MissRate × MissPenalty)) × CycleTime 或者 CPUtime = IC × (CPIExecution + (每指令缺失数 × MissPenalty)) × CycleTime

  • CPIExecution 包含 ALU 和存储器指令本身的执行周期(不考虑存储系统停顿)。

平均访存时间 (AMAT - Average Memory Access Time)

AMAT = 命中时间 (Hit Time) + 缺失率 (Miss Rate) × 缺失代价 (Miss Penalty)

  • 对于分离的指令和数据 Cache,可以分别计算指令 AMAT 和数据 AMAT,然后加权平均。 AMAT = (HitTimeInst + MissRateInst × MissPenaltyInst) × Inst% + (HitTimeData + MissRateData × MissPenaltyData) × Data%

  • CPU 时间也可以用 AMAT 表示: CPUtime = IC × ( CPIAluOps × (AluOps/Inst) + AMAT × (MemAccess/Inst) ) × CycleTime (CPIAluOps 是纯计算指令的CPI,AluOps/Inst 是计算指令比例,MemAccess/Inst 是访存指令比例)

(P19 示例计算了 Cache 对性能的影响)

如何改进 Cache 性能 (P20-P22)

💡 Cache 优化目标 AMAT = HitTime + MissRate × MissPenalty 优化方向:

  1. 减少缺失代价 (Reduce Miss Penalty)
  2. 减少缺失率 (Reduce Miss Rate)
  3. 减少命中时间 (Reduce Hit Time)
  4. 通过并行性减少缺失代价和缺失率

具体优化技术分类 (超过20种):

  1. 减少缺失代价:
    • 多级缓存 (Multilevel Caches): L1, L2, L3…
    • 关键宇优先 (Critical Word First) / 提前重启动 (Early Restart): 缺失时,先取回请求的字给CPU,再取块中其余部分。
    • 读缺失优先于写缺失 (Read Miss Before Write Miss): 优先处理读缺失,因为读操作通常更直接地阻塞CPU。
    • 合并写缓冲 (Merging Write Buffers): 将多个对同一Cache块的写操作在写缓冲中合并。
    • 牺牲缓存 (Victim Caches): 一个小的全相联Cache,用于存放被替换出主Cache的块,以减少冲突缺失。
  2. 减少缺失率:
    • 更大的块大小 (Larger Block Size): 利用空间局部性,但可能增加冲突和缺失代价(传输时间)。
    • 更大的缓存容量 (Large Cache Size): 减少容量缺失,但可能增加命中时间和功耗。
    • 更高的相联度 (Higher Associativity): 减少冲突缺失,但增加命中时间和硬件复杂性。
    • 路预测 (Way Prediction) 和伪相联 (Pseudo-Associativity): 尝试获得高相联度的好处,同时保持较低的命中时间。
    • 编译器优化 (Compiler Optimizations): 如循环交换、分块等,改善访存局部性。
  3. 减少命中时间:
    • 小而简单的缓存 (Small and Simple Caches): 减少查找和访问延迟。
    • 避免地址翻译 (Avoiding Address Translation): 在Cache访问路径中尽早使用物理地址或使用虚实地址映射的Cache (VIPT)。
    • 流水线化 Cache 访问 (Pipelined Cache Access): 将Cache访问分解为多个阶段。
    • 踪迹缓存 (Trace Caches): 存储动态指令序列而非静态块,提高指令带宽。
  4. 通过并行性减少缺失代价和缺失率:
    • 非阻塞缓存 (Non-Blocking Caches / Lockup-Free Caches): Cache缺失时允许CPU继续执行其他不相关的指令(hit under miss, miss under miss)。
    • 硬件预取 (Hardware Prefetching): 硬件根据访存模式预测未来需要的块并提前加载。
    • 编译器控制的预取 (Compiler Prefetching): 编译器插入预取指令。

(P21-P22 提供了各种Cache优化技术的总结表格,包括对命中时间、缺失代价、缺失率、硬件复杂度的影响以及评论)


§3.6 CPU 漏洞实战分析 (Meltdown & Spectre) - 简介

本节介绍利用现代处理器特性(如乱序执行、推测执行和缓存机制)进行攻击的CPU漏洞,特别是 Meltdown 和 Spectre。

相关背景:缓存侧信道攻击 (Cache Side-Channel Attack)

  • 缓存 (Cache): CPU和主存之间数据交互的桥梁。存储敏感数据(密钥等)时若处理不当,易被察觉和泄露。
  • 共享缓存: 不同应用(甚至不同核心的进程)可能共享同一块缓存,数据可以互相替换,这为跨越安全隔离边界提供了可能。
  • 攻击原理: 攻击者利用访存时延的差异(缓存命中比缓存缺失快得多)来推测受害者的访存行为或泄露机密信息。
  • 特点: 细粒度、高隐蔽性。可以跨平台和CPU。

(P2 展示了处理器核心间、处理器间、核心内进程间缓存共享的示意图)

漏洞介绍 (P3)

  • Meltdown (熔断):
    • 影响: Linux, macOS, Windows 等主流操作系统上的 Intel CPU。
    • 原理: 利用 Intel CPU 的乱序执行 (out-of-order execution) 技术。通过对内存的响应时间差建立侧信道,突破用户态和内核态之间的基本隔离,使得低权限用户程序可以“越界”访问系统级内存(如内核空间),造成数据泄露。
    • “熔化”了由硬件实现的安全边界。
  • Spectre (幽灵):
    • 影响: 破坏不同应用程序之间的隔离。几乎影响所有现代高性能处理器 (Intel, AMD, ARM)。
    • 原理: 利用处理器的预测执行 (speculative execution) 机制。处理器会推测未来有用的数据并提前执行计算。在此过程中,即使是低权限的应用程序也可能在预测执行路径上访问到本应被隔离的私有数据(例如,通过操纵分支预测器使其错误预测,导致在权限检查完成前就访问了非法内存,并将数据带入缓存)。
    • 尽管错误预测的结果会被丢弃,但对CPU缓存的影响(数据被加载到缓存)会被保留。

CPU 缓存缺陷分析 (P4-P5)

  • 核心问题: 在具有预测执行/乱序执行能力的处理器中,内存加载到缓存这个环节,可能不依赖于前序指令(如权限检查指令)是否能正常完成,并且在这个阶段不会验证访问内存的合法性
  • 流程:
    1. 指令获取解码。
    2. (乱序/预测)执行指令,例如指令A需要访问某受保护内存地址X。
    3. 在真正提交指令A并进行权限检查之前,CPU可能已经将地址X的内容加载到了CPU缓存中。
    4. 如果指令A最终因权限不足而失败(或分支预测错误),其执行结果(寄存器更新等)会被撤销,异常可能不会触发。
    5. 但是,数据X已经存在于缓存中了!
  • CPU只有在数据从缓存加载到寄存器这个环节才会去检测地址是否合法。分支预测/乱序执行可能仅仅完成了内存到CPU缓存的加载。
  • 虽然用户态程序没有权限直接访问内核内存或CPU缓存中的数据,但如果能通过侧信道“感知”到CPU缓存中的内容,逻辑缺陷就暴露了。

侧信道攻击缓存 (P6)

  • 多核之间实现 Cache 数据共享,改善了CPU与内存访问速度之间的矛盾。
  • Cache 命中和失效对应的响应时间有显著差别。
  • 攻击流程(高度概括):
    1. 触发加载: 攻击者通过某种方式(如利用Meltdown的乱序执行或Spectre的预测执行)使得一个受保护的、攻击者无法直接读取的内存地址 S (Secret) 的内容被加载到CPU缓存中。
    2. 探测缓存: 攻击者设计一系列对自己可控内存区域 P_i 的访问。如果在上一步中,秘密数据 S 的某个字节的值是 v,并且攻击代码被构造成会基于 v 去访问 P_v (例如,如果秘密字节是ASCII ‘A’=65, 则访问 ProbeArray[65*CacheLineSize])。
    3. 计时分析: 攻击者测量访问各个 P_i 的时间。如果访问 P_v 的时间远小于其他 P_i,则说明 P_v 之前被加载到了缓存,从而推断出秘密字节 S 的值是 v
  • 对于CPU缓存中的数据,在用户态和内核态都是无法“正常”访问的(除非数据被加载到寄存器)。但侧信道提供了一种间接读取的方式。

Meltdown 漏洞原理与攻击过程 (P8-P10)

  • 原理回顾: 利用CPU乱序执行特性。CPU在等待某些资源(如内存读取)时,会利用空闲计算能力继续执行后续指令。指令的提交(retirement)必须按序进行,而安全检查通常在提交时进行。这导致在安全检查前,某些后续指令可能因乱序执行而被提前执行。
  • 攻击阶段:
    1. 获取解码: 获取指令,解码后存放到执行缓冲区(如保留站)。
    2. 乱序执行: 指令乱序执行,结果保存在结果序列中(如ROB)。
    3. 退休期 (Retired Circle / Commit): 重新排列结果序列,进行安全检查(如地址访问权限),提交结果到寄存器。
  • 攻击示例代码 (P10):
    PLAINTEXT
    1. ; rcx = kernel address (secret data location)
    2. ; rbx = probe array (attacker-controlled, large enough)
    3. mov al, byte [rcx]        ; Attempt to read kernel byte into al (will cause exception on commit)
    4. shl rax, 0xc              ; rax = al * 4096 (page size, assuming al is in rax)
       probe_array[rax]          ; Access probe_array at an offset dependent on the secret byte
                                 ; This line is a simplification, actual access is more complex
                                 ; e.g., mov rbx, qword [rbx + rax]
    5. ; // 访问rbx+rax处的内存,将对应cache行加载到CPU缓存中
  • 利用步骤:
    1. 指令获取解码。
    2. 乱序执行第3条指令: 尝试读取内核地址 [rcx] 的内容到 al。同时,指令4和5等待指令3的结果。
    3. 推测执行指令4和5: 在指令3的权限检查(提交阶段)之前,CPU可能已经乱序执行了指令4和5。指令5会根据(推测的)al 的值访问 probe_array 中的一个位置,并将该位置所在的 Cache 行加载到 CPU Cache 中。
    4. 安全检测与回滚: 当CPU准备提交指令3时,会进行安全检查,发现非法访问内核内存,于是丢弃指令3、4、5的执行结果,恢复CPU状态到乱序执行前。但是,CPU Cache的状态(probe_array 的某个Cache行被加载)通常不会恢复!
    5. 缓存侧信道攻击: 攻击者遍历 probe_array 的所有可能被访问的 Cache 行。由于被内核字节值索引的那个 Cache 行此时已经在缓存中,访问它的时间会远小于其他行。通过计时,攻击者可以推断出哪个内存页(Cache 行)被访问过,从而推断出内核内存字节的值。

(P11 漏洞验证截图)

Meltdown 缓解措施 (P12)

  • KAISER (Kernel Address Isolation to Have Side-channels Efficiently Removed) / KPTI (Kernel Page Table Isolation):
    • 一种内核修改,使得在用户空间运行时,内核的大部分地址空间不映射到用户进程的页表中。这样,即使用户程序尝试非法访问内核地址,在地址翻译阶段就会失败,或者乱序执行时也无法轻易访问到实际的内核数据。
    • 问题: 仍存在一些限制。由于x86架构设计,仍需要在用户空间映射少量必要的内核内存位置(如中断处理入口),这些可能构成残留攻击面。

Spectre 漏洞原理与攻击过程 (P14-P15)

  • 原理回顾: 利用CPU的预测执行 (speculative execution),特别是分支预测 (branch prediction)。CPU会预测分支的走向并提前执行预测路径上的指令,以提高流水线性能。如果预测错误,结果会被丢弃,状态回滚。但与乱序执行类似,对CPU缓存的影响可能被保留。

  • 攻击阶段 (P15):

    1. 训练BPU: 攻击者通过特定方式执行代码,“训练”CPU的分支预测单元 (BPU),使其在后续执行漏洞利用代码时,按照攻击者期望的方式进行错误预测。
    2. 预测执行加载: 在BPU被“误导”后,当CPU执行到一个关键的条件分支时(例如一个边界检查 if (x < array1_size)),即使条件实际上为假(x 越界),BPU也可能错误地预测为真,导致CPU推测执行分支内的代码。这些推测执行的代码可能包含一个依赖于 array1[x](此时 x 是一个受害者控制的、可能非法的索引)的内存访问,例如 y = array2[array1[x] * 256]。这个访问会将 array2 中由 array1[x] 的内容索引的位置加载到CPU Cache中。
    3. 缓存侧信道推测: 当CPU最终发现分支预测错误时,会撤销推测执行的结果。但是,array2 中被访问过的Cache行仍然在缓存中。攻击者通过计时分析访问 array2 的不同位置,可以推断出 array1[x](即泄露的数据)的值。
  • Spectre 攻击示例代码 (P15):

    C
    1. if (x < array1_size) {             // Vulnerable conditional branch
    2.   y = array2[array1[x] * 256];   // array1[x] is the secret, array2 is the probe array
    3.   // do something using Y that is observable via side channel (not strictly necessary for cache attack)
    4. }
    5. // observe when speculatively executed (e.g., by timing access to array2)
    • 攻击者会多次用合法的 x 值执行此代码,使BPU倾向于预测 if 条件为真。
    • 然后,用一个非法的 x (越界值,指向受害者内存) 执行。BPU可能仍预测为真,推测执行第2行。
    • array1[x] 读取了秘密数据。
    • array2[...] 的访问会将秘密数据相关的Cache行加载。
    • 之后通过计时 array2 的访问来恢复秘密。

(P16 漏洞验证截图)

Spectre 缓解措施 (P17)

  • 序列化指令 (e.g., LFENCE): 在关键分支后插入序列化指令,限制预测执行的深度或范围。
    • 问题: 可能无法在所有CPU或系统配置中有效工作,且性能影响较大。
  • 插入推测执行阻止指令 (Speculation Barrier / retpoline):
    • Retpoline (“return trampoline”): 一种编译器技术,用间接跳转(通过受控的返回指令序列)替换易受攻击的间接分支,使得BPU难以准确预测其目标,从而阻止有害的推测执行。
    • 问题: 可能会严重降低性能,尤其对于缓解IBRS (Indirect Branch Restricted Speculation)等场景。
  • 微代码修复现有处理器:
    • CPU厂商发布微代码更新来修改BPU行为或提供新的控制机制(如IBRS, STIBP, SSBD)。
    • 问题: 修补程序可能阻止推测执行或推测内存读取,但这会极大地破坏性能。

Meltdown & Spectre 对比 (P18)

  1. 核心机制:
    • Meltdown: 依赖CPU的乱序执行
    • Spectre: 主要利用CPU的分支预测来实现有害的推测执行
  2. 利用点与影响:
    • Meltdown: 利用了Intel处理器特有的提权漏洞(乱序执行期间权限检查延迟),导致推测性执行的指令可以绕过内存保护,直接从用户空间推测性地读取内核内存。异常在提交时才发现,但数据已入缓存。
    • Spectre: 利用难度更大。通过“训练”分支预测器,诱导CPU推测性地执行本不该执行的路径,从而泄露同一进程内或跨进程的数据。影响范围更广,包括AMD, ARM, Intel等多数现代处理器。KAISER补丁对Spectre无效。

(P20-P23 详细的实验环境、漏洞检测工具输出、POC代码复现结果)

其他新场景下的攻击 (P25)

  • 基于硬件特性的攻击: 利用处理器查找逻辑、替换策略、预取技术、瞬态执行(transient execution,指那些最终会被撤销但仍可能留下可观测副作用的指令执行路径,如错误预测或乱序执行中的路径)等。攻击者需深入了解硬件架构,发现难度大,且通常对软件透明,更难被防御机制检测。
  • 安全机制场景下的攻击: 即使有可信执行环境 (TEE) 等软硬件安全机制,如果不同安全域之间存在缓存共享,且隔离不彻底,仍可能遭受缓存侧信道攻击。
  • 性能驱动下的攻击: 在对性能要求苛刻的环境(如运行窗口短、并发噪声大、缓存预取干扰),攻击者需要更精密的手段。攻击性能提升主要体有:攻击速度、攻击精度、泄露信息量。

攻击分布情况 (P26)

  • 基于缓存共有性质的攻击具有通用性。
  • 新型攻击特定于缓存的具体实现,范围较小但危害不容忽视。
  • 目前主流商用处理器均未能完全抵御缓存侧信道攻击,尤其是Intel处理器因性能优化忽略了许多安全考虑,导致几乎任何Cache攻击均能实现。

Cache 安全增强方案 (P27-P33)

商用处理器Cache安全增强方案总结 (P29)

攻击类型Intel采用的防御方法ARM采用的防御方法
边界检查绕过 (Spectre-V1)内存屏障指令: LFENCE新内存屏障指令: CSDB (推测屏障)
分支目标注入 (Spectre-V2)返回跳板 (retpoline), IBRS/STIBP (微码)无通用缓解方法 (依赖软件和微码)
数据缓存恶意加载 (Meltdown)内核页表隔离技术 (KPTI/PTI)(Meltdown主要影响Intel)
系统寄存器恶意加载 (变体3a)更新微码非必要 (通常不直接受此影响)
推测存储绕过 (Spectre-V4)LFENCE指令; 特殊模块寄存器(MSR)SSBD标识位两种新内存屏障指令: SSBB, PSSBB
  • 目前主要针对已披露的 Meltdown 和 Spectre 攻击。其他类型的缓存侧信道攻击尚未有具体防御手段。

基于软件的防御机制分析 (P30)

  • 漏洞检测方法: 通过静态或动态程序分析,找出程序中可能存在的缓存侧信道漏洞部分。
  • 用户级防御措施: 在漏洞检测基础上增强用户程序,如保持特定运算时间恒定(避免时序差异)、清除缓存(使用 clflush 等指令)。
  • 系统级防御措施: 从操作系统/虚拟机管理程序的角度防御,通过完善内存管理(如KPTI)或锁定缓存行等方法避免攻击者恶意探查。
  • 优点: 软件实现灵活,可快速部署。
  • 缺点: 依赖下层微架构实现,或需硬件额外支持以降低开销,不同场景效果不一。长期看仍需硬件层面防范。

基于硬件的防御机制分析 (P31-P33)

  1. 缓存分组方法 (Cache Partitioning / Way Partitioning) (P31):
    • 将缓存(通常是LLC的ways)划分为不同区域,分配给不同进程/安全域使用,实现隔离。
    • 优点: 从原理上完全避免因共享缓存导致的侧信道攻击。
    • 缺点: 实现受硬件限制(需要硬件支持划分),对性能影响较大(每个用户可用的Cache减少)。
  2. 缓存随机化方法 (Cache Randomization) (P32):
    • 消除特定物理地址在缓存中的固定映射关系,使攻击者难以推断受害者的访存地址。
    • 基于表的映射: 将随机映射存储于表,通过查表获取实际缓存索引。
    • 基于计算的映射: 根据地址通过加密或哈希等方法计算随机映射。
    • 优点: 性能开销较小,有效性得到较充分验证。
    • 缺点: 无法完全避免侧信道攻击(仍可能通过统计分析等方式探测),但能大幅缓解。
  3. 其他防御策略 (P33):
    • 针对目录结构的防御方法 (e.g., for inclusive caches):
      • 从源头出发: 增加目录项数量,减少冲突。
      • 从最终目标出发: 检测跨核心缓存行替换,重载被替换的缓存行至L2(防止信息从LLC驱逐时泄露到下级共享结构)。
    • 完善软硬件协作关系,定义新指令集体系结构:
      • 数据遗忘编程 (Data Oblivious Programming): 消除机密信息与分支执行时延差异的关联。程序执行路径不依赖于秘密数据。
      • 优化基本块执行: 减少指令执行时的分支预测依赖。

第三章总结 (P34 & P23 of first deck)

  1. 存储层次结构与技术趋势 (Trend and Memory Hierarchy):
    • 处理器-存储器性能差距 (Processor-Memory Performance Gap) 是核心挑战。
    • 利用局部性原理 (Cache Locality - 时间局部性、空间局部性) 构建存储层次。
    • Cache 命中/缺失 (Cache hit/miss) 是关键性能指标。
  2. Cache 设计的四个基本问题 (Four Questions for Cache Designers):
    • 块放置 (Block Placement: Direct-mapped, Fully-associative, Set-associative)
    • 块识别 (Block Identification: Tag, Index, Offset, Valid bit)
    • 块替换 (Block Replacement: Random, LRU, FIFO, OPT)
    • 写策略 (Write Strategy: Write-through vs. Write-back; Write-allocate vs. No-write-allocate; Write buffer)
  3. Cache 性能 (Cache performance):
    • 评估指标:AMAT, 存储停顿周期。
    • 优化目标:减少缺失率、减少缺失代价、减少命中时间。 (P23 of first deck)
  4. CPU 漏洞与 Cache 安全 (CPU vulnerabilities & Cache security):
    • 理解 Meltdown 和 Spectre 等利用现代CPU特性和缓存侧信道的攻击。
    • 探讨软硬件层面的防御和增强方案。

Thanks for reading!

计算机系统 III 硬件第三章:存储层次结构

周四 9月 25 2025 Course
9003 字 · 33 分钟
cover

His Smile

麗美