ARMv7-A 处理器窥探 —— Cache
ARMv7-A Cache Architecture
cache以地址来进行查找 Cache 的,那么我们到底是用虚拟地址还是物理地址呢?三种方式:
1、早期的 ARM 处理器,如 ARM720T 或 ARM926EJ-S 处理器使用虚拟地址提供 Index 和 Tag。 这有一个优点,即 CPU 不需要虚拟到物理地址转换就可以进行缓存查找。 缺点是,每当进行进程切换(虚拟地址映射表发生改变),Cache 中的虚拟地址就不能再用了,导致性能下降,现在这种方式已经淘汰。
2、使用物理地址来查找 Cache(我们叫它 PIPT),那么这么做很明显解决了第一种的缺点(因为是以物理地址进行 Cache 的,不管映射表怎么变,物理地址不会变)。但是由引入了一个缺点:每次进行查表的时候,都需要到MMU去进行地址转换,这样增加了查找cache需要的时间,效率明显没有采用虚拟地址的高。注:这种方法,依赖MMU,即MMU关闭,Cache 就必须关闭;
3、是第 1 种和第 2 种的折中处理,将这个查找过程分为两步,Tag 用物理地址表示,Index 用虚拟地址的,我们叫它 VIPT(Virtually Indexed, Physically Tagged)。那么怎么实现呢?首先,由于 Cache 控制器和 MMU 是两个独立模块,因此通过 MMU 去查找 Tag 和通过 Index 去 Cache 查找 Way 是相互独立的即可以同时运行。即当用 Index 去 Cache 查找 Set(上面有解释,即 Index 相同,但处于不同 Way 的一组集合)的同时也在用虚拟地址去 MMU 找物理地址的 Tag,当从cache找到一组set(line[way])的时候(因为只提供了index,因此cache control不知道到底是哪个way,所以返回每个包含index的way),此时MMU中也查到了物理tag,然后再用该物理tag去匹配返回的set,最后获取到对应的 cache line。常用CPU情况如下。
可能到这里有人会问了,混合使用物理地址和虚拟地址不会有问题吗?毕竟虚拟地址在进程发生变动的时候是会不断变化的。理论上是不会有问题的,为什么呢?我们知道我们的虚拟映射表了,我们的映射表一般是以4K为一个page,即4k对齐,不管虚拟地址怎么发生变化,一个page内的偏移是不变的。要寻址一个4k大小我们需要[11:0]共12个bit来提供支持,即在MMU当中,虚拟地址的低12位和物理地址的低12位是相同的。假如我们用的是一个16kb大小,含有4个way,每个line 32bytes的cache,那么通过计算[4:0]用于cache的offset定位,[11:5]则用于cache的index定位。如上所说,虚拟地址和物理地址的[11:0]是相同的,因此index用虚拟地址就不会有影响。但是话又说回来,如果我们的cache大小超过了16k,假如为32kb呢?那么我们以32KB,含有4个way,每个line 32bytes的cache来说,[4:0]用于offset定位,[12:5]用于index定位,那么问题来了,由于虚拟地址和物理地址仅仅是[11:0]相同,那么第13位在发生切换后,就可能会出现0/1两个值,意味着一个物理地址可能会同时占用2个cache line,即两个副本, 这样就会容易引发cache一致性的问题。针对于这种cache alias问题,目前的方案是由操作系统来保证,对于同一物理地址在不同进程空间的虚拟地址,他们的虚拟地址的差一定是cache way大小的整数倍,也就是说他们的第13位一定是相同的。同时已经有些cpu厂商在开发监视模块,试图在硬件层面解决类似的同步问题。同理对于64kb的cache也采用同样的方法。
ARMv7-A Cache policies
在 Cache 操作策略中可以做出许多不同的选择。
没有读策略是因为,读都是先读 cache,失效再读主存。
Allocation Policy(分配策略)
分配策略是指什么情况需要为数据分配cache line。当处理器内核对cache发起读写访问时,如果该访问的请求数据不在cache中时,cache控制器必须决定是否要将数据linefill到cache中,并将相应的tag也linefill到cache中 。
读分配:CPU读数据时。只有当读取的时候,发现cache miss,才从cache中申请一个line去缓存该数据。写的时候,不申请,直接写入主存,例如对某一页内存进行初始化操作(全部写0),此时就没必要将对应数据写入cache中。
读写分配:写和读时,只要访问时,不管读或者写,发生了cache miss都去申请一个cache line。该分配策略通常与处理器内核write-back写策略配合使用,首先将主存中的数据加载到cache line中(变成命中),然后更新cache line的数据/主存的数据。
Replacement Policy(替换策略)
组相联cache中,所有的way都已经有填充数据了,miss时决定替换掉哪一个way的cacheline。
基于前面提到的分配策略,当cache未命中时,cache控制器必须为该数据分配一个cacheline。被选择的cacheline就是victim。如果victim包含有效的dirty数据,则必须先将该cacheline的内容写入主存,然后才能将新数据linefill到victim cacheline。整个替换的过程称为eviction。
替换策略控制着victim选择的过程,从备选的cache line中选择哪个cache line的策略。
Round-robin替换策略:会有一个victim counter,然后进行周期循环,选择相应的cache line,重点是实现公平性 。
Pseudo-random替换策略:随机选择一个cacheline进行替换。
Least Recently Used(LRU)替换策略:选择最近最少使用的被替换
Write Policy(写策略)
Write-Back 模式:写数据时,只向 Cache 写入数据,并标记 Cache 为 Dirty,不写入主存的或者只有当cache被替换时或清理cache行时才写入主存
Write-Through 模式:写数据时,Cache 和主存都要写一份;