购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

6.1 L1高速缓存

在Zynq-7000 SoC器件的APU中,每个Cortex-A9处理器都有独立的32KB L1指令高速缓存和32KB L1数据高速缓存。

我们经常说,高速缓存对于程序员是“透明的”,或者是“隐藏的”,但是知道高速缓存操作的一些细节问题对于程序员也是十分必要的。

6.1.1 高速缓存的背景

在最初开发Arm CPU架构时,处理器的时钟速度和访问存储器的速度大致相同。但是,CPU变得越来越复杂,其时钟速度提高了好几个数量级。然而,存储器外部总线的频率却不能提高到相同的数量级。也就是说,CPU的运行速度比存储器外部总线的频率高很多。因此,人们就尝试实现一小块片上SRAM,使访问它的速度和CPU的运行速度一样,即CPU可以用与自己一样快的速度访问这一小块片上SRAM,但是这种SRAM的成本要比标准的DRAM高很多。因为在相同的价格上,标准DRAM的容量为SRAM的几千倍。在基于Arm处理器的系统上,对外部存储器的访问将使用几十个甚至上百个CPU周期才能完成。

高速缓存是一小块快速存储器,它位于CPU与主存储器之间。实际上,它是对主存储器一部分内容的复制,即将主存储器的一部分内容按一定规则复制到这一小块快速存储器中。CPU对高速缓存的访问比对主存储器的访问快得多。

由于高速缓存只保存主存储器的一部分内容,因此必须同时保存相对应的主存储器的地址。当CPU想读/写一个特定的地址时,CPU应该在高速缓存内进行快速查找。如果在高速缓存内找到了相应的地址,则使用高速缓存内的数据,而不需要访问主存储器。减少访问外部主存储器的次数潜在地提升了系统的性能,并且由于这样避免了对外部信号的驱动,因此也显著地降低了系统功耗。

与系统中整体的存储器相比,高速缓存的容量是比较小的。当增大高速缓存的容量时,将使得芯片成本变高。此外,当增大内核的缓存容量时,将潜在减慢CPU的运行速度。因此,对这个有限资源的高效利用将是程序员编写能高效运行在CPU上的代码的关键因素之一。

前面提到,片上SRAM可以用于实现高速缓存,它保存着主存储器的一部分内容。而一个程序中的代码和数据具有暂时性和空间局部性特点。这就意味着,程序可能在一个时间段内重复使用相同的地址(时间局部性)及互相靠近的地址(空间局部性)。例如,代码可能包括循环,表示重复执行相同的代码,或者多次调用一个函数。数据访问(如堆栈)能限制到存储器内很小的一个区域。基于这个事实,即访问的局部性,而不是真正的随机,因此可以使用高速缓存策略。

写缓冲区用于执行保存指令时对CPU的写操作进行解耦,这个写操作是通过外部存储器总线对外部存储器的访问实现的。CPU将地址、控制和数据值放到一套硬件缓冲区内。就像高速缓存那样,它位于CPU与主存储器之间。这就使得CPU可以移动并执行下一条指令,而不需要由于对慢速的外部主存储器的直接写操作,使得CPU停下来等待这个过程完成,因为这个过程往往需要消耗很多CPU周期。

6.1.2 高速缓存的优势和问题

正如上面提到的那样,程序的执行并不是随机的,因此缓存加速了程序的运行。程序趋向于重复访问相同的数据集,以及反复执行相同的指令集。在首次访问时,将代码或数据移动到更快的存储器中,随后对这些代码或数据的访问将变得更快。刚开始,将数据提供给缓存的访问并不比正常情况更快。但是,随后对缓存数据的访问将变得很快,因此显著改善了系统的整体性能。尽管已经将存储器的一部分(包含外设)标记为非缓存,但是CPU的硬件将检查缓存内所有的取指和数据读/写。由于高速缓存只保存了主存储器的子集,因此需要一种方法来快速确定所需的地址是否在高速缓存中。

从上面可以很明显地看出,由于对执行程序进行了加速,因此高速缓存和写缓冲区自然是一个优势。然而,这也带来了一些问题。例如,当指令/数据没有出现在缓存中时,如何处理?程序的执行时间可能变得不确定。

这就意味着,由于高速缓存的容量很小,因此它只保存了很少一部分的主存储器的内容,当执行程序时,必须快速地填充它。当高速缓存满时,必须将其中的一些条目(指令/数据)移出,以便为新的条目腾出空间。因此,在任意给定的时间,对一个应用程序来说,并不能确定特定的指令/数据是否在高速缓存中。也就是说,在执行代码的某个特殊部分时,执行时间的差异很大。因此,对要求有确定时间的硬实时系统来说,这就会带来一个问题,即响应时间不确定。

而且,要求有一种方法用于控制高速缓存和写缓冲区对存储器不同部分的访问。在一些情况下,用户可能想让CPU从外设中读取最新的数据,这样就不趋向于使用被缓存的数据,如定时器外设。有时,用户想让CPU停止运行并等待完成保存过程。这样,就需要对高速缓存和写缓冲区做一些额外的工作。

高速缓存的内容偶尔和存储器的内容并不相同,这可能是由于处理器在更新高速缓存的内容时,没有将更新后的内容写回主存储器中;或者当CPU更新高速缓存的内容时,由一个代理更新相应主存储器的内容。这就是一致性问题。当有多个CPU或存储器代理(如外部DMA控制器)时,这就成为一个特殊的问题。

6.1.3 存储器的层次

在计算机科学中,存储器的层次是指存储器类型的层次,容量较小且速度较快的存储器靠近CPU,而容量较大且速度较慢的存储器离CPU较远。在绝大多数系统中,有第二级存储,包括磁盘驱动和基本存储,如Flash、SRAM和DRAM。在嵌入式系统中,可以分为片上和片外存储器。与CPU在同一个芯片(至少在相同封装)内的存储器的速度更快。

在一个层次中,可以在任何一级包含高速缓存,用于改善系统的性能。在基于Arm处理器的系统中,第一级(L1)高速缓存直接连接CPU,用于取指及处理加载和保存指令。对哈佛缓存结构来说,它提供了用于指令的独立高速缓存和用于数据的独立高速缓存,如图6.1所示。

图6.1 典型的哈佛缓存结构

这些年来,由于SRAM的容量和速度的增大(加快),L1高速缓存的容量也在不断增大。在进行写操作时,16KB和32KB容量的高速缓存是非常普遍的,因为这是最大的RAM容量,SRAM能提供单周期访问,访问速度可以和CPU的运行速度一样,甚至更快。

很多Arm系统额外提供了第二级高速缓存,它的容量比L1高速缓存要大,如256KB、512KB或1MB;但是其速度较慢,并且功能是统一的,即用于保存指令和数据。它可以在CPU中,也可以使用外部的块实现。它位于CPU和存储器系统剩余部分之间。Arm L2C-310是外部L2高速缓存控制器块的例子。

此外,在簇内可以实现CPU,每个CPU都有自己的L1高速缓存。这个系统要求具备一种机制,用于维护高速缓存之间的一致性。这样,当一个CPU改变了一个存储器位置中的值时,这种变化对于共享该存储器的其他CPU是可见的。

6.1.4 高速缓存的结构

在冯·诺依曼体系结构中,一个统一的高速缓存用于指令和数据。在一个修改的哈佛缓存结构中,采用了分离的指令和数据总线,因此有两种高速缓存,即指令高速缓存和数据高速缓存。在一个Arm系统中,有独立的指令和数据L1高速缓存,以及统一的L2高速缓存。

高速缓存要求保存一个地址、一些数据和状态信息。32位地址的高位用于告诉高速缓存信息在主存储器中的位置,称为标记(Tag)。总的缓存容量使用可以保存数据的总量进行衡量,用于保存标记值的RAM没有被计算在内。实际上,标记要占用缓存内的物理空间。

在每个标记地址中保存一个数据字是不充分的,因此在相同的标记下,要对一些位置进行分组。我们通常把这个逻辑块(分组后位置的集合)称为缓存行(Line)。地址中间的比特位,或者称为索引(Index)用于识别缓存行。索引被用作缓存RAM的地址,不要求将其保存为标记的一部分。当一个缓存行保存了缓存的数据或指令时,称之为有效的;否则,称之为无效的。

这就意味着地址的最低几位(偏置)并不需要保存在标记中,即用户要求的是整个缓存行的地址,而不是一行中每个字节的地址。这样,地址的低5位或6位总是0。

与每行相关的数据有一个或多个状态位。例如,使用有效位,用于标记缓存行中包含的数据。在一个数据高速缓存中,可能使用一个或多个脏比特位,标记它是否是一个缓存行或是它的一部分,保存了与主存储器相同位置的不同数据。

1.缓存相关术语

为了帮助读者理解上面的缓存术语,下面用图6.2进行说明。

图6.2 高速缓存术语

(1)Line是缓存行:用于指向一个缓存最小可加载的单位,它是来自主存储器连续的一块字。

(2)Index是索引:存储器地址的一部分,它决定在高速缓存的哪一行可以找到缓存地址。

(3)Way是路:一个缓存的细分。每一路的容量相同,并且以相同的方式进行索引。将来自每一路的带有一个特定索引值的多行关联在一起,组成一组,称为Set。

(4)Tag是标记:缓存内存储器地址的一部分,用于识别与缓存行数据对应的主存储器地址。

2.缓存映射方式

下面对高速缓存的两种不同映射方式进行详细说明,以帮助读者进一步理解高速缓存的工作原理。

1)直接映射

在高速缓存和主存储器之间,最简单的缓存映射方式就是直接映射。在直接映射的高速缓存中,主存储器的每个位置映射到高速缓存中的一个位置。然而,由于主存储器的容量要比高速缓存的容量大很多,因此主存储器的很多地址将映射到高速缓存中相同的位置。如图6.3所示,一个小的高速缓存,每个缓存行有4个字,一共有4个缓存行。

图6.3 直接映射高速缓存操作

这就意味着,缓存控制器使用地址的[3:2]比特位,作为一个缓存行内用于选择某个字的偏置;地址的[5:4]比特位作为索引,用于在4个可用的缓存行内选择一个。地址的[31:6]比特位用作标记,如图6.4所示。

图6.4 高速缓存地址

当查找高速缓存内的一个特定地址时,硬件从地址中提取出索引位,并在高速缓存中读取与该缓存行相关的标记值。如果两者相同,并且有效位表示缓存行包含有效数据,则表示命中。通过使用偏置和地址的字节部分,就能从缓存行相关的字中提取出数据值。如果缓存行包含有效数据,但是没有命中,则标记显示缓存行保存主存储器中的不同地址,之后就将该缓存行从高速缓存中移出,并用所要求地址的数据进行替换。

很明显,与[5:4]比特位有相同值的所有主存储器地址将映射到高速缓存中相同的缓存行。在任何一个时刻,这些缓存行中只有一行能在高速缓存中。这就意味着,在早期就会发生频繁替换的情况。考虑在地址0x00、0x40和0x80处反复执行一个循环的情况,如代码清单6.1所示。

代码清单6.1 for循环C语言代码

在该段代码中,如果result、data1和data2分别在0x00、0x40和0x80中,则反复执行该循环会引起反复访问主存储器位置的问题,这是因为它们都映射到高速缓存中相同的缓存行。

(1)当读取地址0x40时,它不在高速缓存中,这时会发生行填充,把0x40~0x4F地址的数据填充到高速缓存中。

(2)当读取地址0x80时,它不在高速缓存中,这时会发生行填充,把0x80~0x8F地址的数据填充到高速缓存中。在这个过程中,将地址为0x40~0x4F的数据从高速缓存中移出。

(3)将结果写到0x00中。根据分配策略,这将引起另一次行填充。在这个过程中,将地址为0x80~0x8F的数据从高速缓存中移出。

(4)在循环的每次迭代中,将发生相同的事情,此时,软件的执行情况很不好。因此,一般在Arm核的主缓存内部不使用直接映射方式,但是可以在一些地方看到这种方式,如Arm 1136处理器的分支目标地址缓存。

CPU使用硬件优化,用于写整个缓存行。在一些系统中,这可能占用很多的时钟周期,如执行类似memcpy()或memset()函数,执行块复制,或者执行大块的零初始化。在这些情况下,在初次读数据值时并没有优势。这就导致高速缓存的性能特性和通常所期望的有很大的不同。

缓存分配策略对CPU来说是不可见的,其不能保证将一片存储器读进高速缓存,结果是用户不能依赖缓存分配策略。

2)组关联映射

Arm核的主缓存使用组关联结构,这显著地减少了高速缓存的内容频繁替换情况的发生,同时加快了程序的执行速度,并且程序的运行时间更加确定。这是以增加硬件复杂度为代价的,会略微地提高功耗,这是由于在一个周期内需要同时比较多个标记。

使用这种类型的缓存结构,高速缓存被分成大小相同的许多片,称为路。一个主存储器的位置映射到路,而不是缓存行。地址索引仍然用于选择特殊的缓存行,但是现在指向所有路的每一行。一般使用2路或4路,一些Arm的实现会使用更多路。

由于L2高速缓存的容量更大,因此实现它(如ArmL2 C-310)时使用了更多路,因此有更高的关联性。有相同索引值的缓存行属于一组。当检查命中时,必须检查组内的每个标记。一个2路缓存的结构如图6.5所示,来自地址0x00、0x40或0x80的数据可以在其中一路中的第0行找到。

图6.5 2路缓存的结构

当提升相关性时,就减少了高速缓存的内容频繁替换情况的发生。理想的情况是全关联,即主存储器的任何一个位置都能映射到高速缓存内的任何地方。然而,构建这样一个高速缓存是不实际的。在实际中,当超过4路关联时,对高速缓存的性能的改善是比较小的,而8路或16路关联对更大的第2级高速缓存来说是有用的。

下面以Cortex-A9处理器的L1高速缓存组关联的结构(见图6.6)为例进行相关说明。

图6.6 Cortex-A9处理器的L1高速缓存组关联的结构

在该缓存结构中,每个缓存行的长度为8个字(32字节)。32KB容量的高速缓存被分为4路,每路有256个缓存行。这就意味着需要使用地址的[12:5]比特位在每路中索引一个缓存行。同时,尽管在缓存行中所要求的索引取决于是访问一个字、半字还是字节,但是仍使用地址的[4:2]比特位从一个缓存行中的8个字中选择一个。在这种结构中,地址的[31:13]比特位用作标记。

3.缓存控制器

用于控制缓存的缓存控制器是一个硬件块,负责管理高速缓存存储器,它对程序是不可见的。它自动地将代码或数据从主存储器写到高速缓存中。它接收来自CPU的读/写存储器请求,并对高速缓存或外部存储器执行必要的操作。

当它收到来自CPU的请求时,首先检查在高速缓存中是否有所请求的地址,这称为缓存查找。这是通过将带有标记的请求地址与缓存行进行比较来实现的。当命中时,就将该缓存行标记为有效状态,并通过缓存存储器执行读/写操作。

当CPU请求特殊地址的指令或数据,但没有命中时,将导致缓存缺失,此时,请求将传给第二级高速缓存或外部存储器。这也将引起缓冲行的填充操作。高速缓存的行填充将主存储器新的内容重新复制到高速缓存内。同时,所请求的指令或数据也会被送给CPU。这个过程对软件程序员来说是透明的,他们根本看不到这个过程。

CPU没有必要等待缓冲行过程的结束就可以使用数据。例如,缓存控制器将首先访问缓存行内的关键字。如果想执行一个加载指令,而该指令在缓存中缺失,则引起缓存行的填充操作,CPU首先重新得到包含所请求数据的部分缓存行。所请求数据被提供给CPU流水线,同时,缓存硬件和外部总线结构可以在后台读剩余的缓存行。

4.虚拟和物理标记及索引

注:学习该部分内容需要有地址转换过程的一些基本知识。不熟悉该部分内容的读者需要先学习6.3节的知识。

每个Arm处理器(如Arm720T或Arm926EJ-S)使用虚拟地址提供所有的索引和标记值。这种方式的优势是CPU可以查找存储器,而不需要虚拟地址到物理地址的转换;劣势是改变系统内虚拟地址到物理地址的映射意味着必须先清空无效缓存,这对性能有很大的影响。

Arm11系列处理器使用不同的缓存标记方式。在这种方式中,缓存索引仍然来自一个虚拟地址,但是标记来自物理地址。物理标记的优势在于,当改变系统内虚拟地址到物理地址的映射时,不需要使缓存无效。对经常需要修改转换表映射的多任务操作系统来说,这种方式有很明显的优势。因为这种方式使用虚拟地址,所以有一些硬件优势。这意味着缓存硬件能以并行的方式读取来自每路相应行的标记值,而不必真正执行虚拟地址到物理地址的转换操作,这样就得到快速的缓存响应,经常称之为虚拟索引物理标记(Virtually Indexed,Physically Tagged,VIPT)。

VIPT的实现也有一些缺点。对一个4路组关联32KB或64KB的高速缓存来说,地址的[12]和[13]比特位要求用于选择索引。如果在MMU中使用了4KB的页面,那么虚拟地址的[13:12]比特位可能不等于物理地址的[13:12]比特位。因此,如果多个虚拟地址映射到相同的物理地址,则可能存在潜在的一致性问题。一般通过使用物理索引和物理标记(Physically Indexed,Physically Tagged,PIPT)缓存策略来避免这个问题。Cortex-A9处理器的数据缓存就使用了这个策略。

6.1.5 缓存策略

在缓存操作中,有不同的选择,包括考虑如何将来自外部存储器的一行放到高速缓存中,即分配策略;控制器如何决定组关联缓存的哪一行用于缓存新进来的数据,即替换策略;当CPU执行一个写操作,并且该写操作命中高速缓存时所发生的情况,即写策略。

1.分配策略

当CPU执行缓存查找操作,并且地址不在高速缓存中时,它必须确定是否执行缓存行的填充操作,并且复制存储器的地址。

1)读分配策略

只在读时分配一个缓存行。如果CPU执行写操作,而在缓存中缺失,则缓存没有影响,并且将写传到层次中的下一级存储器中。

2)写分配策略

在读或写时,如果在缓存中缺失,则分配一个缓存行,更准确地应该称之为读—写缓存分配策略。对于所有缺失缓存的读和写,将执行缓存行的填充操作。

2.替换策略

当出现缓存缺失情况时,缓存控制器必须选择组内的一个缓存行,用于缓存新进来的数据。被选中的缓存行称为被淘汰者(Victim)。如果被淘汰者包含有效的脏数据,则在将新数据写到被淘汰者中之前,必须将该被淘汰者写到主存储器中,这称为淘汰。

注:Victim表示被选中的缓存行内的数据将要被替换掉,也可以翻译成牺牲者。

替换策略用于控制选择被淘汰者的过程。地址的索引位用于选择缓存行的组,替换策略用于从所要替换的组中选择特定的缓存行。

1)轮询或周期替换策略

轮询或周期(Round-Robin)替换策略是指使用一个计数器(被淘汰者计数器),它周期性地贯穿可用的路。当它到达最大的路时,返回0。

2)伪随机替换策略

伪随机(Pseudo-Random)替换策略是指随机选择组内下一个缓存行作为替换内容。被淘汰者计数器以随机方式递增,可以指向组内的任意一行。

3)最近很少使用替换策略

最近很少使用(Least Recently Used,LRU)替换策略用于替换最近很少使用的缓存行或页面。

大多数Arm处理器同时支持前两种替换策略。Cortex-A15处理器也支持最近很少使用替换策略。轮询或周期替换策略有更好的可预测性,但是在某些情况下,可能其性能较低,因此可能倾向于使用伪随机替换策略。

3.写策略

当CPU执行一个保存指令时,执行给定地址上的缓存查找。对一个缓存命中的写操作来说,它有以下两个选择。

1)写通过策略

写通过策略对高速缓存和主存储器同时执行写操作。这就意味着高速缓存和主存储器必须保持一致性。由于对主存储器有较多的写操作,因此在一些情况下,如在频繁更新主存储器时,写通过策略比写回策略的速度要慢。如果连续写较大的存储器块,则将对写进行缓冲,这样,它就可以和写回策略一样高效。如果不期望很快从任何时候开始读主存储器,如较大主存储器的复制和主存储器初始化,则最好不要使用这种策略填充缓存行。

2)写回策略

写回策略只对高速缓存执行写操作,而不写主存储器。这就意味着缓存行和主存储器中可以保存不一样的数据,即缓存行保存较新的数据,而主存储器则保存旧数据。为了标识这些缓存行,每行都有一个相关的脏位。当发生写操作更新缓存行而不是主存储器时,设置脏位。在替换带有脏位的缓存行时,将该行写到主存储器中。使用写回策略能显著地减少对主存储器的访问次数,因此提高了系统性能并降低了功耗。然而,如果在系统中没有其他代理,则它能以与CPU相同的速度访问主存储器,此时需要考虑一致性问题。

6.1.6 写和取缓冲区

写缓冲区是CPU内(有时它在系统的其他部分)的硬件块,使用大量的缓冲区实现。它接受与写主存储器相关的地址、数据和控制值。当CPU执行保存指令时,它会放置相应的细节,如所写的位置、要写的数据和写到缓冲区的交易大小。CPU并不等待完成对主存储器的写操作,而是继续执行下一条指令。写缓冲区本身将清空来自CPU的写操作,并将它们写到存储器系统中。

由于CPU不必等待写主存储器过程的完成,因此使用写缓冲区可以提高系统性能。事实上,假设在写缓冲区内有空间时,写缓冲区掩盖了延迟。如果写次数较少且有空间,则写缓冲区不会变满。如果CPU产生写的速度快于写缓冲区将数据写到主存储器的速度,则写缓冲区最终会变满,这样对系统性能的提升就比较有限。

一些写缓冲区支持写合并(也称为写组合)操作,能将多个写,如将相邻字节的写数据流合并成一个猝发,这就能减少对外部主存储器写的次数,提高系统性能。

当访问外设时,写缓冲区的行为可能不是程序员想要的,程序员可能想让CPU在处理下一步之前停下来,等待写过程的完成。有时程序员确实想写字节流,并且不想合并写操作。

类似的部件称为取缓冲区,在一些系统中能用于读。例如,CPU包含预取缓冲区,在将指令真正插入流水线之前,从存储器中读取这些指令。通常,这些缓冲区对于程序员是透明的。当看存储器访问顺序时,可能需要考虑与插入流水线有关的一些风险情况。

6.1.7 缓存性能和命中速度

命中速度被定义为在一个指定的时间范围内,命中缓存的次数除以存储器到缓存的请求次数,通常用百分比表示,即命中率。缺失率是总的缓存缺失次数除以存储器到缓存的请求次数。

通常,较高的命中率将带来较高的性能。命中率主要取决于代码或数据关键部分的长度和空间的局限性,以及缓存大小等。

有一些简单的规则可以给出更好的性能。这些规则中最明显的是使能高速缓存和写缓冲区,并且在任何可能的地方使用它们。例如,对于存储器系统中保存代码的所有部分,更进一步地是指RAM和ROM,而不是外设。如果指令存储器被缓存,则将显著提高Cortex-A系列处理器的性能。在存储器中,将经常访问的数据放在一起也是很有帮助的。例如,将经常访问的数组放在一个缓存行开始的一个基地址中。

取一个数据的值涉及读取整个缓存行,如果没有使用缓存行内的其他字,则对性能提高的贡献就很小。这个问题可以通过“缓存友好”进行缓解,如访问连续的地址、访问数组的一行。

较短的代码要比较长的代码缓存得更好,有时甚至给出看上去非常矛盾的结果。例如,当把代码编译为Thumb(为最短的代码)时,一段C语言代码可以适配到整个缓存;但是当编译为Arm(为最高的性能)时,却不是这样。这样,导致未优化的版本可能比某些优化过的版本的运行速度还要快。

6.1.8 无效和清除缓存

当外部存储器的内容已经变化,并且想要从高速缓存中移除旧数据时,就要求无效和清除缓存。对于MMU的相关活动,如改变访问许可、缓存策略或虚拟地址到物理地址的映射,也要求无效和清除缓存。

刷新一词用于描述无效和清除缓存操作。Arm通常只用术语清除(Clean)和无效(Invalidate)。

(1)无效缓存或缓存行意味着清除数据,通常通过清除一个缓存行或多个缓存行的有效位来实现。当复位后没有定义有效位的内容时,高速缓存总是无效的。如果缓存包含脏数据,那么,通常来说使它无效是不正确的。通过简单的无效,任何在缓存中已经被更新的数据在写回存储器中可缓存的区域时,都会造成数据丢失。

(2)清除缓存或缓存行意味着将脏数据的内容写到主存储器中,并且清除缓存行内的脏位。这就使得缓存行和主存储器的内容一致。这只应用于使用写回策略的数据高速缓存。缓存组、缓存路或虚拟地址可以执行无效和清除缓存操作。

将代码从一个位置复制到另一个位置可能要求清除或无效缓存。存储器复制代码将使用加载和保存指令,它们将运行在CPU的数据一侧。如果数据高速缓存将写回策略用于被写代码的区域,那么在执行代码前,必须清除来自缓存的数据。这就保证保存指令将数据写到主存储器中,用于取指令逻辑。此外,如果用于代码的区域先前被其他程序使用,则指令高速缓存将包含旧数据。因此,在跳转到最新复制的代码之前,有必要使指令高速缓存无效。

对缓存的清除和无效操作是通过CP15指令完成的。它们只可用于特权级代码,不能在用户模式下执行。在使用Trust Zone安全性扩展的系统中,对于这些操作的非安全使用有一些硬件上的限制。

CP15指令可以清除/无效,或者同时清除和无效第一级数据与指令高速缓存。当知道缓存没有包含脏数据时,只无效但不清除是安全的。例如,哈佛缓存结构指令高速缓存,或者当数据正在被覆盖时,不用担心丢失以前的值。可以在整个缓存或缓存行上执行操作。这些单独的缓存行可以通过给定一个虚拟地址进行清除或无效,或者在一个特殊组内指定缓存行号。

准备缓存的代码如代码清单6.2所示。

代码清单6.2 准备缓存的代码

注:(1)内存屏障是一条指令或指令序列,用于强制对事件进行同步。也就是说,当没有执行完前面的代码时,不能继续执行下面的代码。例如,数据存储器屏障指令DMB用于保证在DMB指令后的任何存储器访问到来之前,系统中访问存储器的指令都应该完成。它不影响处理器执行其他指令的顺序。

(2)数据同步屏障DSB与DMB有相同的效果;此外,DSB还对包含全指令流的存储器访问进行同步,而不只针对其他存储器访问。当遇到DSB指令时,停止执行,直到完成所有超前明确的存储器访问。当完成所有超前读,以及清空写缓冲区后,继续正常的操作。它不影响指令预取。

(3)指令同步屏障ISB指令用于刷新处理器的流水线和预取缓冲区。这样,在指令完成后,从缓存或存储器中取出在ISB后的所有指令,保证改变上下文操作的效果在ISB后所加载的指令之前执行,如CP15、ASID、TLB或分支预测器操作的变化。它本身并不会引起数据和指令高速缓存之间的同步,但是被要求作为其中的一部分。

(4)可以通过Linux内核代码访问这些操作,使用下面的函数:

类似的函数存在于其他操作系统中,如谷歌的安卓操作系统中使用cacheflush()函数。

一种无效和清除缓存的普遍情况是DMA请求。当要求CPU所进行的修改对外部存储器可见时,DMA存储器可以进行读操作,此时需要清除缓存。当DMA写外部存储器时,也需要保证它对CPU是可见的,在缓存中,必须无效那些被影响的地址。

6.1.9 一致性点和统一性点

对于基于组/路的清除和无效,在一个指定的缓存级上执行操作。对使用虚拟地址的操作来说,架构定义了以下两个概念点。

1.一致性点

对于特殊的地址,一致性点(PoC)是一个点,在该点上,所有能访问存储器的块(如CPU、DSP或DMA引擎)保证在一个存储器位置看到相同的拷贝(复制),如只是外部的主系统存储器,如图6.7所示。

图6.7 一致性点

2.统一性点

用于一个CPU的统一性点(PoU)是一个点,在该点上,确保CPU的指令和数据高速缓存在一个存储器位置中看到相同的拷贝(复制),如图6.8所示。例如,一个统一的L2高速缓存是系统中的PoU,包含哈佛缓存结构的第一级高速缓存和一个用于缓存转换表入口的TLB。如果没有出现外部缓存,则主存储器应该是PoU。

图6.8 统一性点

在Cortex-A9处理器中,PoC和PoU本质上处于相同的位置,即L2接口。

注:对TLB不熟悉的读者可以参考6.3节的内容。

维护高速缓存的代码如代码清单6.3所示。该段代码给出了一个通用的机制,用于清除整个数据或将缓存统一到PoC中。

代码清单6.3 维护高速缓存的代码

6.1.10 Zynq-7000 SoC中的Cortex-A9 L1高速缓存的特性

1.公共特性

(1)通过系统控制协处理器,可以单独禁止每个缓存。

(2)所有L1高速缓存的缓存行长度均为32字节。

(3)所有缓存为4路组关联。

(4)L1高速缓存支持4KB、64KB、1MB和16MB的虚拟存储器页。

(5)两个L1高速缓存均不支持锁定特性。

(6)通过64位的接口,L1高速缓存连接到整数核和AXI主接口上。

(7)缓存替换策略为伪轮询或伪随机替换策略。当缓存缺失时,读取被淘汰者计数器。如果没有分配缓存,则在分配时递增被淘汰者计数器。在组中,替换一个无效的缓存行先于使用被淘汰者计数器。

(8)当缓存缺失时,首先执行使用关键字填充缓存行的操作。

(9)为了降低功耗,通过利用很多缓存操作是连续的这个特点,减少读全部缓存的次数。如果一个读缓存操作与前面的读缓存操作是连续的,并且在一个相同的缓存行内读取,则只访问之前读取的数据RAM组。

(10)所有的L1高速缓存都支持奇偶校验。

(11)将所有存储器的属性输出到外部存储器系统中。

(12)对于TrustZone安全性,支持将安全/非安全状态输出到缓存和存储器中。

(13)当CPU复位时,清除L1高速缓存的所有内容,以遵守安全性要求。

注:用户在使用指令缓存、数据缓存和BTAC前,必须使它们无效。即使为了安全性的原因推荐,也不要求无效主TLB。这样可以保证兼容未来处理器的版本。

2.指令高速缓存的特性

L1指令高速缓存用于给Cortex-A9处理器提供一个指令流。L1指令高速缓存直接连接预取指令单元。预取指令单元包含两级预测机制。L1指令高速缓存为虚拟索引和物理标记。

3.数据高速缓存的特性

L1数据高速缓存用于保存Cortex-A9处理器使用的数据。L1数据高速缓存的关键特性如下。

(1)数据高速缓存为物理索引和物理标记。

(2)数据高速缓存为非阻塞型。因此,加载/保存指令能连续地命中缓存。同时,执行由于先前读/写缺失所产生的来自外部存储器的分配操作。数据高速缓存支持4个超前读和4个超前写。

(3)CPU能支持最多4个超前预加载指令。然而,明确的加载/保存指令有更高的优先级。

(4)Cortex-A9加载/保存单元支持预测的数据预加载,加载/保存单元用于监控程序的顺序访问。在请求开始前,开始加载下一个期望的缓存行。通过CP15辅助控制寄存器的DP位使能该特性。在分配前,可以不使用这个预取行,因为预加载指令有更高的优先级。

(5)数据高速缓存支持两个32字节的行填充缓冲区和一个32字节的淘汰缓冲区。

(6)Cortex-A9处理器有一个包含64位槽和数据合并能力的保存缓冲区。

(7)数据高速缓存的所有读缺失和写缺失都是非阻塞型的,数据高速缓存支持最多4个超前数据读缺失和4个超前数据写缺失。

(8)通过使用MESI算法,APU数据高速缓存支持全部侦听一致性控制。

(9)Cortex-A9内的数据高速缓存包含本地保存/加载互斥监控程序,用于LDREX/STREX指令的同步,该指令用于实现信号量。互斥监控程序只管理包含8个字或一个缓存行颗粒度的地址。因此,需要避免使用交错的LDREX/STREX序列。并且,总是执行一个CLREX指令,作为任何上下文切换的一部分。

(10)数据高速缓存只支持写回/写分配策略,并不支持写通过和写回/非写分配策略。

(11)L1数据高速缓存支持与L2高速缓存相关的互斥操作。互斥操作表示只有在L1或L2内的一个缓存行上是有效的,但不同时有效,即当填充到L1的某一行时,在L2内将该行标记为无效。同时,淘汰L1中的某一行,将使得该行被分配到L2中。当把来自L2中带有脏数据的一行填充到L1中时,会强制将该行淘汰到外部存储器中。默认禁止互斥操作,这样可以提高缓存的利用率并降低功耗。 zgxYFofB9oE1EagEwM2tY3gVffGFi3SPTSACsgLoCFU++ca7OkbgMU3GtnQVN7RE

点击中间区域
呼出菜单
上一章
目录
下一章
×