转载自:https://www.eefocus.com/embedded/323218/p1
当第一代RISC微处理器刚出现时,标准存储器元件的速度比当时微处理器的速度快。很快,半导体工艺技术的进展被用来提高微处理器的速度。标准DRAM部件虽然也快了一些,但其发展的主要精力则放在提高存储容量上。
1980年,典型DRAM部件的容量为4KB。1981年和1982年开发出了16KB芯片。这些部件的随机访问速率为3MHz或4MHz,局部访问(页模式)时速率大约快1倍。当时的微处理器每秒需要访问存储器2M次。
到2000年,DRAM部件每片的容量到达256Mbit,随机访问速率在30MHz左右。微处理器每秒需要访问存储器几百兆次。如果处理器速率远高于存储器,那么只能借助Cache才能满足其全部性能。
Cache存储器是一个容量小但存取速度非常快的存储器,它保存最近用到的存储器数据拷贝。对于程序员来说,Cache是透明的。它自动决定保存哪些数据、覆盖哪些数据。现在Cache通常与处理器在同一芯片上实现。Cache能够发挥作用是因为程序具有局部性特性。所谓局部性就是指,在任何特定的时间,微处理器趋于对相同区域的数据(如堆栈)多次执行相同的指令(如循环)。
Cache经常与写缓存器(write buffer)一起使用。写缓存器是一个非常小的先进先出(FIFO)存储器,位于处理器核与主存之间。使用写缓存的目的是,将处理器核和Cache从较慢的主存写操作中解脱出来。当CPU向主存储器做写入操作时,它先将数据写入到写缓存区中,由于写缓存器的速度很高,这种写入操作的速度也将很高。写缓存区在CPU空闲时,以较低的速度将数据写入到主存储器中相应的位置。
通过引入Cache和写缓存区,存储系统的性能得到了很大的提高,但同时也带来了一些问题。比如,由于数据将存在于系统中的不同的物理位置,可能造成数据的不一致性;由于写缓存区的优化作用,可能有些写操作的执行顺序不是用户期望的顺序,从而造成操作错误。
1 Cache的分类
Cache有多种构造方法。在最高层次,微处理器可以采用下面两种组织中的一组。
1.1 统一Cache
也称混合cache,指令和数据用同一个Cache。结构如图1所示。
图1 统一的指令Cache和数据Cache
1.2 指令和数据分开的Cache
有时这种组织方式也被称为改进的哈佛结构。
图2 显示了这种组织方式。
这两种组织方式各有优缺点。统一Cache能够根据当前程序的需要自动调整指令在Cache存储器的比例,比固定划分的有更好的性能。另一方面,分开的Cache使Load/Store指令能够单周期执行。
2 Cache性能的衡量
只有当所需要的Cache存储器内容已经在Cache时,微处理器才能以高时钟速率工作。因此,系统的总体性能就可以用存储器访问中命中Cache的比例来衡量。当要访问的内容在Cache时称为命中(hit),而要访问的内容不在Cache时称为未命中(miss)。在给定时间间隔内,Cache命中的次数与总的存储器请求次数的比值被称为命中率。
图2 指令Cache和数据分开的Cache
命中率用下面的公式进行计算:
命中率=(Cache命中次数÷存储器请求次数)×100%
未命中率与命中率形式相似,即在给定时间间隔内,Cache未命中的总次数除以总的存储器请求次数所得的百分比。未命中率与命中率之和等于100。
目前设计良好的处理器,Cache的未命中率只有百分之几。未命中率依赖多个Cache参数,包括Cache大小和组织。
3 Cache工作原理
Cache的基本存储单元为Cache行(Cache line)。存储系统把Cache和主存储器都划分为相同大小的行。Cache与主存储器交换数据是以行为基本单位进行的。每一个Cache行都对应于主存中的一个存储块(memory block)。
Cache行的大小通常是2L字节。通常情况下是16字节(4个字)和32字节(8个字)。如果Cache行的大小为2L字节,那么对主存的访问通常是2L字节对齐的。所以对于一个虚拟地址来说,它的bit[31∶L]位,是Cache行的一个标识。当CPU发出的虚拟地址的bit[31∶L]和Cache中的某行bit[31∶L]相同,那么Cache中包含CPU要访问的数据,即成为一次Cache命中。
为了加快Cache访问的速度,又将多个Cache行划分成一个Cache组(Cache Set)。Cache组中包含的Cache行的个数通常也为2的N次方的倍数。为了方便起见,取N=S。这样,一个Cache组中就包含2S个Cache行。这时,虚拟地址中的bit[L+S-1∶L]为Cache组的标识。虚拟地址中余下的位bit[31∶L+S]成为一个Cache标(Cache-tag)。它标识了Cache行中的内容和主存间的对应关系。
图3显示了Cache的访问过程。
图3 Cache访问过程
4 Cache与主存的关系
在Cache中采用地址映射将主存中的内容映射到Cache地址空间。具体的说,就是把存放在主存中的程序按照某种规则装入到Cache中,并建立主存地址到Cache地址之间的对应关系。而地址变换是指当程序已经装入到Cache后,在实际运行过程中,把主存地址变换成Cache地址。
地址的映射和变换是密切相关的。采用什么样的地址映射方法,就必然有与之对应的地址变换。
常用的地址映射和变换方式包括直接映射和变换方式、组相联映射和变换方式以及全相联和变换方式。
4.1 直接(direct-mapped)映射方式
直接映射是一种最简单,也是最直接的映射方式。主存中的每个地址都对应Cache存储器中惟一的一行。由于主存的容量远远大于Cache存储器,所以在主存中很多地址被映射到同一个Cache行。
图4 显示了主存与Cache的直接映射关系。
图4 主存和Cache的直接映射
直接映射Cache是一种简单的解决方法,但这种设计使得每个主存块在Cache中只有一个特定的行可以存放。如果程序同时用到对应于Cache同一行的两个主存块,那么就会发生冲突,冲突的结果是导致Cache行的频繁变换。这种由直接映射导致的Cache存储器中的软件冲突称为颠簸(thrashing)问题。
4.2 组相联映射方式
为了减少颠簸问题,有些Cache使用了组相联的映射策略。在组相联的地址映射和变换中,把主存和Cache按同样大小划分成组(set),每个组都由相同的行数组成。
由于主存的容量比Cache容量大得多,因此,主存的组数要比Cache的组数多。从主存的组到Cache的组之间采用直接映射方式。主存中的一组与Cache中的一组之间建立了之间映射方式后,在两个对应的组内部采用全相联映射方式。
在ARM中采用的是组相联的地址映射和变换方式。如果Cache的行大小为2L,则同一行中各地址的bit[31∶L]是相同的。如果Cache中组的大小(每组中包含的行数)为2S,则虚地址位bit[L+S∶L]用于选择Cache中的某个组。
图5 显示了一个Cache与主存储器的组相联映射
图5 Cache与主存储器组相联映射
拥有相同组索引的Cache行称为组相联的(set associative)。主存中的程序或代码段可以在不影响程序执行的情况下被分配到Cache中的某一组中。也就是说,将数据或代码存入Cache行中的操作不会影响程序的执行。
4.3 全相联映射方式
随着Cache控制器的相联度的提高,冲突的可能性减少了。理想的目标是,尽量提高组相联程度,使主存地址能够映射到任意Cache行。这样的Cache被称为全相联Cache。然而,随着相联度的提高,与之相匹配的硬件的复杂度也在提高。硬件设计者提高Cache相联度的一种方法就是使用内容寻址寄存器CAM(Content Addressable Memory)。
CAM使用一组比较器,以比较输入的标签地址和存储在每一个有效Cache行中的标签位。CAM采取了与RAM相反的工作方式;RAM在得到一个地址后再给出数据;而CAM则是在检测到给定的数据值在存储器中后,再给出该数据的地址。使用CAM允许同时比较更多的地址中的标签位,从而增加了可以包含在一个组中的Cache行数。
在ARM920T和ARM940T存储器核中,ARM使用了CAM来定位地址中的标签域。ARM920T和ARM940T中的Cache是64组组相联的。图6 所示为ARM940T的Cache结构图。Cache控制器把地址标签域作为CAM的输入,它的输出选择了包含有效Cache行的组。
图6 ARM940T64路组相联Cache
访问地址的标签部分被作为4个CAM的输入,输入标签的同时与存储在64组中的所有Cache标签比较。如果有一个匹配,那么数据就由Cache寄存器提供;如果没有匹配,存储器就会产生一个失效(misss)信号。
控制器使用组索引位(set index)在4个CAM中选择一个。被选中的CAM会在Cache存储器中选择一个Cache行,该地址的数据索引部分(data index)在该Cache行中选择出所需的字、半字或者字节。
5 Cache的写策略
当CPU更新了Cache内容时,要将结果写回到主存中,通常有两种方法:
·直写法(write-through);
·回写法(write-back)。
直写法是指,当CPU在执行写操作时,必须把数据同时写入Cache和主存,以确保Cache和主存数据一致。在这种写策略下,处理器在每次写Cache时也要写相应的主存单元。由于要访问主存,直写法的速度比回写法要慢一些。
回写法是指,当处理器和写Cache命中时,只向Cache存储器写数据,而不立即写入主存。这样,主存储器与相应的Cache行数据有可能不一致。Cache中的数据是新的,而主存中的数据可能是较早的、没有被更新过的。
配置成回写法的Cache要使用Cache行的状态信息块中的一个或多个脏位(dirty bit)。当回写Cache控制器向Cache存储器中的某一行写入数据时,它会将脏位设置为1。如果控制器内核此后访问该Cache行,那么通过脏位的状态就可以知道该Cache行中含有主存储器中没有的数据。如果Cache控制器要将一个脏位被设置的Cache行替换出Cache存储器,那么该Cache行数据会自动被写入主存单元中。控制器通过这种方法来防止只存在于Cache中而主存中没有的重要信息的丢失。
表15.12比较了直写法和回写法的优缺点。
表15.12 直写法与回写法
写 策 略 | 直 写 法 | 回 写 法 |
可靠性 | 高 | 低 |
与主存的通信量 | 多 | 少 |
控制的复杂性 | 简单 | 复杂 |
硬件实现代价 | 大 | 小 |
下面分析产生这些性能差异的原因。
·可靠性。直写法要优于回写法。这是因为直写法始终保证Cache是主存的正确副本。当Cache发生错误时,可以从主存中纠正。
·与主存的通信量。一般情况下,回写法少于直写法。这是因为,一方面,Cache的命中率很高,对于回写法来说,CPU绝大多数操作只需要写Cache,不必写主存。另一方面,当Cache失效时,要将Cache中的行替换到主存,而直写法每次只写一个字到主存。总的来说,由于直写法在每次写Cache时,同时写主存,从而增加了写操作的开销。而回写法是把与主存的数据交换集中到一次主存操作,可能要一次性的进行多个字的操作。
·控制的复杂性。直写法必回写法简单。直写法在Cache的行状态表中不需要修改位。同时,直写法的纠错技术相对简单。
·硬件代价。回写法比直写法好。因为直写法中,每次写操作都要写主存,因此为了节省写主存所花费的时间,通常要采用一个高速小容量的缓存存储器,把要写的数据和地址写到这个缓存中。在每次读主存时,也要首先判断所读的数据是否在这个缓存中。而回写法不需要上述操作,相对硬件代价要小。
6 Cache的替换策略
在Cache访问过程中,发现查找的Cache行已经失效,则需要从主存中调入新的行到Cache中。在采用组相联的Cache中,一个来自主存的行可以放入多个Cache组中。当所有组中的对应行都已经装满时,就要使用Cache替换算法,从这些组中找出一个Cache,把它调回到主存中原来存放它的地方,腾出新行来存放新调入的行。被选中替换的Cache行被称为丢弃者(victim)。如果丢弃者中包含有效的脏数据,那么在该行被写入新数据之前,控制器必须把该行中的数据写到主存。选择和替换丢弃Cache行的过程被称为淘汰(eviction)。
Cache控制器选择下一个丢弃Cache行的策略被称为替换策略。在ARM常用的替换算法有两种:轮转算法和随机替换算法。
轮转算法又叫循环法,这种算法维护一个逻辑计数器,每进行一次替换,计算器加1,当计算器达到最大值时,就被复位成预先定义好的一个基值。这种算法容易预测最坏情况下的Cache性能。但它一个明显缺点就是,在程序发生很小变化时,可能造成Cache性能急剧下降。
随机算法从特定的位置上随机地选出一行替换出去。它通过一个随机发生器来完成上述操作。当每次需要替换Cache行时,随机发生器将产生一个随机数,用新行将编号为该随机数的行替换出去。这种算法与轮转算法最大的区别在于它在每次产生替换行时,增加的是一个非连续值,这个值是由控制器随机产生的。同样,当丢弃计算器达到最大值时,会被复位成预先定义好的一个基值。
相比之下,随机算法没有考虑到程序的局部性特点,因而效果有时不尽人意,同时这种算法不易预测最坏情况下Cache性能。而轮转法就有更好的可预测性,容易预测最坏情况下Cache性能,在一些实时系统中,十分重视这一点。但是,轮转法替换策略在存储器访问发生很小变化时,可能造成Cache性能有较大变化。
表1 显示了目前比较流行的ARM核所使用的策略。
表1 常见ARM核使用的替换策略
内 核 | 写 策 略 | 替 换 策 略 |
ARM720T | 直写法 | 随机 |
ARM740T | 直写法 | 随机 |
ARM920T | 直写法、回写法 | 随机、轮转 |
ARM940T | 直写法、回写法 | 随机 |
ARM926EJ-S | 直写法、回写法 | 随机、轮转 |
ARM946E | 直写法、回写法 | 随机、轮转 |
ARM1020E | 直写法、回写法 | 随机、轮转 |
ARM1026EJS | 直写法、回写法 | 随机、轮转 |
Intel Strong ARM | 回写法 | 轮转 |
Intel Xscale | 直写法 | 轮转 |
7 与Cache相关的编程接口
与Cache编程相关的CP15的寄存器共有3个,它们分别为c1、c7及c9。
7.1 寄存器c1中与Cache相关的位
c1寄存器在前面CP15寄存器一节中已经介绍过,下面对Cache的控制位进行详细介绍。
表2 显示了c1中与Cache有关位的作用。
表2 c1中与Cache相关的位
相 关 位 | 作 用 |
C(bit[2]) | 当数据Cache和指令Cache分开时,本控制位禁止/使能数据Cache 当数据Cache和指令Cache统一时,本控制位禁止/使能整个Cache 0:禁止Cache 1:使能Cache 如果系统中不含Cache,读取时该位返回0,写入时忽略该位 当系统中Cache不能禁止时,读取返回1,写入时忽略该位 |
相 关 位 | 作 用 |
I(bit[12]) | 当数据Cache和指令Cache是分开的,本控制位禁止/使能指令Cache 0:禁止指令Cache 1:使能指令Cache 如果系统中使用统一的指令Cache和数据Cache或者系统中不含Cache,读取该位时返回0,写入时忽略该位 当系统中的指令Cache不能禁止时,读取该位返回1,写入时忽略该位 |
RR(bit[14]) | 如果系统中Cache的淘汰算法可以选择的话,本控制位选择淘汰算法 0:选择常规的淘汰算法,如随机淘汰算法 1:选择预测性的淘汰算法,如轮转(round-robin)淘汰算法 如果系统中淘汰算法不可选择,写入该位时被忽略,读取该位时,根据其淘汰算法可以简单地预测最坏情况,并返回1或者0 |
7.2 寄存器c7
CP15中的寄存器c7主要用于控制Cache和写缓存。
注意 | c7有时也用于其他相似的功能,如果系统中存在预测缓存(prefetch buffers)和分支目标(branch target)Cache,c7也将负责对它们进行控制。 |
c7是一个只写存储器,可以使用协处理器指令MCR对其进行操作。如果程序中包含读c7的操作,那么指令的结果不可预知。
使用MCR指令写该寄存器的命令格式如下所示。
MCR P15,0,<Rd>,<c7>,<CRm>,<opcode2>
其中,CRm和opcode2的不同组合,决定指令执行的不同操作。具体组合与操作的对应关系见表3。
表3 CRm与opcode2不同组合与操作的应用关系
CRm | Opcode2 | 含 义 | 数 据 |
c0 | 4 | 等待中断 | 0(SBZ,should be zero) |
c5 | 0 | 使整个指令Cache无效 | 0 |
c5 | 1 | 使指令Cache中某行无效 | 虚拟地址 |
c5 | 2 | 使指令Cache中某行无效 | 组号/索引 |
c5 | 4 | 清空预取缓存区 | 0 |
c5 | 6 | 清空整个分支目标Cache | 0 |
c5 | 7 | 清空分支目标Cache中的某入口项 | 生产商定义 |
c6 | 0 | 使整个数据Cache无效 | 0 |
CRm | Opcode2 | 含 义 | 数 据 |
c6 | 1 | 使数据Cache中的某行无效 | 虚拟地址 |
c6 | 2 | 使数据Cache中的某行无效 | 组号/索引 |
c7 | 0 | 使整个统一Cache无效 哈佛结构中,使整个数据Cache和指令Cache无效 | 0 |
c7 | 1 | 使统一Cache中某行无效 | 虚拟地址 |
c7 | 2 | 使统一Cache中某行无效 | 组号/索引 |
c8 | 2 | 等待中断 | 0 |
c10 | 1 | 清理数据Cache行 | 虚拟地址 |
c10 | 2 | 清理数据Cache行 | 组号/索引 |
c10 | 4 | 清除写缓存区 | 0 |
c11 | 1 | 清理统一Cache行 | 虚拟地址 |
c11 | 2 | 清理统一Cache行 | 组号/索引 |
c13 | 1 | 预取指令Cache中的某行 | 虚拟地址 |
c14 | 1 | 清理并使数据Cache中的某行无效 | 虚拟地址 |
c14 | 2 | 清理并使数据Cache中的某行无效 | 组号/索引 |
c15 | 1 | 清理并使统一Cache中的某行无效 | 虚拟地址 |
c15 | 2 | 清理并使统一Cache中的某行无效 | 组号/索引 |
7.3 寄存器c9
将Cache进入存储系统的注意目的是要提高系统的平均访问速度。但Cache是一把双刃剑,在某些情况下,可能使系统的性能更遭。下面列出了3种使Cache性能明显下降的原因。
① Cache访问未命中,处理器转向主存寻址数据,这期间的延时对系统性能影响很大。
② 在回写型Cache中,如果Cache中的数据所在地址被存储管理单元重新定位(即Cache中存储的为虚地址数据),那么数据回写的操作延时很大。
③ 当处理器需要一个字节数据,而此数据恰好不在Cache中,那么Cache的替换策略就会将整个Cache行换进,增加了系统不必要的开销。
以上3点对实时系统来说,影响更为明显。
为了减少这种不利的影响,在ARM系统中引入了Cache内容锁定技术。这种技术允许编程人员人为地将一些关键代码或数据预取到Cache中后,通过寄存器操作对其设定一定的属性,这样当有Cache未命中发生,需要进行Cache替换时,将这些数据保护起来,使这些关键代码或数据不会被换出。
这种策略在很大程度上保证了处理器对关键代码或数据访问时的性能。
Cache的锁定操作是分组块(block)为单位进行的,它的分块方法如下。
为了叙述方便,作下述假设。
L(Length of the Line):Cache的基本存储单元行的大小。
A(Associativity):表示每个Cache组中的行数。
N(Number of Sets):Cache中的组数。
M表示Cache中的锁定块。
每个锁定块(lockdown block)包括Cache每组中的一行。这样Cache中共有A个锁定块,其编号为从0到A-1。其中编号为0的锁定块中包含Cache组0中的0#行,组1中的0#行,直到组A-1中的0#行。依此类推,锁定块1包含Cache组0中的1#行,组1中的1#行,直到组A-1中的1#行。这样每个锁定块中包含了N个Cache行。
当编号为0~M的锁定块被锁定在Cache中,编号为M+1~A的锁定块可以用于正常的Cache替换操作。
注意 | 编程中不能将全部Cache锁定,至少要留出一个未锁定的块来支持存储器的正常操作。 |
每一个锁定块都包含有N个不同组中的Cache行。建议程序在使用Cache锁定时,使每个Cache块中的N个Cache行映射的为存储器中的连续地址。也就是说,存储器中N×L大小的联系区域被映射到Cache中锁定,这块区域是Cache行边界对齐的(如果一个Cache行包含4字节,那么被锁定的区域要是4字节对齐的,如果一个Cache行包含8字节,那么被锁定的Cache行就是8字节对齐的)。
在ARM的存储管理体系中,主要依靠系统协处理器和协处理器的寄存器c9来实现和管理Cache锁定。如果系统中使用的是数据和指令分离的Cache,那么就依靠协处理器指令MCR和MRC中的<opcode>2来区分:
·<opcode>=0使用数据Cache锁定寄存器;
·<opcode>=1使用指令Cache锁定寄存器。
如果系统使用的是数据和指令统一的Cache,那么<opcode2>要设置成0。
另外,无论是MCR指令还是MCR指令,指令中的<CRm>通常设为c0。
寄存器c9有两种主要的格式:格式A和格式B。
格式A的编码如图7所示。
图7 格式A编码
程序员通过指令对寄存器中的Cache组内行号进行操作。读取格式A的寄存器c9,将返回最后一次写入寄存器c9的值。将数据index写入寄存器c9,就是对要锁定的Cache行进行设置。当用MCR指令向寄存器写入数据时,执行以下操作。
① 当下一次发生Cache未命中时,将预取的存储器行存入Cache中与该行相对应的组中编号为index的Cache行中。
② 这时被锁定的Cache块包括序号为0~index-1的锁定块。当发生Cache替换时,从编号为index到A-1的块中选择被替换的块。
格式B的编码如图8所示。
程序员通过指令对寄存器中的Cache组内行号进行操作。读取格式B的寄存器c9,将返回最后一次写入寄存器c9的值。将数据index写入寄存器c9,就是对要锁定的Cache行进行设置。当用MCR指令向寄存器写入数据时,执行以下操作。
图8 格式B编码
① 当L=0时,如果方式Cache未命中,将预取的存储行存入Cache中与该行对应的组中序号为index的Cache行中。
② 当L=1时,如果本次写操作之前L=0,并且index值小于本次写入的index,本次写操作执行的结果不可预知;否则,这时被锁定的Cache块包括序号为0~index-1的块。当发生Cache替换时,从序号为index~A-1的块中选择被替换的块。
下面以锁定块N来说明要锁定一个Cache块的步骤。
① 首先确保在下面的整个Cache锁定过程不会被中断打断。如果程序要求中断不能关闭,那么必须确保被打开的中断相关代码和数据位于非缓存(uncachable)的存储区域。
关中断的典型做法如下所示。
MRS r2,CPSR ;读出当前程序状态字 CPSR
ORR r2,r2,# 0x000000C0 ;关中断
MSR CPSR_cxsf,r2 ;设置当前程序状态字
② 如果锁定是指令Cache或者统一Cache,必须保证锁定过程所执行的代码位于非缓存的存储域。
③ 如果锁定的是数据Cache或者统一的Cache,必须保证锁定过程所执行的数据位于非缓存的存储域。
④ 保证要锁定的代码和数据位于缓存的存储区域中。
⑤ 如果要锁定的代码和数据不在Cache中,使用Cache清除或清理指令,将其置换到Cache中。
⑥ N次循环执行下面的操作。
·index=I写入寄存器c9,当使用B格式的锁定寄存器时,令L=0。
·如果锁定的是数据Cache或数据和指令统一Cache,使用LDR指令将数据从内存读出,这个读操作将使要锁定的内容存在于Cache行中。
·如果锁定的是指令Cache,那么要借助c7寄存器,相关指令详细内容,参见c7寄存器一节。
⑦ 将index=N写入寄存器c9,当使用B格式的锁定寄存器时,令L=0。
如果要解除对N锁定块的锁定,执行以下操作。
·将index=0写入寄存器c9。
·当使用格式B的锁定寄存器时,令L=0。
8 内存一致性
当一个系统中同时使用了Cache、写缓存时,同一地址的数据可能同时出现在包括系统内存在内的多个不同的物理位置中。如果Cache引入了哈佛架构,使用数据和指令分类的Cache,那情况将更复杂。
由于上述存储系统的多样性特点,当从内存中读取数据时,不能保证读取的是数据的最新值(即有可能出现下述情况:写操作将数据写入到Cache中,但更新数据还没有被回写到内存)。
ARM存储系统中,数据不一致问题一方面可以通过存储系统自动保存解决,另一方面编写程序时要遵循一定的规则,防止数据不一致性发生。
下面就几个常出现数据不一致的地方进行讨论。
·地址映射发生变化时
·指令和数据分离的Cache
·系统执行DMA(Direct Memory Access)操作
8.1 地址映射发生的变换
当系统中使用MMU时,Cache行对应的地址可能是:
① 内存中的实际地址;
② 经过地址转换后的虚拟地址。
如果查询Cache时相联地址比较使用的是虚拟地址,则当系统地址到物理地址的映射发生变换时,可能造成Cache中数据与主存中的不一致。
同时,当系统中使用了写缓存,处理器对写缓存中的数据处理也是按虚拟地址进行的,所以同样会发生数据不统一的问题。比如,当前处理器使用虚拟地址向某个内存单元写数据,该写操作已经将虚拟地址和数据写入到写缓存区中,此时,虚拟地址到物理地址的映射关系发生变换,使先前要写入数据的虚拟地址发生了变化,当写缓存将上面被延时的写操作写到主存时,使用的是变换后的地址,从而写操作执行失败。
为了避免发生这种数据不统一的情况,在系统虚拟地址到物理地址的映射关系发生变换前,根据系统的具体情况,执行下面的操作序列中的一种或几种。
·如果数据Cache为写回型Cache,清空该数据Cache。
·使数据Cache中相应的行无效。
·使指令Cache中相应的行无效。
·将写缓存区中被延时的操作全部执行。
·有些情况可能还要求相关的存储区域被置换成非缓存的。
8.2 指令/数据Cache分离
当系统中采用分离的数据Cache和指令Cache时,下面的几种情况可能造成指令不一致情况的发生。
① 地址为A1的指令被预取,该指令的数据行被取到Cache中。
② 和A1同在一个数据行的地址为A2的数据被一条存储器写操作修改。这个数据写操作可能影响数据Cache中、写缓存中和主存的地址为A2的存储单元内容,但不影响指令Cache中地址为A2的存储单元中的内容。
③ 如果地址A2存放的是指令,当该指令执行时,就可能发生指令不一致问题。如果地址A2所在的行还在指令Cache中,系统将执行修改前的指令;如果地址A2所在的行不在指令Cache中,地址将执行修改后的指令。
为了避免这种指令不一致的情况发生,要在地址A2的数据被修改前执行一些防护性的操作。也就是说,在步骤①和②之间插入下面必要的操作。
·如果系统中使用的数据、指令统一的Cache,程序跳到步骤②继续执行。
·对于使用数据和指令分离Cache的系统,使指令Cache的内容无效。
·对于使用数据和指令分离Cache的系统,如果数据Cache是写回类型的,清空数据Cache。
上述操作系列可作为一种标准,应用于一些典型的场合。
注意 | 当可执行文件加载到主存中后,在程序跳转到入口点处开始执行之前,先执行上述操作序列,以保证新加载的可执行代码正确执行。 |
8.3 DMA造成的数据不一致
DMA操作直接访问内存,不更新Cache和写缓存区中相应内容,这样就很可能造成数据不一致。
为了避免DMA造成的数据不统一,根据系统情况,执行下面操作的一种和几种。
·将DMA访问的存储器设置成非缓存的
·将DMA访问的存储区所涉及的数据Cache中的行设置成无效,或者清空数据Cache。
·清空写缓存区(将写缓存区中延时操作全部执行)。
·在DMA访问期间限制存储器访问DMA所访问的存储区域。
9 Cache初始化子程序示例
下面给出了一段例子代码,此代码以ARM740T芯片为参考,显示了Cache初始化的标准过程。
;下面代码必须运行于处理器的特权模式下。
AREA INIT740, CODE, READONLY ;设置段属性
ENTRY
EXPORT Cache_Init ;以便作为子程序被其他程序使用
Cache_Init
;禁止MMU/MPU
;清理数据Cache
;
;
MRC p15, 0, r0, c1, c0, 0 ;读CP15寄存器c1到r0
BIC r0, r0, #0x1 ;清除bit[0]
MCR p15, 0, r0, c1, c0, 0 ;将设置的新值写回
MOV r0,#0 ;准备禁止其他域
MCR p15, 0, r0, c6, c1, 0
MCR p15, 0, r0, c6, c2, 0
MCR p15, 0, r0, c6, c3, 0
MCR p15, 0, r0, c6, c4, 0
; MCR p15, 0, r0, c6, c5, 0
; MCR p15, 0, r0, c6, c6, 0
; MCR p15, 0, r0, c6, c7, 0
;
; 区域0:背景区:从0x0地址开始的4GB存储空间
; 区域1:SRAM区:从0x0地址开始的0x4000字节存储空间
; 区域2:FLASH:从0x24000000开始的0x02000000字节存储空间
; 区域3:外设区:从0x10000000地址开始的0x10000000字节存储空间
;开启 region 0
MOV r0,#2_111111
MCR p15, 0, r0, c6, c0, 0 ; region 0区域0
; 开启region 1
MOV r0,#2_100011
MCR p15, 0, r0, c6, c1, 0 ; region 1区域1
; 开启region 2
LDR r0,=2_110001+0x24000000
MCR p15, 0, r0, c6, c2, 0 ; region 2区域2
; 开启region 3
LDR r0,=2_110111 + 0x10000000
MCR p15, 0, r0, c6, c3, 0 ; region 3区域3
; 开启Cache/写缓存
MOV r0, #2_0110
MCR p15, 0, r0, c2, c0, 0 ;Cache
MCR p15, 0, r0, c3, c0, 0 ;写缓存
; 开启access 允许
MOV r0, #2_11111100
MCR p15, 0, r0, c5, c0, 0 ;允许访问
;
; 设置全局配置
;
MRC p15, 0, r0, c1, c0, 0 ;读CP15寄存器到r0
ORR r0, r0, #(0x1 <<2) ;开启Cache
ORR r0, r0, #0x1 ;开启MPU
;
;增加的配置选项
;
; ORR r0, r0, #(0x1 <<13) ;开启Hi Vectors
; ORR r0, r0, #(0x1 <<7) ;开启大端模式
MCR p15, 0, r0, c1, c0, 0 ;写CP15寄存器c1
MOV pc,lr ;返回
END
上述程序端可作为参考,可以在程序中直接使用,也可以作为子程序被其他程序调用。如果作为子程序调用,下面两种调用方式作为参考。
① 在汇编程序中调用。
IMPORT Cache_Init
BL Cache_Init
② 在C语言中调用。
extern void Cache_Init(void);
Cache_Init();
10 个人小结
对于cache,编程人员主要注意一下几个问题
(1)cache映射关系
直接映射、组相连、全相连
(2)cache写策略
直写:同时更新cache和主存
回写:先写到缓冲器
(3)cache替换策略
丢弃cache行的策略:轮询,随机