ARM体系架构——cache

2020-04-08  本文已影响0人  wipping的技术小栈

一、前言

相信很多开发人员都听说过 cache,但有可能对其不甚了解。毕竟在软件开发中很少会接触到这个概念,它的运作完全由 CPU 来完成,一般情况下不需要我们人员去干预。本文就来借助《ARM嵌入式系统开发》一书进行笔记整理,简单地讲述一下 cache 的相关内容,希望能够帮助到各位读者。

二、正文

首先,我们先需要明确 为什么需要cache?
回答: CPU 的运行速度比 内存 的存储速度好快上许多,这样会导致 CPU 需要等待 内存 完成处理后才能继续下一道指令,而 cache 是为了能够解决这一现象,cache 的处理速度跟得上 CPU,但它的存储空间非常小。将 cache 作为中间缓存,CPU 可以不用等待 内存,而 cache 可以在接收 CPU 的数据后,在往后的时间将这些数据放进 内存
其次是 cache的大致原理是什么?
回答:在程序运行过程中,我们会有一个 局部性原理。这里不展开说明。它的大致意思就是程序会 频繁访问局部内存cacheCPU硬件 自动通过 内存地址 来找到对应的数据的。如果地址变换频繁,那么 cache 中存放的数据就会频繁改变。如果程序频繁访问 局部数据,那么 cache 中的数据改变就不会很大。因而命中率就会提高,从而 CPU 的运行效率也会提升。

2.1 计算机内存存储层次

如下图所示:


计算机内存存储层次

寄存器内存 之间存在一道缓冲,分别是 cache写缓冲

cache高速片上存储阵列,用于临时装载慢速存储器中的程序和代码。
写缓冲器一个容量很小的FIFO缓冲器,主要用于对cache中写入内存的数据提供缓冲

根据 cacheMMU(内存管理单元) 的关系,可以将 cache 分为以下类型:

2.2 cache结构

CPU 在现阶段分为 冯诺依曼结构哈佛结构cache 也分为 2 种结构分别支持这 2 种 CPU结构

整个 cache 分为 cache控制器cache存储器

2.2.1 cache存储器

如下图:


cache存储器结构

cache行 分为以下 3 个部分:

注意:每个 cache 的地址分段长度是不同的,所以图中没有给出对应的位域

2.2.3 cache控制器

cache控制器 可以将 内存 中的数据或代码自动复制到 cache存储器中,也就是能够在软件不为所知的情况下 自动完成搬运工作
cache控制器 的工作流程如下:

  1. cache控制器 通过 组索引cache存储器 中确定可能所要求的代码或数据的 cache行 的位置。然后通过 cache标签状态位 来确定数据的实际存储位置。
  2. cache控制器 检查 有效位,确定该 cache行 当前是否处于 活动状态,并且将 请求地址 上的 标签cache标签 比较。如果 cache行 当前是活动的,并且 标签域cache标签 的值也相同,则 cache命中(hit),否则,cache失效(miss)
  3. cache失效 的情况下,cache控制器内存 中复制整个 cache行cache储存器中。这个复制过程称为 cache行填充
  4. cache命中 时,cache控制器 直接从 cache存储器 为处理器提供数据或代码。cache控制器 使用 数据索引cache行 中选择命中的代码或数据,并将其提供给处理器。

2.3 cache与内存的关系

前面讲过 cache 是通过内存地址进行数据索引的,那么就存在一种 从内存到cache行 的映射。

2.3.1 直接映射cache

这种映射关系比较简单,内存 中的每个地址都对应 cache存储器 中唯一的一行。
举个例子,假设 一个4K大小的cache,前面提到 2 点,即一个数据8bit 和 一个cache行有16个数据数据项,那么可以计算出一共有 256 个cache行。并且该 cache 的索引规则为 0-3bit为数据索引(刚好可以索引16个数据项),4-11bit为组索引。有 0x000108240x00020824 这 2 个地址,那么可以发现这 2 个地址对应的 cache行 是同一个。

可以通过将 内存地址中标签域 进行比较,就可以确定存储的是 0x00010824 的数据还是 0x00020824 的数据。

当想要从 cache存储器 中读取 0x00010824 的数据,但 cache存储器 中存放的数据是地址 0x00020824 时,会发生 cache行 填充。此时 cache控制器 可以从 内存 中取出数据向 cache 中搬运的同时,也将数据传送给处理器,该过程称为 数据流注(data stream)数据流注 允许处理器一边执行程序,一般向相应的 cache行 搬运剩余的数据和代码。

如果此时存放 0x00020824 数据的 cache行有效位1(即该cache行有效),同时内存中地址 0x00020824cache行 中的不同。那么 这个过程也称为 替换

cache替换 会将 cache行 中的数据写入 内存 的对应地址中,并删除 cache行 的原本内容,替换新地址的数据

直接映射cache 的缺点是会导致 cache行 频繁置换,即 cache存储器 中同一位置的软件冲突,这称为 cache颠簸(thrashing)

2.3.2 组相联cache

2.3.2.1 组相联cache结构

前面讲到了 直接映射cache 的缺点,那么 组相联cache 就是用于解决该问题。
组相联cachecache存储器 分成了一些相同容量的小单元,称职为 路(way)。以前面的 4Kcache 为例子,一共过来 256个cache行,现在分成 4路,那么每路有 64个cache行
如下图所示:

多路cache

组相联cache 中,每一路都有 相同组索引的cache行,所以一个 组索引 对应多个 cache行组索引 相同的 4个cache行 被称为 处于同一组,这就是 组索引 命名的由来。此时这 4个cache行 也被称为 组相联的。这就是 组索引 命名的由来,如下图所示:

同组cache行

内存中的代码或数据在不影响程序执行的情况下会被 分配到任一路中。同时 同一组cache行 的数据具有 排他性,可以防止同样的数据被重复放在 同组内的不同cache行中

使用 组相联cache,我们内存映射到 cache 的大小被缩小了4倍,而同一个 cache行 被替换的概率减小为原来的 1/4,所以 组相联大小 为代价来解决 cache颠簸

2.3.2.2 CAM

假设在 64路组相联 的情况下,给出一个地址,那么这个地址的 标签域 就要被比较 64次 才有可能找出正确的 cache行,毕竟谁也不知道该地址的数据被藏在哪个 cache行 中。所以这个时候就有硬件 内容寻址存储器CAM(Content Addressable Memory)。在本例中 cache一共是64路cache,每路cache4个cache行,此时 CAM的数量为4
CAM 的工作流程如下:

  1. 地址的 标签部分 作为 4个CAM 的输入,每个 CAM输入标签同组内的64个cache行 进行比较。如果发现配,则数据由该 cache存储器 提供,否则将产生 失效信号(miss)
  2. 使用 组索引域 来选择 4个CAM 中的一个
  3. 再通过 数据索引部分 找到正确的数据。

2.3.3 cache指标

一般情况下有 1=命中率 + 失效率

2.4 cache策略

cache策略 分一下 3 种,每种策略都是应用于不同的操作:

2.4.1 写策略

写策略 分为 2 种:

下面说说 回写法 的如何把数据写入内存:

  1. cache控制器cache存储器 中的某一行写入数据时,会将 脏位 设置为 1
  2. 如果 处理器 访问该 cache行,通过 脏位 的状态可以知道该 cache行 的数据是否含有内存中没有的数据。
  3. 如果 cache控制器 要将一个 脏位为1cache行 替换出 cache存储器,那么该 cache行 的数据会自动写到 内存 中去,以保证数据不会在 内存 中丢失。

当程序频繁使用某些临时的局部变量时,回写法优于直写法

2.4.2 替换策略

cache失效 时,cache控制器 必须从当前有效的组中选择一个 cache行 来存储新的数据。被选中的 cacha行 称为 丢弃者(victim)。如果 丢弃者脏位 为 1,则在被写入新数据前会把原来的数据写入 内存。选择和替换的过程称为 淘汰

替换策略 就是如何在一个组内选择 cache行 来写入新的数据,一般有下面几种 替换策略

一般来说,轮转法 有更好的 可预测性,但是当存储器访问发生一些小的变化时有可能造成性能上较大的变化。

书中使用了一个例子来说明 轮转法 的缺点,笔者本来想复现代码,但发现手上的开发板的策略只支持 伪随机法,无法设置策略、

2.4.3 分配策略

按照笔者的理解,分配策略 是指 cache失效 在什么条件下需要找 cache行 来替换。一般情况下有下面 2 种策略:

2.5 cache架构

2.5.1 cache架构基本信息

前面我们讲了 cache 的基本信息,都是基于单个 cache 来说的。但是在 ARMA系列核心 上,cache架构 是具有多个层次的,其架构图如下:

cache架构

如图所示,每一个 CPU核 上都有 IcacheDcache(我们一般将这 2 种 cache 称为 L1 cache),再往下一级就是 L2 cache。每个 CPU核 都有自己的 L1 cache,往下再 共享L2 cache。一般来说,L1 cache 的大小在 16KB-32KB,而 L2 cache 的大小在 256KB 以上。

按照图中所示,我们可以将其 存储层次 分为 3 层:

  1. L1 cache
  2. L2 cahce
  3. memory

通常来说,越接近 CPU 的存储层次,其访问速度越快

2.5.2 VIPT

我们前面讲到访问 cache 的地址会被拆分为 标签组索引数据索引。但是并没有说明这个地址是 物理地址 还是 虚拟地址

在现在 嵌入式linux 系统中,大部分都有 MMU 来管理 虚拟地址,以支持 进程特性。如果系统使用 虚拟地址,那么在访问 cache 时会出现以下问题:

为了解决上面的问题,ARM 使用了另一种方案来访问 cache。即 virtual index physical tag(VIPT),也就是说 使用虚拟地址的组索引和物理地址的标签

其步骤如下:

  1. 访问 TLB,将 虚拟地址 转换为 物理地址。与此同时,利用 虚拟地址 中的 组索引 访问cache,获取取出同一组内所有的cache行信息
  2. 假设 TLBcache 都命中,则利用 步骤1 得到的 物理地址 中的 标签来找出确定 cache行

2.5.3 PoC和PoU

前面说到 cache 是有多个层次的,最多的 ARMv8 可以支持 L1-L7 cache,再加上 内存。 一共 8 个 存储层次。随着 cache层次 的增加,cache硬件 也会越来越复杂,其管理难度也随机上升。

cache汇编指令操作 如下:

  1. 清理(clean)清理cache 会将 cache 中的 脏数据 写入内存。
  2. 清除(Invalidating):清除cache 会直接将 cache 中的数据复位,一般来讲是 有效位置0
  3. 锁定:锁定 cache数据 不被修改

当我们需 清理多层次cache 时,这时就有一个问题: 如何保证多个存储层次层次的一致性?
前面讲过 cache 会根据 脏位 来考虑是否将 cache数据 写入 内存。这看起来很简单,但是因为只简到了 1个层次的cache。如果 多存储层次高存储层次 中包含了部分下一级存储层次 的内容。此时维护数据一致性变得复杂了。例如:当要 清理 某个地址对应的 cache行 时,我们可以假设有下面 3 种操作层次:

  1. 仅仅操作 L1
  2. 覆盖L1和L2
  3. 覆盖L1-L3中对应的cache行

按照笔者的理解,为了明确操作的层次,ARM 定义了 2 个概念来解决歧义:

笔者也将自己在查找资料过程中涉及的其他信息也罗列出来,如下:

参考链接

《ARM嵌入式系统开发》
《Cortex-A7 MPCore Technical Reference Manual》
《ARM Cortex-A Series Programmer’s Guide》
《ARM Architecture Reference Manual》
为打开MMU而进行的CPU初始化
什么是PoU和PoC?
Cache 为什么是物理地址映射? 及与TLB的关系?
ARMv8之Observability
物理CPU CPU核数 逻辑CPU 几核几线程的概念详解
PoU and PoC in cache maintenance operations in arm

上一篇 下一篇

猜你喜欢

热点阅读