当ARM架构最初被开发时,处理的时钟速率和内存的访问速率基本相当。现在处理器core更加复杂且是内存时钟的指数级倍。但外部总线和内存设备的频率却没有达到这个范围。实现小块的速率可以达到core的片上SARM是可能的,但是这种RAM与标准DRAM块相比昂贵很多,标准DRAM比这种RAM容量大得多。在很多ARM处理器系统中,访问外部内存需要10倍甚至100倍的core周期。 Cache就是小块的,快速的内存,它位于core和主内存之间。它保存着主内存的拷贝。访问cache明显快于访问主内存。当core访问(读或写)某个特殊地址,它首先在cache中查找。如果在cache中找到这个地址,它使用cache中的数据,而不是发起主内存的访问。这能通过减少缓慢的外部内存访问次数来明显提升系统潜在的性能。它也通过避免驱动外部信号来减少系统功耗。 实现了ARMv8-A架构的处理器通常实现了二级或更多级的cache。这通常意味着处理器对每个core有小的L1指令和数据cache。Cortex-A53和Cortex-A57处理器正常来说实现了两级或更多级的cache,即小的L1指令和数据cache,和大的统一的L2 cache(它被一个cluster中的多个core共享)。另外,也可以有外部L3 cache作为外部硬件块被多个cluster共享。 初次访问会将数据提供给cache,并不会比正常访问快。后续对已缓存的数据的访问将快于正常访问,从此时性能会增加。core的硬件会检查cache中的所有获取的指令和读写的数据,虽然你需要标记部分内存,比如包含外设的作为non-cacheable。因为cache仅存储主内存的一个子集,你需要一种方式来决定是否这个地址是你在cache中想要查找的。 有时cache中的指令和数据和外部内存中的数据可能不一样,这是因为处理器可以更新cache中的内容,但并没有写回到主内存中。与此相对,agent可能在core获取到自己的拷贝后更新主内存。这个问题就是一致性问题,将在后续章节中描述。当你有多个core或内存agent(如外部的DMA控制器)时这将成一个特殊的问题。 1 Cache术语在冯诺依曼架构中,单个cache用于指令和数据(统一cache)。修改的哈佛架构有分开的指令和数据总线,因此有两个cache:指令cache(I-cache)和数据cache(D-cache)。在ARMv8处理器中,有不同的指令和数据L1 cache,但有统一的L2 cache。 Cache要求能够缓存地址,数据和一些状态信息。 下面为使用的一些术语的简单介绍,以及说明缓存基本结构的图表: (1)tag为存储在cache中的内存地址的一部分,它来区分与数据线相关的主内存地址。 64bit地址的高位说明了来自主内存中的信息的cache的位置,它是tag位。总共的cache大小为它能够保存数据的大小,虽然RAM用于保存tag值。但是,tag占用了cache的空间。 (2)对每个tag地址保存一个字是没有效率的,因此通常多个位置为一组使用相同的tag。这里逻辑块为cache line,它为最小的cache可加载单元,内存中连续的一块字。当它包含可缓存的数据或指令时cache line被认为valid,否则被认为invalid。 与每个数据线相关的是一个或多个状态位。通常,你有一个有效位,它标识数据线包含着可以使用的数据。这意味着地址tag表示一些真实的值。在数据cache中,你可以也有一个或多个dirty位来标识是否cache line包含数据,但数据和主存中的数据不一样。 (3)index为内存地址的一部分,它决定地址可以在哪个cache line中被找到。 地址的中间位或index区分cache line。index被用来作为cache RAM的地址且不需要作为tag一部分。这将在本章节的后面详细介绍。 (4)way为cache的细分,每中方式的大小相等,并以相同的方式索引。Set由共享某个index的所有way的cache line组成。 (5)这意味着地址的最后几位,称为offset,并不要求被存储在tag。你要求整个line的地址,而不是在line中的每个byte,因此5或6个最低位通常为0。 1.1 组相连cache和路ARM核的cache通常使用一组相连的cache实现。这明显减少直接映射缓存出现cache thrashing的可能性,提高了程序执行速度,赋予更多的确定性执行。它是以增加硬件的复杂度和轻微的功耗为代价,因为多个tag在一个周期被比较。 在这种cache组织中,cache被分为很多相同大小的片,称为way。一个内存位置可以映射到way而不是cache line。地址的index域继续用于选择特定的line,但是现在它只想每个way中的单个line。通常,每个L1 数据cache有二个或四个way。Cortex-A57有3路L1指令cache。通常对于L2 cache,它有16路。 一个外部L3 cache的实现,如ARM CCN-504 Cache Coherent Network,可以有大量的way,它们高度相关,由于它们更大的size。有相同的index值的cache line属于同一个组。为了判断是否命中,你必须检查set中每个tag。 如下图为2路cache。来自地址0x00,0x40或0x80的数据可能在line0中被找到,但并不是在两个cache路中同时被找到。 增加cache的相连减少了thrashing的可能性。最理想的为全相连cache,任何主存的位置可以映射到cache中,但是,构建这种cache只在非常小的cache时实用,比如MMU TLB。在实际情况中,对于8路以上的性能改进最小,16路关联对较大的L2缓存更有用。 1.2 Cache tags和物理地址每个cache line都有一个tag与之相关,该tag记录了与cache line相关的外部内存的物理地址。Cache line的大小是由实现定义的。但是,由于内部互连所有的core需要有相同大小的cache line。 访问的物理地址用来决定数据在cache中的位置。最低位用于选择cache line中的相关项。中间位作为index用来选在cache组中的特定的line。最高位用于标识地址的其他部分,并用于与该line存储的标识进行比较。在ARMv8中,数据cache通常为PIPT(物理index物理tag),但也可以为无别名的VIPT(虚拟index物理tag)。 Cache中每个cache line包含:
ARM cache为组相连。这意味着对于给定的地址有多个可能的cache位置或路。一个组相连的cache明显减少cache thrashing的可能性且改善程序执行速度,但代价为增加硬件复杂度和对功耗有轻微增加。 一个简单的4路组相连的32KB L1 cache(比如Cortex-A57处理器的数据cache),16word cache line长度,如下图所示: 1.3 包含和独占caches考虑一个简单的内存读,比如在单个core处理器上执行LDR X0, [X1]。 (1)如果X1指向内存中的一个位置,它被标记为可cacheable,然后再L1数据cache中会有cache查找; (2)如果在L1 cache中找到地址,然后数据从L1 cache中读取并返回给core;
(3)如果在L1 cache中没有找到地址,但在L2 cache中,cache line会从L2 cache加载到L1 cache中,然后数据返回到core。这会导致cache line从L1中被回收腾出空间,但仍在L2 cache中存在; (4)如果地址既不在L1 cache也不再L2 cache中,数据被同时从外部内存加载到L1和L2 cache中,然后返回给core。这也会导致cache line被回收。 这是相当简化后的视角。对于多核和多cluster系统中,在从外部内存加载之前,需要检查L2 cache或在cluster中的L1 cache或其他cluster。另外,在这里没有考虑L3 cache或系统cache。 这就是包含cache模型,这里相同的数据可能在L1和L2 cache中同时存在。在独占cache中,数据只能在一种cache中存在,地址不能够同时在L1和L2 cache中存在。 2 cache控制器Cache控制器为负责管理cache内存的硬件模块,它是以一种程序不可见的方式。它自动从主存中将代码或数据写到cache中。它从core中获取读写内存请求,并对cache内存或外部内存发起必要的行为。 当它接受到来自core的请求时,它必须检查请求的地址是否在cache中。这就是cache查找过程。它是通过将请求的地址位的子集与cache中tag值做比较。若匹配即命中,cache line被标记为有效,可以使用cache中的内存进行读或写。 当core请求某个地址的指令或数据时,在cache tag中没有匹配,或tag无效,cache没有命中,请求必须被传递到内存层次的下一级,如L2 cache或外部内存。它也会造成cache line的填充。同时请求的数据或指令被发送到core。这个过程是透明的且对软件开发者不可见。core不需要在使用数据前等待cache line填充的完成。cache控制器通常访问cache line中的critical word。比如,你发送一个加载指令,但在cache中没有命中,会触发cache line的填充,core首先取出包含请求的数据的部分cache line。这些critical数据被发送到core的流水线,同时cache硬件和外部总线接口然后在后台读取剩下的cache line。 3 cache策略当一个cache line被分配为数据cache,cache策略描述了当store指令执行且在数据cache中被命中时应该如何做。 cache分配策略如下: Write allocation(WA): 一个cache line在写未命中时分配。这意味着在处理器上执行store指令可能会导致突发读产生。对于cache line,在写发出前通过linefill来获取数据。cache包含整个cache line,它是加载的最小单元,即使你仅在cache line写单个byte。 Read allocation(RA) 一个cache line在读未命中时分配。 cache更新策略如下: Write-back(WB): 写只更新cache且将cache line标识为脏。当cache line被回收或明确的清除时才更新外部的内存。 Write-through(WT) 写同时更新cache和外部内存系统,这不会标记cache line为脏。 数据读在cache中命中的行为在WT和WB cache模式中一样。 正常内存的cacheable属性可分为inner和outer属性。Inner和outer的分界线由实现决定的,这在Memory Ordering中最更详细的描述。通常,inner属性用于集成cache,outer属性用于外部cache使用的处理器内存总线。 正常内存可能被处理器预测性访问,这意味着它可以自动将数据加载到cache中,而无需程序员显示的请求特定的地址。这也在Memory Ordering中最更详细的描述。 但是它也可以让程序员提供暗示给core在将来那些数据可以被使用。ARMv8-A提供提前加载的暗示指令。它是由实现来定义是否cache支持预测和提前加载。下列指令有效:
更普遍的,A64指令用于预取内存的有如下形式: PRFM <prfop>, addr 这里,<prfop> <type><target><policy> | #uimm5 <type> PLD对加载做预取,PST对store做预取 <target> L1/L2/L3 <policy> 用于保留和临时预取的KEEP意味着正常分配在cache中;用于streaming或非临时预取的STRM意味着内存只使用一次 Uimm5 表示暗示被编码为5bit。它是可选的。 4 PoC和PoU对于组为基础和路为基础的清除和无效化,操作针对的是cache中的某一级cache。对于使用VA的操作,架构定义了连个点: (1)一致性点PoC。对于某个地址,PoC为所有能够访问内存的观察者比如core,DSP,dma引擎保证看到一个内存的位置是相同内容。通常,主要是外部系统内存。 (2)统一点PoU。对于core来说,PoU为指令/数据cache以及core中转换表walk保证看到一个内存位置是相同的内容。比如,一个统一的L2 cache将是哈佛系统中L1 cache和缓存转换表项的TLB的PoU。如果没有外部cache存在,主存为PoU。 了解PoU可以自我修改代码,以保证将来从修改后的代码中正确获取指令。他们从过两个阶段来做到这点:
ARM架构不要求硬件来保证指令cache和内存之间的一致性,即使是共享内存的位置。 5 cache维护有时软件进行清除或无效化cache是必要的。当外部内存的内容被修改时,有必要移除cache中无效的数据。在MMU相关活动中(如更改访问权限,cache策略或虚拟地址到物理地址的映射)之后,或者当必须为动态生成的代码(如JIT编译器和动态库加载程序)同步I和D cache时,也可能需要它。
对这些操作,你可以选择操作应该应用于哪些项:
AArch64 cache维护操作通过如下格式发出: <cache> <operation>{,<Xt>} 下列操作有用:
接受地址参数的指令采用64位寄存器,该寄存器保存要维护的虚拟地址。此地址使用于对其限制。采用set/way/level参数的指令采用64为寄存器,其低32位遵循ARMv7体系结构中描述的格式。AArch64数据cache按地址无效化DC IVAC需要有写入权限,否则会产生权限错误。 所有cache维护指令可以以其他指令cache维护指令,数据cache维护指令,以及加载和存储的任何顺序执行,除非在指令之间执行DSB。 除了DC ZVA,指定地址的数据cache操作只有在相同地址的情况下,才保证以相对于彼此程序的顺序执行。指定地址的操作相对于所有为指定地址的维护操作,按程序顺序执行。 考虑如下代码时序: IC IVAU, X0 //通过地址进行指令cache的无效化到PoU DC CVAC, X0 //通过地址进行数据cache的清楚到PoC IC IVAU, X1 //若X0不等于X1,由于之前的操作导致无序 前两个指令以顺序执行,因为它们涉及到相同的地址。但是,最后指令相对之前的操作重新排序,因为它涉及一个不同的地址。 IC IVAU, X0 //通过地址进行I cache无效化到PoU IC IALLU //无效化I cache所有到PoU 这仅应用于发指令。完成只有DSB指令之后来保证。 在ARMv8-A中通过DC ZVA指令来提前用0来加载数据cache是新的。处理器可以运行明显快于外部内存系统,它有时会需要花长的时间来从内存加载cache line。 Cache line清零的行为方式和预取类似,它会提示处理器某些地址在将来很可能被用到。但是,清零操作可以更快因为它不需要等待外部内存访问完成。 取代从内存中获取数据到cache中,你可以将cache line填充0。它允许向处理器提示代码完全覆盖了cache line的内容,因此不需要初始化读取。 考虑情况:你需要一个大的临时存储buffer或正初始化新的结构体。你可以让代码简单的开始使用内存,或你可以在使用之前写代码预取。两者都会在读取初始内容到cache过程中使用很多cycle和内存带宽。通过使用cache清零选项,你可能节省这些浪费的带宽并让代码执行更快。 cache维护点定义依赖于是否可以通过VA或SET/WAY来执行指令。 你可以选择范围,或PoC或PoU,这些操作也可以被广播,看Multi-core processors,你可以选择shareability。 一些点需要注意:
如果软件要求指令执行和内存的一致性,它必须使用ISB和DSB内存屏障和cache维护指令来管理一致性。 6 cache的发现cache维护操作可以从过cache set或way或VA来发出。平台无关的代码需要知道cache的大小,cache line的大小,组和路的数目,有多少级的cache。此需求最可能出现在post-reset后cache无效化和清零操作中。所有其他对架构cache的操作都是基于PoC和PoU。 这里有一些系统控制寄存器包含这些信息:
要求异常级别访问两个分开的寄存器来决定cache中组和路的数目。
比如,如果仅L1和L2集成的,CLIDR/CLIDR_EL1定义了两级cache且处理器没有意识到任何外部的L3 cache。 当发出cache维护或代码来位置集成cache一致性时,有必要考虑非集成cache。 另外,big.LITTLE系统中,描述的cache层次结构每个core都不一样,比如,Cortex-A53和Cortex-A57处理器在CTR.L1IP域不一样。 |
|