目录
2.1 Set associative caches 和 ways
2.3 Inclusive 和 exclusive caches
5. Point of Coherency 和 Unification
1. 多级缓存和内存层次结构
Cahe 即高速缓存,其存在的理论基础:
- 时间局部性(Temporal locality)
- 空间局部性(Spatial locality)
对高速缓存存储器的访问比对主存储器的访问速度要快得多。高速缓存(cache)是片上 SRAM 的小块(small blocks),可以以与内核相同的速度运行,而访问外部存储器需要数十甚至数百个内核周期。
因为哈佛架构具有独立的指令和数据总线,所以 L1 缓存具有独立的指令缓存和数据缓存。但 L2 和 L3 是统一缓存。
2. 缓存相关的术语
Line
- 缓存行,指 Cache 的最小可加载单元,即上图 4x4 的方块中的一行。其数据来自于主存或者下一级缓存的连续字块。
- 每一行数据都有一个或多个状态位,包括:
- 一个有效位(valid bit)
-
一个或多个脏位(dirty bits)
-
缓存行中包含缓存的数据或指令时称为有效,否则称为无效。
Way
- way 是缓存的细分,每个 way 的大小相同并以相同的方式索引。即上图中一个 4x4 的方块称为一个 Way。
Set
- 所有 ways 上, 由共享特定索引的缓存行组成。即在上图中,不同的 4x4 方块,相同行号的所有行组成一个 Set。
Tag
- tag 是存储在高速缓存中的内存地址的一部分,用于标识与数据行关联的主内存地址。
- tag 既是来自 CPU 的地址的一部分,也会被存储在 Cache 中,作为 cache line 的一部分。
- 在数据访问时,会将来自CPU 地址中的 tag 和 cache line 中的 tag 比较是否匹配来确定cache line 中的数据是否来自于主存中对应的物理地址,详见下文。
Index
-
Index 是内存地址的一部分,它确定可以在缓存的哪些行中找到该地址的数据。
Offset
- 用于选择位于缓存行中的相关数据项。
2.1 Set associative caches 和 ways
ARM 内核的主缓存始终使用一组关联缓存来实现。这显着降低了直接映射缓存出现缓存抖动的可能性,提高了程序执行速度并提供了更具确定性的执行。它以增加硬件复杂性和略微增加功率为代价,因为每个周期都会比较多个 tags。
图中,来自地址 0x00、0x40 或 0x80 的数据可能会在任一缓存 way 的第 0 行中找到,但不能同时存在于两个缓存 ways 中。
增加缓存的关联性会降低抖动的可能性的含义:
假设程序循环访问 0x00~0x70 的数据,如果不采用 set associative caches,即只存在 cache way 0,那么 0x00 和 0x40 会共享 cache line 0,依此类推,会造成频繁的 cache 驱逐操作。而采用 2-way set-associative cache,那么 0x00~0x30 的数据缓存在 cache way 0,0x40~0x70 的数据缓存在 cache way 1,那么将获得很好的读写性能。
理想的情况是完全关联的缓存,其中任何主内存位置都可以映射到缓存中的任何位置。但是,除了缓存非常小(例如与 MMU TLB 相关的缓存)之外,构建这样的缓存是不切实际的。在实践中,8-ways 以上的性能改进是最小的,16-ways 关联性对于更大的 L2 缓存更有用。
2.2 Cache tags 和 Physical Addresses
每个 cache line 都有一个与之关联的 tag,该 tag 记录了与该 cache line 关联的外部存储器中的物理地址。高速缓存行的大小由实现定义。但是由于互连,所有 CPU Core 都应具有相同的高速缓存行大小。
数据访问时,物理地址用于确定数据在缓存中的位置:
- 中间位:index bits,用作索引以选择高速缓存 set 的特定行,注意 index 的作用就是确定一个 set,但是其中包含多个 cache line。
- 最高有效位:tag bits,标识地址的其余部分,并用于与 index bits 选择的多个 cache line 中存储的 tag 比较,确定是哪个 cache line。
- 最低有效位:offset bits,用于选择高速缓存行中的相关数据项。
VIVT、VIPT、PIPT
- VIVT :Virtual Index Virtual Tag
- VIPT :Virtual Index Physical Tag
- PIPT :Physical Index Physical Tag
缓存类型 | 描述 |
---|---|
VIVT | 导致 cache 别名(cache alias) 问题。 比如当两个虚拟地址对应相同物理地址,并且没有映射到同一 cache line,那么就会产生问题。 |
VIPT | 当进程切换时,不在需要对 cache 进行 invalidate 等操作(因为匹配过程中需要使用物理地址)。 但是这种方法仍然存在 cache 别名的问题(即两个不同的虚拟地址映射到同一物理地址,且位于不同的 cache line),但是可以通过一定手段解决。 |
PIPT | 现代的 ARM Cortex-A 大多采用PIPT方式,由于采用物理地址作为 Index 和 Tag,所以不会产生cache alias 问题。 不过PIPT的方式在芯片的设计要比 VIPT 复杂得多,而且需要等待 TLB/MMU 将虚拟地址转换为物理地址后,才能进行 cache line寻找操作。 |
在 ARM v8 和 v9 架构中, 通常采用 VIPT,好处是 cache 控制器通过虚拟地址的 index bits 查找 cache set 的动作可以和 TLB/MMU 将虚拟地址转换成物理地址的动作同步进行。但是 VIPT 会导致重名(或别名)问题,即不同的虚拟地址被映射到相同的物理地址。
Virtually indexed, Physically Tagged (VIPT) behaving as Physically Indexed, Physically Tagged (PIPT)。
根据 ARM Technical Reference Manual 的描述,软件可以将缓存作为 PIPT 处理,缓存微架构在硬件上已经解决缓存别名问题。
2.3 Inclusive 和 exclusive caches
- 在 inclusive 缓存模型中,相同的数据可以同时存在于 L1 和 L2 缓存中。
- 在 exclusive 缓存模型中,数据只能存在于一个缓存中,并且不能同时在 L1 和 L2 缓存中找到相同地址。
ARM v8和v9 架构的 SOC 采用 inclusive 缓存模型。
3. 高速缓存控制器
高速缓存控制器是负责管理高速缓存内存的硬件块,其对程序来说在很大程度上是不可见的。它自动将代码或数据从主存写入缓存。它接受来自 CPU Core 的读写内存请求,并对高速缓存或外部存储器执行必要的操作。
缓存查找(cache look-up)
- 当它收到来自核心的请求时,它必须检查是否要在缓存中找到所请求的地址,这称为缓存查找(cache look-up)。
缓存命中(cache hit)
- 缓存查找通过将请求的地址位的子集与与缓存中的行关联的 tag 值进行比较。
- 如果存在匹配,称为缓存命中(cache hit),并且该行被标记为有效,则使用高速缓存进行读取或写入。
缓存未命中(cache miss)
- 当 CPU core 从特定地址请求指令或数据,但与缓存 tag 不匹配或 tag 无效时,会导致缓存未命中(cache miss),并且必须将请求传递到内存层次结构的下一层,即 L2缓存或外部存储器。
缓存行填充(cache linefill)
- 当缓存未命中时,还可能导致缓存行填充(cache linefill),它会将一块主内存的内容复制到缓存中。同时请求的数据或指令被流式传输到 CPU core 。
- 这个过程是透明地发生的,软件开发人员不能直接看到。在 使用数据之前,core 不需要等待 linefill 完成。高速缓存控制器通常首先访问高速缓存行内的 critical word。
- 例如,如果您执行的加载指令在缓存中未命中并触发缓存行填充,则内核首先检索缓存行中包含所请求数据的那部分。这些关键数据被提供给 core 流水线,而缓存硬件和外部总线接口随后在后台读取缓存线的其余部分。
这里介绍一个可视化 cache 行为模拟工具: 4-Way Set Associative Cache
4. Cache Policies
缓存策略使我们能够描述何时应将 cache line 分配给数据缓存,以及当执行存储指令时命中数据缓存会发生什么。
缓存分配策略包括:Write allocation (WA) 和 Read allocation (RA)。
Write allocation (WA)
- 在写未命中时分配高速缓存行。这意味着在处理器上执行存储指令可能会导致发生突发读取(burst read)。在执行写入之前,有一个 linefill 来获取缓存行的数据。缓存包含整行,这是其最小的可加载单元,即使您只写入行内的单个字节。
Read allocation (RA)
- 在读取未命中时分配高速缓存行。
缓存更新策略包括:Write-back (WB) 和 Write-through (WT)。
Write-back (WB)
- 写入仅更新缓存并将缓存行标记为脏。仅当 cache line 被逐出(evicted)或明确清理(explicitly cleaned)时才更新到外部存储器。
Write-through (WT)
- 写入会更新缓存和外部内存系统,但不会将缓存行标记为脏。
在 WT 和 WB 缓存模式下,读取数据时,命中缓存的行为相同。
普通内存的可缓存属性分别指定为内部(inner)和外部(outer)属性(attributes),内部和外部之间的划分由实现定义。通常内部属性由集成缓存使用,外部属性在处理器内存总线上可供外部缓存使用。
处理器可以推测性地访问普通内存,这意味着它可以潜在地自动将数据加载到缓存中,而无需程序员明确请求特定地址。但是程序员也可以向内核指示将来使用哪些数据。 ARMv8-A 提供预加载提示指令。
缓存是否支持推测和预加载由实现定义。可以使用以下指令:
AArch64: PRFM PLDL1KEEP, [Xm, #imm]
这表明从 Xm + 偏移量加载到 L1 缓存中的预取作为
临时预取(temporal prefetch),这意味着数据可
能会被多次使用。
AArch32: PLD Rm
将数据从 Rm 中的地址预加载到缓存中
更一般地,预取内存的 A64 指令具有以下形式:
PRFM <prfop>, addr
Where:
<prfop> <type><target><policy> | #uimm5
<type> PLD for prefetch for load PST for prefetch for store
<target> L1 for L1 cache, L2 for L2 cache, L3 for L3 cache
<policy> KEEP for retain or temporal prefetch means allocate
in cache normally STRM for streaming or non-temporal
prefetch means the memory is used only once
uimm5 Represents the hint encodings as a 5-bit immediate. These are optional.
5. Point of Coherency 和 Unification
对于 set-based 和 way-based 的 Cache 清理(clean)和无效(invalidate)操作,需要在指定层级的 cache 上操作。在使用虚拟地址的操作时,为了保证 cache 数据的一致性,架构定义了两点:
Point of Coherency (PoC)。对于特定地址,PoC 确保所有观察者(例如,可以访问内存的 cores、DSP 或 DMA engines)对相同内存位置(通常指主外部系统内存)的副本,看到的内容是一致的。
Point of Unification (PoU)。对于一个 core, PoU 是保证这个 core 的指令和数据缓存以及 TLB, 在观察相同内存位置的多个副本时,它们的内容是一致的。例如,统一的 L2 缓存时系统中的一个统一点,这个系统指具有哈佛 L1 缓存和用于缓存转换表条目的 TLB。如果不存在外部缓存,则主内存将是统一点。
换句话说:
- POC 是一个横向的概念,它保证不同 core 以及总线上其他 master 之间内存的一致性。
- POU 是一个纵向的概念,保证单个 core 或者其他 maser,在不同层级的内存结构之间,保证数据一致性。
对PoU的了解使 self-modifying 代码能够确保未来的指令获取能够正确地从修改后的代码版本中执行。他们可以通过使用两个阶段的过程来做到这一点:
- 按地址清理(Clean)相关数据缓存(data cache)条目。
-
按地址使指令缓存(instruction cache)条目无效(Invalidate)。
ARM 架构不需要硬件来确保指令缓存(instruction caches)和内存之间的一致性,即使对于共享内存的位置也是如此。
6. Cache maintenance
软件有时需要清理缓存(Clean)或使缓存失效(Invalidate),可能的场景包括但不限于:
- 当外部内存中的内容已更改并且需要从缓存中删除陈旧数据时,可能需要这样做。
- 在与 MMU 相关的活动(例如更改访问权限、缓存策略或虚拟到物理地址映射)之后。
- 当 I 和 D 缓存必须为动态生成的代码(例如 JIT 编译器和动态库加载器)同步时也可能需要它。
缓存维护操作集包括:
Invalidate
- 高速缓存或高速缓存行的无效意味着通过清除一个或多个高速缓存行的有效位来清除其中的数据。
- 缓存在重置后必须始终无效,因为它的内容是未定义的。
- 这也是一种让缓存的用户看到缓存外的内存域中数据发生改变的方式。
Clean
- 清理高速缓存或高速缓存行意味着将标记为脏的高速缓存行的内容写入下一级高速缓存或主内存,并清除高速缓存行中的脏位。
- 这使得高速缓存行的内容与高速缓存或内存系统的下一级保持一致。这仅适用于使用回写策略的数据缓存。这也是一种使缓存中的更改对外部内存域的用户可见的方式,但仅适用于数据缓存。
Zero
- 可以将缓存中的一个内存块清零,而不需要首先从外域读取其内容。这只适用于数据缓冲区。
对于这些操作中的每一项,你可以选择该操作应适用于哪些条目:
- all,表示整个缓存,但是不可用于数据缓存或统一缓存
-
Modified Virtual Address (MVA),VA 的另一个名称,是包含特定虚拟地址的高速缓存行
-
Set 或 Way 是根据其在缓存结构中的位置选择的特定缓存行
AArch64 缓存维护操作使用具有以下一般形式的指令执行
<cache> <operation>{, <Xt>}
有许多操作可用:
Cache | Operation | Description |
---|---|---|
DC | CISW | Clean and invalidate by Set/Way |
CIVAC | Clean and Invalidate by Virtual Address to Point of Coherency | |
CSW | Clean by Set/Way | |
CVAC | Clean by Virtual Address to Point of Coherency | |
CVAU | Clean by Virtual Address to Point of Unification | |
ISW | Invalidate by Set/Way | |
IVAC | Invalidate by Virtual Address, to Point of Coherency | |
DC | ZVA | Cache zero by Virtual Address |
IC | IALLUIS | Invalidate all, to Point of Unification, Inner Sharable |
IALLU | Invalidate all, to Point of Unification, Inner Shareable | |
IVAU | Invalidate by Virtual Address to Point of Unification |
详见:《Programmer’s Guide for ARMv8-A》
7. Cache discovery
缓存维护操作可以通过 cache set、way 或虚拟地址来执行。与平台无关的代码可能需要知道缓存的大小、缓存行的大小、sets 和 ways 的数量以及系统中有多少级缓存。重置后缓存失效和零操作最有可能出现此要求。架构缓存上的所有其他操作都可能在 PoC 或 PoU 的基础上进行。
有许多包含这些信息的系统控制寄存器:
- 缓存级别的数量可以通过软件读取 Cache Level ID Register (CLIDR_EL1) 来确定。
- cache line 大小在 Cache Type Register (CTR_EL0) 中给出。
- 如果需要由运行在 EL0 的用户代码访问,可以通过设置 System Control Register (SCTLR/SCTLR_EL1) 的 UCT 位来完成。
确定高速缓存中的 sets 和 ways 的数量, 需要对两个单独的寄存器进行异常级别访问。
- 代码必须首先写入 Cache Size Selection Register (CSSELR_EL1) 以选择您想要获取信息的缓存。
-
然后代码读取 Cache Size ID Register (CCSIDR/CCSIDR_EL1) 获取当前选定缓存的体系结构的信息。
-
Data cache Zero ID Register (DCZID_EL0) 包含要为 Zero operations 操作归零的块大小。