Fork me on GitHub
侧边栏

【ARM Cache 与 MMU 系列文章 1.5 -- ARM Cache 直接映射 详细介绍】

ARM Cache组织形式

在ARM体系结构中,缓存(Cache)是一种关键的硬件机制,用于减少处理器访问内存所需的时间。缓存可以根据其组织结构分为三种主要类型:全相连(Fully Associative),直接映射(Direct Mapped),和多路组相连(Set Associative)。每种类型在访问速度、硬件复杂度和成本之间提供了不同的折衷方案。

image

直接映射(Direct Mapped)

介绍: 在直接映射缓存中,每个内存地址通过某种映射函数(通常是地址的一部分)映射到一个特定的缓存行。这种结构简单,硬件实现成本较低,但可能会导致较高的缓存冲突(两个内存地址映射到同一缓存行),从而降低缓存效率。

在介绍直接映射之前,以停车场停车作为例子,先把结构的特点简单地概括出来,便于读者了解。

  • 停车场 - cache
  • 停车 - linefill
  • 取车 - read cache line

直接映射示例

假如所有人的车都被赋予了一个独一无二的车牌号A(A=0,1,2,…,100,101,…),现在有一个共N=10个车位的停车场,每个车位号从0开始依次递增。现在规定车牌号为A的车子只能停在 停车位 n = A % N = A%10 的位置。如下图所示,车牌号为2的车子停在2号车位,车牌号为5的车子停在5号车位。

按照这种规则,如果又来了一辆车牌号为102的车子,即使其他车位上还有空位,102号车子也只能停2号车位,如果2号车位已经有2号车占了,按照直接映射的规则,102车(newer)会把2号车(older)给驱逐(evict)出去。

image

如上图1-1 描述了按照直接映射规则停车的过程,下图则是车主取车的过程(也就是从cache 中读取数据)。

假设我是102号车主,通过简单计算停车位 n =102%10 =2 ,很容易知道我的车子停在2号车位。

直接映射规则下的停车场,每个车位都与车牌号直接对应,即使停车场还有大量空位,2号车和102号车也只能停2号车位。并按照后来者居上的原则,102号车会把2号车给驱逐出去,如果又来一辆52号车,102号车也会被52号车挤出去。所以驱逐现象( eviction )会频繁发生。

直接映射其优势 在于车主很容易就知道自己的车子停在哪个车位, 不用进行 look-up 来确认是否 miss还是hit。

image

直接映射原理

在众多的 cache 实现方式中,直接映射方式是最简单的。主存中的每个地址都能在 cache 中找到对应的 cache line,不过主存空间是远远大于cache的存储空间的,所以它必须按照上述直接映射停车场的规则: n= A % N ,将所有满足 n= A % N 的地址A放入cache下边为n的 cache line。

如下图所示的 cahce,N=4,一个 cache line 大小为 4 个word,主存地址为 0x0 0x40 0x80 、…、的数据都将放在该cache的第 0 个cache line,以此类推。

image

我们可以推算出一个主存的地址如何被划分成 cache 地址,如下图1-4 所示,

  • 由于该cache只有 4 个cache line,所以只需 2 bit 即可描述 cache line 的 index( 0b00 \ 0b01 \ 0b10 \ 0b11 ),这里我们使用地址的 bits [5:4]
  • 一个cache line有 4 个 word,也只需 2 个 bit 即可描述每个word的具体位置( 0b00 \ 0b01 \ 0b10 \ 0b11 ),这里我们使用地址的 [3:2]
  • 地址的 bits [31:6] 我们用作Tag 信息,即告诉cache controler该地址来自主存的何处,用于判断 hit or miss

image

当CPU读写一个地址时,cache controler会将该地址按照上图结构划分,并且进行如下操作:

  • 首先抽取该地址的 index 位,直接去找 cache 中对应 index 的 cache line。
  • 然后抽取该地址的 tag 信息,如果与当前 cache line里的tag一致,并且 该 cache line 的 valid bit1 (该cache line里的数据有效),即说明发生了 hit。如果 valid bit1 ,但是tag信息不一致,说明当前cache line保存的数据是其他地址的,接着需要将当前cache line里的数据 驱逐 到下一级内存中,并将新的地址上的数据填充进来。
  • 如果是hit,接着把该地址的 Line 偏移量,可能还有 bytes 偏移量取出,在对应的cache line中提取数据。
    所以内存中所有地址的bits [5:4] 相同的地址,都会映射到同一个位置的cache line 。但是在某个时刻,同一个cache line只能存放其中一个地址的数据,就像车位上某个时刻只能停一辆车一样。

Cache颠簸(cache thrashing)原因

直接映射的一大副作用就是cache颠簸(cache thrashing),下面笔者用一个示例来解释这种现象。有如下函数:

void add_array(int *data1, int *data2, int *result, int size)
{
	int i;
	
	for (i=0 ; i<size ; i++) {
		result[i] = data1[i] + data2[i];
}

功能很简单,传入三个int类型指针: int *data1 , int *data2 , int *result ,并在有限的 size 个循环内求和: result[i] = data1[i] + data2[i]

假如传入如下地址参数:

add_array(0x40, 0x80, 0x00, 16);

在一个直接映射cache实现下会发生什么呢?

完成求和运算 result[i] = data1[i] + data2[i] ,会经过如下步骤:

  • 假设当前 i=0 , 首先会读取 data1[0] ,也就是 0x40上的数据,先发生 read miss,然后linefill,将 0x40 0x4F 一个cache line大小的数据填充到 cache的第 0 行:

image

  • 首先会读取 data2[0] ,也就是 0x80 上的数据,地址 0x80 按照规则,其数据也将放在第 0 个cache line。先发生 read miss ,由于第0行已经存放了 0x40 的有效数据,所以会先进行 evict ,然后再把 0x80 上的数据替换进来:

image

  • 最后进行求和操作 data1[0] + data2[0] ,并将结果保存在 result[0] ,也就是地址 0x00 0x00 其数据也将放在第 0 个cache line。先发生 write miss ,由于第 0 行已经存放了 0x80 的有效数据,所以还会先进行 evict ,然后再把求和结果 0x00 上的数据写进cache line。

image

我们可以发现,仅仅是在一个求和 result[i] = data1[i] + data2[i] 循环中,就发生了 2 次 eviction。cache里同一个cache line里的数据经常被写入写出(linefill and evict),这就是cache thrashing。这样的现象会严重影响系统的性能,因此在ARM系列处理器中,直接映射类型的主缓存基本上没有,但是可以在一些,比 如ARM1136 处理器的分支目标地址缓存中看到直接映射缓存。

posted @ 2025-06-25 14:04  yooooooo  阅读(69)  评论(0)    收藏  举报