目录
1. 多级缓存和内存层次结构
2. 缓存相关的术语
2.1 Set associative caches 和 ways
2.3 Inclusive 和 exclusive caches
3. 高速缓存控制器
4. Cache Policies
5. Point of Coherency 和 Unification
6. Cache maintenance
7. Cache discovery
Cahe 即高速缓存,其存在的理论基础:
对高速缓存存储器的访问比对主存储器的访问速度要快得多。高速缓存(cache)是片上 SRAM 的小块(small blocks),可以以与内核相同的速度运行,而访问外部存储器需要数十甚至数百个内核周期。
因为哈佛架构具有独立的指令和数据总线,所以 L1 缓存具有独立的指令缓存和数据缓存。但 L2 和 L3 是统一缓存。
Line
一个或多个脏位(dirty bits)
缓存行中包含缓存的数据或指令时称为有效,否则称为无效。
Way
Set
Tag
Index
Index 是内存地址的一部分,它确定可以在缓存的哪些行中找到该地址的数据。
Offset
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 缓存更有用。
每个 cache line 都有一个与之关联的 tag,该 tag 记录了与该 cache line 关联的外部存储器中的物理地址。高速缓存行的大小由实现定义。但是由于互连,所有 CPU Core 都应具有相同的高速缓存行大小。
数据访问时,物理地址用于确定数据在缓存中的位置:
VIVT、VIPT、PIPT
缓存类型 | 描述 |
---|---|
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 处理,缓存微架构在硬件上已经解决缓存别名问题。
ARM v8和v9 架构的 SOC 采用 inclusive 缓存模型。
高速缓存控制器是负责管理高速缓存内存的硬件块,其对程序来说在很大程度上是不可见的。它自动将代码或数据从主存写入缓存。它接受来自 CPU Core 的读写内存请求,并对高速缓存或外部存储器执行必要的操作。
缓存查找(cache look-up)
缓存命中(cache hit)
缓存未命中(cache miss)
缓存行填充(cache linefill)
这里介绍一个可视化 cache 行为模拟工具: 4-Way Set Associative Cache
缓存策略使我们能够描述何时应将 cache line 分配给数据缓存,以及当执行存储指令时命中数据缓存会发生什么。
缓存分配策略包括:Write allocation (WA) 和 Read allocation (RA)。
Write allocation (WA)
Read allocation (RA)
缓存更新策略包括:Write-back (WB) 和 Write-through (WT)。
Write-back (WB)
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 , addr
Where:
| #uimm5
PLD for prefetch for load PST for prefetch for store
L1 for L1 cache, L2 for L2 cache, L3 for L3 cache
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.
对于 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 代码能够确保未来的指令获取能够正确地从修改后的代码版本中执行。他们可以通过使用两个阶段的过程来做到这一点:
按地址使指令缓存(instruction cache)条目无效(Invalidate)。
ARM 架构不需要硬件来确保指令缓存(instruction caches)和内存之间的一致性,即使对于共享内存的位置也是如此。
软件有时需要清理缓存(Clean)或使缓存失效(Invalidate),可能的场景包括但不限于:
缓存维护操作集包括:
Invalidate
Clean
Zero
对于这些操作中的每一项,你可以选择该操作应适用于哪些条目:
Modified Virtual Address (MVA),VA 的另一个名称,是包含特定虚拟地址的高速缓存行
Set 或 Way 是根据其在缓存结构中的位置选择的特定缓存行
AArch64 缓存维护操作使用具有以下一般形式的指令执行
{, }
有许多操作可用:
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》
缓存维护操作可以通过 cache set、way 或虚拟地址来执行。与平台无关的代码可能需要知道缓存的大小、缓存行的大小、sets 和 ways 的数量以及系统中有多少级缓存。重置后缓存失效和零操作最有可能出现此要求。架构缓存上的所有其他操作都可能在 PoC 或 PoU 的基础上进行。
有许多包含这些信息的系统控制寄存器:
确定高速缓存中的 sets 和 ways 的数量, 需要对两个单独的寄存器进行异常级别访问。
然后代码读取 Cache Size ID Register (CCSIDR/CCSIDR_EL1) 获取当前选定缓存的体系结构的信息。
Data cache Zero ID Register (DCZID_EL0) 包含要为 Zero operations 操作归零的块大小。