0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深入理解CACHE VIPT与PIPT的工作原理

冬至子 来源:Linux与SoC 作者:linux-soc 2023-06-05 14:56 次阅读

启动信息描述

内核启动过程中有如下打印信息:

CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache

这行打印信息代表了处理器L1 CACHE所支持的寻址方式。

在kernel启动过程中,虽然这里第一次出现CACHE相关的打印信息,但是,此处并不是kernel第一次操作CACHE。

例如:对于zImage而言,解压缩过程中会开启CACHE并配置CACHE 写属性。

#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
#define CB_BITS 0x08
#else
#define CB_BITS 0x0c
#endif

CPU初始化的汇编文件head.S中,会根据kernel配置来使能CACHE。

#ifdef CONFIG_CPU_DCACHE_DISABLE
        bic     r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
        bic     r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
        bic     r0, r0, #CR_I
#endif

代码分析

以上打印信息源自于 kernel 代码的setup.c中。

static void __init cacheid_init(void)
{
 unsigned int arch = cpu_architecture();

 if (arch >= CPU_ARCH_ARMv6) {
  unsigned int cachetype = read_cpuid_cachetype();

  if ((arch == CPU_ARCH_ARMv7M) && !(cachetype & 0xf000f)) {
   cacheid = 0;
  } else if ((cachetype & (7 < < 29)) == 4 < < 29) {
   /* ARMv7 register format */
   arch = CPU_ARCH_ARMv7;
   cacheid = CACHEID_VIPT_NONALIASING;
   switch (cachetype & (3 < < 14)) {
   case (1 < < 14):
    cacheid |= CACHEID_ASID_TAGGED;
    break;
   case (3 < < 14):
    cacheid |= CACHEID_PIPT;
    break;
   }
  } else {
   arch = CPU_ARCH_ARMv6;
   if (cachetype & (1 < < 23))
    cacheid = CACHEID_VIPT_ALIASING;
   else
    cacheid = CACHEID_VIPT_NONALIASING;
  }
  if (cpu_has_aliasing_icache(arch))
   cacheid |= CACHEID_VIPT_I_ALIASING;
 } else {
  cacheid = CACHEID_VIVT;
 }

 pr_info("CPU: %s data cache, %s instruction cache\\n",
  cache_is_vivt() ? "VIVT" :
  cache_is_vipt_aliasing() ? "VIPT aliasing" :
  cache_is_vipt_nonaliasing() ? "PIPT / VIPT nonaliasing" : "unknown",
  cache_is_vivt() ? "VIVT" :
  icache_is_vivt_asid_tagged() ? "VIVT ASID tagged" :
  icache_is_vipt_aliasing() ? "VIPT aliasing" :
  icache_is_pipt() ? "PIPT" :
  cache_is_vipt_nonaliasing() ? "VIPT nonaliasing" : "unknown");
}

首先,通过cpu_architecture()获取处理器内核版本号

int __pure cpu_architecture(void)
{
 BUG_ON(__cpu_architecture == CPU_ARCH_UNKNOWN);

 return __cpu_architecture;
}

处理器内核版本号通过数据结构proc_arch来维护,数据结构成员的值代表的ARM-vXXX

static const char *proc_arch[] = {
 "undefined/unknown",
 "3",
 "4",
 "4T",
 "5",
 "5T",
 "5TE",
 "5TEJ",
 "6TEJ",
 "7",
 "7M",
 "?(12)",
 "?(13)",
 "?(14)",
 "?(15)",
 "?(16)",
 "?(17)",
};

处理器内核版本编号如下,例如__cpu_architecture为4则代表的是ARM-v5内核。

#define CPU_ARCH_UNKNOWN 0
#define CPU_ARCH_ARMv3  1
#define CPU_ARCH_ARMv4  2
#define CPU_ARCH_ARMv4T  3
#define CPU_ARCH_ARMv5  4
#define CPU_ARCH_ARMv5T  5
#define CPU_ARCH_ARMv5TE 6
#define CPU_ARCH_ARMv5TEJ 7
#define CPU_ARCH_ARMv6  8
#define CPU_ARCH_ARMv7  9
#define CPU_ARCH_ARMv7M  10

需要注意的是,这里获取处理器内核版本号,不是通过读取MIDR寄存器来实现的。

针对具体的ARM内核版本,获取CACHE所支持的寻址方式,并据此初始化cacheid。后面以cacheid为依据对CACHE的所支持的寻址方式进行分类。

在ARM处理器内部包含了CACHE类型信息的只读寄存器,这在芯片设计初期便已经定义好,后期无法修改。在这个寄存器中包含了CACHE的寻址策略。对于ARM v系列的处理器内核而言,获取CACHE类型的指令代码如下:

#define read_cpuid(reg)       \\
 ({        \\
  unsigned int __val;     \\
  asm("mrc p15, 0, %0, c0, c0, " __stringify(reg) \\
      : "=r" (__val)     \\
      :       \\
      : "cc");      \\
  __val;       \\
 })

基于读取CACHE类型寄存器得到信息来初始化cacheid,利用cachetype.h中定义的宏,打印出cache类型。

基于cacheid定义了如下的宏定义:

#define cache_is_vivt()   cacheid_is(CACHEID_VIVT)
#define cache_is_vipt()   cacheid_is(CACHEID_VIPT)
#define cache_is_vipt_nonaliasing() cacheid_is(CACHEID_VIPT_NONALIASING)
#define cache_is_vipt_aliasing() cacheid_is(CACHEID_VIPT_ALIASING)
#define icache_is_vivt_asid_tagged() cacheid_is(CACHEID_ASID_TAGGED)
#define icache_is_vipt_aliasing() cacheid_is(CACHEID_VIPT_I_ALIASING)
#define icache_is_pipt()  cacheid_is(CACHEID_PIPT)
...
static inline unsigned int __attribute__((pure)) cacheid_is(unsigned int mask)
{
 return (__CACHEID_ALWAYS & mask) |
        (~__CACHEID_NEVER & __CACHEID_ARCH_MIN & mask & cacheid);
}

而本文开头中打印出来的信息,就是基于上面这些宏得出的结果。

从kenrel启动的日志信息中可以看出,当前CPU内核的CACHE类型如下:

DCACHE

PIPT / VIPT nonaliasing

ICACHE

VIPT nonaliasing

ARM处理器不同内核版本的CACHE类型如下:

图片

通常来说,CACHE得寻址类型包括VIPT、PIPT、VIVT,而本文重点分析得是VIPT以及PIPT的工作原理

VIPT和PIPT

具备MMU(TLB)、CACHE的ARM处理器,其地址翻译的流程如下:

图片

那么上图中红框内的CACHE部分,根据索引标签对应的是物理地址还是虚拟地址,将CACHE的寻址方式分为VIPT CACHE和PIPT CACHE。

VI的含义是使用虚拟地址来构建缓存索引(index)。反之,PI的含义是使用物理地址来构建缓存索引。

缓存索引用于从缓存中提取标记,将它与从物理地址计算的缓存标记比对。如果匹配,则缓存中命中该查找,从缓存中提取相关的数据。否则,将从下一层缓存(或从内存)中提取。

PT的含义是使用物理地址来构建缓存标记(tag),之所以使用PT作为CACHE查找索引是为了解决VIVT中存在的索引冲突,即两个进程可以为不同的物理地址使用相同的虚拟地址。

VIPT

VIPT CACHE使用物理地址作为Tag,逻辑地址作为Index。通过Index查询CACHE获取到物理地址中的tag部分。同时呢,利用逻辑地址去查TLB,在TLB中获取到物理地址。然后将CACHE中查询到的物理地址Tag部分,同TLB中获取到的物理地址Tag部分作比较。若二者相同,则CACHE hit,否则CACHE miss。

图片

aliasing

对于VIPT CACHE而言,下面代码中的virtual_addr_A和virtual_addr_B虽然不同,但是它们指向了同一个物理地址PA。

mmap(virtual_addr_A,4096,prot,flags,file_descriptor,offset)
mmap(virtual_addr_B,4096,prot,flags,file_descriptor,offset)

VIPT使用虚拟地址作为CACHE Index,因此物理地址A的数据在CACHE中有两份,分别由virtual_addr_A和virtual_addr_B进行管理。这样的情形称之为CACHE 别名。

图片

可以通过下面的4种方法来避免别名问题带来的影响:

1.当进行内存数据更新时进行cache invalid操作

  1. 多副本数据同步更新

以上两种方法需要进程通过虚实地址转换等操作,获取到是否有副本数据存在这样的信息。而VIPT的设计初衷是避免虚实地址转换,因此,这两种方法并不是非常可取。

  1. 缩小CACHE size

假设对于一个32bit位宽的虚拟地址而言,他的page offset为12bit。若CACHE size小于4K则不会产生别名问题,假设CACHE size大于4K,则会产生别名问题。但是CACHE过小,cache miss又会大幅增加。

图片

  1. page color或者cache cloring

若以上3种方法都没有解决CACHE别名问题,那么可以使用缓存着色的方法。

PIPT

PIPT中的tag和index均为物理地址。而CPU发出的逻辑地址也称之为虚拟地址,因此,首先需要通过TLB或查询内存中的页表,将逻辑地址转换为对应的物理地址。再进行CACHE缓存查找。索引和标签都使用物理地址。虽然这很简单,避免了重名问题,但速度也很慢,因为必须先查找物理地址(这可能涉及TLB丢失和访问主内存),才能在缓存中查找该地址。

图片

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    19284

    浏览量

    229796
  • ARM
    ARM
    +关注

    关注

    134

    文章

    9094

    浏览量

    367526
  • 寄存器
    +关注

    关注

    31

    文章

    5343

    浏览量

    120337
  • LINUX内核
    +关注

    关注

    1

    文章

    316

    浏览量

    21648
  • cache技术
    +关注

    关注

    0

    文章

    41

    浏览量

    1064
收藏 人收藏

    评论

    相关推荐

    深入理解运放的工作原理(空气净化器系统案例)

    重点讲解了运放的内部电路结构,帮助深入理解运放的工作原理。运放是设计使用非常频繁且非常重要器件,通常在信号放大,电流采样电路里常见,对于初学者经常感到困惑,所以掌握好能够帮助你很好的分析电路。
    的头像 发表于 04-19 09:10 6255次阅读

    深入理解运放的工作原理内部电路结构

    重点讲解了运放的内部电路结构,帮助深入理解运放的工作原理。运放是设计使用非常频繁且非常重要器件,通常在信号放大,电流采样电路里常见。
    的头像 发表于 04-22 16:02 1.8w次阅读
    <b class='flag-5'>深入理解</b>运放的<b class='flag-5'>工作原理</b>内部电路结构

    深入理解Android

    深入理解Android
    发表于 08-20 15:30

    深入理解和实现RTOS_连载

    和trcohili的帖子。深入理解和实现RTOS_连载1_RTOS的前生今世今天发布的是第一篇,"RTOS的前生今世"。通过软件系统结构的比对简要的介绍rtos为何而生。如果读者对RTOS
    发表于 05-29 11:20

    深入理解和实现RTOS_连载

    和trcohili的帖子。trochili rtos完全是作者兴趣所在,且行且坚持,比没有duo。深入理解和实现RTOS_连载1_RTOS的前生今世今天发布的是第一篇,"RTOS的前生今世"
    发表于 05-30 01:02

    深入理解lte-a

    深入理解LTE-A
    发表于 02-26 10:21

    深入理解STM32

    时钟系统是处理器的核心,所以在学习STM32所有外设之前,认真学习时钟系统是必要的,有助于深入理解STM32。下面是从网上找的一个STM32时钟框图,比《STM32中文参考手册》里面的是中途看起来清晰一些:重要的时钟:PLLCLK,SYSCLK,HCKL,PCLK1,...
    发表于 08-12 07:46

    对栈的深入理解

    为什么要深入理解栈?做C语言开发如果栈设置不合理或者使用不对,栈就会溢出,溢出就会遇到无法预测乱飞现象。所以对栈的深入理解是非常重要的。注:动画如果看不清楚可以电脑看更清晰啥是栈先来看一段动画:没有
    发表于 02-15 07:01

    为什么要深入理解

    [导读] 从这篇文章开始,将会不定期更新关于嵌入式C语言编程相关的个人认为比较重要的知识点,或者踩过的坑。为什么要深入理解栈?做C语言开发如果栈设置不合理或者使用不对,栈就会溢出,溢出就会遇到无法
    发表于 02-15 06:09

    深入理解Android之资源文件

    深入理解Android之资源文件
    发表于 01-22 21:11 22次下载

    深入理解Android》文前

    深入理解Android》文前
    发表于 03-19 11:23 0次下载

    深入理解Android:卷I》

    深入理解Android:卷I》
    发表于 03-19 11:23 0次下载

    深入理解Android网络编程

    深入理解Android网络编程
    发表于 03-19 11:26 1次下载

    深入理解MOS管电子版资源下载

    深入理解MOS管电子版资源下载
    发表于 07-09 09:43 0次下载

    深入理解Cache工作原理

    按照数据关系划分:Inclusive/exclusive Cache: 下级Cache包含上级的数据叫inclusive Cache。不包含叫exclusive Cache。举个例子,
    的头像 发表于 05-30 16:02 808次阅读
    <b class='flag-5'>深入理解</b><b class='flag-5'>Cache</b><b class='flag-5'>工作原理</b>