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可以自我修改代码,以保证将来从修改后的代码中正确获取指令。他们从过两个阶段来做到这点:
- 清楚地址相关的数据cache项;
- 无效化地址的指令cache项;
ARM架构不要求硬件来保证指令cache和内存之间的一致性,即使是共享内存的位置。
5 cache维护
有时软件进行清除或无效化cache是必要的。当外部内存的内容被修改时,有必要移除cache中无效的数据。在MMU相关活动中(如更改访问权限,cache策略或虚拟地址到物理地址的映射)之后,或者当必须为动态生成的代码(如JIT编译器和动态库加载程序)同步I和D cache时,也可能需要它。
- cache或cache line的无效化意味着通过清cache line中的valid位来清楚cache line中的内容。在复位之后cache必须为无效的因为它的内容没有定义。这也可以看着是使cache之外的内存域的更改对cache用户可见的一种方式。
- 清楚cache line意味着写cache line的内容将其标为脏,写入到下一级cache,或到主存,并在cache line中清脏页。这使得cache line的内容与下一级的cache或内存系统中一致。这仅应用于在回写策略使用时的数据cache。这也是一种在cache中修改,让outer内存domain的用户可见,但这仅对数据cache有用。
- 清零。这会将cache中的内存块清零,而不需要首先从outer domain读取它们的内容。这也仅对数据cache有用。
对这些操作,你可以选择操作应该应用于哪些项:
- All,意味着所有的cache和对数据cache或统一cache没有用;
- MVA(修改的虚拟地址),VA的另外一个名字,包含某个虚拟地址的cache line;
- 在cache结构中通过它们的位置来选择的特定的组或路的cache line;
AArch64 cache维护操作通过如下格式发出:
<cache> <operation>{,<Xt>}
下列操作有用:
Cache | Operation | Description | AArch32 |
DC | CISW | 通过路/组清除和无效化 | DCCISW |
CIVAC | 通过虚拟地址来清除和无效化到PoC | DCCIMVAC | |
CSW | 通过路/组清除 | DCCSW | |
CVAC | 通过虚拟地址清除到PoC | DCCMVAC | |
CVAU | 通过虚拟地址清除到PoU | DCCMVAU | |
ISW | 通过路/组无效化 | DCISW | |
IVAC | 通过虚拟地址无效化到PoC | DCIMVAC | |
DC | ZVA | 通过虚拟地址对cache清零 | - |
IC | IALLUIS | 无效化所有到PoU,inner shareable | ICIALLUIS |
IALLU | 无效化所有到PoU | ICIALLU | |
IVAU | 通过虚拟地址无效化到PoU | ICIMVAU |
接受地址参数的指令采用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。
一些点需要注意:
- 在正常情况下,清除或无效化cache项仅firmware来做的,作为core的上下电的一部分。它也花费明显的时间,在L2 cache中cache line数量可能很大,有必要一个一个循环它们。因此这种清除行为明确定义为某些特殊场景。
- cache维护操作如DC CSW在前面节已经做描述。
- cache在时序的开始必须禁用,避免中间时序的新的cache line的分配。若cache为互斥的,cache line可能在多个level之间迁移。
- 在SMP系统中,另外的core可能将脏的cache line从看那个cache 中间时序去除,避免达到PoC。Cortex-A53和Cortex-A57处理器都会做这个操作。
- 如果在EL3,cache必须从安全world进行无效化且不能从正常world无效。如果保持不变,secure dirty数据会因为安全或正常world中的cache被回收时破坏内存系统。
如果软件要求指令执行和内存的一致性,它必须使用ISB和DSB内存屏障和cache维护指令来管理一致性。
6 cache的发现
cache维护操作可以从过cache set或way或VA来发出。平台无关的代码需要知道cache的大小,cache line的大小,组和路的数目,有多少级的cache。此需求最可能出现在post-reset后cache无效化和清零操作中。所有其他对架构cache的操作都是基于PoC和PoU。
这里有一些系统控制寄存器包含这些信息:
- 存在的cache level数目由软件读取CLIDR_EL1决定;
- Cache line大小由CTR_EL0决定;
- 若需要从用户代码访问,执行在EL0,可通过设置SCTLR/SCTLR_EL1的UCT位决定;
要求异常级别访问两个分开的寄存器来决定cache中组和路的数目。
- 代码必须首先写CSSELR_EL1来选择你将信息缓存到哪个cache;
- 代码都CCSIDR/CCSIDR_EL1;
- DCZID_EL0包含清零操作需要清零的块大小;
- SCTLR/SCTLR_EL1的DZE位和HCR/HCR_EL2中的TDZ位控制哪个异常级别和那个world可以访问DCZID_EL0. CLIDR_EL1, CSSELR_EL1和CCSIDR_EL1只通过权限代码访问,即AArch32中的PL1或更高,AArch64的EL1或更高;
- 若DC ZVA指令允许在异常级别执行,由SCTRL_EL1.DZE位控制,HCR_EL2.TDZ位的非安全指令,读取寄存器,返回表明是否指令支持的值;
- CLIDR寄存器仅意识到多少级的cache被集成到处理器中。它不能提供外部内存系统的任何cache的信息。
比如,如果仅L1和L2集成的,CLIDR/CLIDR_EL1定义了两级cache且处理器没有意识到任何外部的L3 cache。
当发出cache维护或代码来位置集成cache一致性时,有必要考虑非集成cache。
另外,big.LITTLE系统中,描述的cache层次结构每个core都不一样,比如,Cortex-A53和Cortex-A57处理器在CTR.L1IP域不一样。