[转载]深入学习Cache系列1:带着几个疑问,从Cache的应用场景学起

adtxl
2023-02-28 / 0 评论 / 349 阅读 / 正在检测是否收录...

作者简介
baron (网名:代码改变世界ctw),九年手机安全/SOC底层安全开发经验。擅长trustzone/tee安全产品的设计和开发

1. 序言

带着问题去学习,关于cache的一些思考:

1、L1/L2/L3 cache到底在哪里?L1/L2/L3 cache分别都是多大?
2、L1/L2/L3 cache的组织形式都是怎样的?n路组相连?
3、你见过VIVT的cache吗?你为什么要学习VIVT的cache?非常干扰你对cache的理解,还不如不学呢.
4、那么cache是VIPT还是PIPT?还是在一个core中既有VIPT,也有PIPT?
5、你要学习MESI的原理吗?你能记得住吗?你是不懂MESI,还是不懂cache架构?
6、MOESI又是啥玩意?现在主流的core是MESI,还是MOESI?
7、MESI仅仅是一个协议,总得有硬件来执行这个协议,硬件是谁?
8、MESI这个协议有4个状态,这4个状态记录在哪里?
9、L1/L2/L3 cache中,或者说core cache/cluster cache中,哪些cache的维护遵守了MESI协议,哪些没有遵守?为什么这样设计?
10、cache line中的data是多少个字节?在分析问题时,你为什么总是按照条件分析,16bytes的cache line是怎样的,64bytes的cacheline是怎样的?难道你不知道,现在主流的arm core的cache line全部都是64bytes?
11、cache的TAG是什么玩意,里面都有什么?别说cache TAG是物理地址?
12、cache line中又都有什么? 为什么没有index?
13、L2 cache到底是在core中,还是在cluster中?
14、假设一块内存配置成了non-cacheable,为什么就不缓存到cache了?
15、页表entry的属性中定义了cache的缓存策略,那如果disable mmu后,那么cpu读写内存时候的缓存策略是什么?
16、做为一名软件工程师,对于L1/L2/L3 cache的缓存策略,哪些可以修改?哪些是硬件定死的不可以修改?而这些的替换策略又都是怎样的?17、什么是inclusive cache?什么是exclusive cache?Strictly和Weakly呢?
18、一些概念的理解,如CCI、SCU、DSU、ACE、CHI ?
19、如何配置一个页面的cacheable属性?如何配置页表的cacheable属性?

2. 前言

做为一名底层安全工程师、一名一线支持客户的FAE,工作的内容涉及到TF-A、TEE、TA、Linux Kernel、Linux native程序等众多模块,也会涉及到一些硬件模块driver。在这些不同的硬件或系统软件之中,有着不同的memory属性的配置,不同的缓存策略,那么我们在这多硬件多软件通过share memory通信时,就会遇到各种各样的问题,其实很多时候,也都是客户的灵魂一问,为了给客户一个专业的感觉,身为FAE也不得不去弄懂底层深层次的原理....

本人不是什么专家,更不是什么的大佬,也就是看了一些arm文档,加上自己的理解,然后总结出如下文章,当然我在总结的时候,一切都以官方资料为准,尽量不瞎说不乱说,有些查不到的资料我求证了一些ASIC专家。其实cache同其它模块(如MMU、异常、gic...)相比,cache应该算上最难的,不过好在它的大多数行为都是硬件帮我们做好了,所以我们软件就简单了,但是越是硬件自动的行为,对于我们软件工程师理解起来就会吃力,因为看不到资料看不到设计,很多都得靠猜。

最后,希望这系列文章,能够对大家有所帮助。好好学习、天天向上,卷起来同志们。

3. 为什么要用cache?

ARM 架构刚开始开发时,处理器的时钟速度和内存的访问速度大致相似。今天的处理器内核要复杂得多,并且时钟频率可以快几个数量级。然而,外部总线和存储设备的频率并没有达到同样的程度。可以实现可以与内核以相同速度运行的小片上SRAM块,但与标准 DRAM 块相比,这种 RAM 非常昂贵,标准 DRAM 块的容量可能高出数千倍。在许多基于 ARM 处理器的系统中,访问外部存储器需要数十甚至数百个内核周期。

高速缓存是位于核心和主内存之间的小而快速的内存块。它在主内存中保存项目的副本。对高速缓冲存储器的访问比对主存储器的访问快得多。每当内核读取或写入特定地址时,它首先会在缓存中查找。如果它在高速缓存中找到地址,它就使用高速缓存中的数据,而不是执行对主存储器的访问。通过减少缓慢的外部存储器访问时间的影响,这显着提高了系统的潜在性能。通过避免驱动外部信号的需要,它还降低了系统的功耗

image.png

4. 为什么要学习cache呢?

cache和我们软件工程师有啥关系?其实在很多时候,硬件都会自动去维护cache和内存直接的一致性,这和我们软件工程师都没有太大的关系,所以很多时候我们也无需去理解cache的原理。但事实就是事实,不管你有没有理解,你都是一直在使用的。做为一名底层的软件开发者,有些时候,你也不得不去主动刷新cache,即软件中维护内存一致性 。那么一般什么时候需要主动刷cache呢(软件中维护内存一致性) ? 以下便举了几个最常见的示例。

4.1 不同的Master硬件共享数据时

例如一个core和一个crypto engine硬件,在共享数据的时候。需要软件主动去invalid或flush cache的操作。

4.1.1 软件中维护缓存一致性

imageaed46c9f5da248d6.png

4.1.2 软件中维护内存一致性 – invalid cache

imaged9c28b520847a7c2.png

4.2 不同的缓存策略的系统共享数据时

例如在一个TEE + linux的系统中,且两个系统有着不同的缓存策略。如linux kernel中是outer cacheable,TEE中是non-cacheable

4.2.1 软件中维护内存一致性 – flush cache

imageab00fdf6f7695556.png

4.2.2 软件中维护内存一致性 – invalid cache

imagefcc7a6b61c16480d.png

5. 怎么去刷cache呢?(软件维护cache的一致性)

ARM提供了操作cache的指令, 软件维护操作cache的指令有三类:

  1. Invalidation:其实就是修改valid bit,让cache无效。
  2. Cleaning:清除cache中的data和TAG,这其实就是我们所说的flush cache,这里会将cache数据回写到内存,并清除dirty标志
  3. Zero:将cache中的数据清0.

那么一般什么时候需要软件维护cache一致性呢?:
(1)、当有其它的Master改变的external memory,如DMA操作
(2)、MMU的enable或disable的整个区间的内存访问,
(3)、当不同缓存策略的系统使用同一块内存通信时,如REE enable了mmu,TEE disable了mmu.

针对第(2)点,cache怎么和mmu扯上关系了呢?那是因为: mmu的开启和关闭,影响了内存的permissions, cache policies

5.1 cache一致性指令介绍

查阅armv8/armv9的aarch64体系中,定义了如下的缓存一致性操作指令

image46cc03ab0dd074d7.png

指令太多,不太好记,然后我们总结如下:

imagec3bb1fcfa54af0a5.png

按照指令,分为:

  1. IC : 操作instruction cache
  2. DC : 操作data cache

按照操作,分为以下三类:

  1. Invalidation:其实就是修改valid bit,让cache无效。
  2. Cleaning:清除cache中的data和TAG,这其实就是我们所说的flush cache,这里会将cache数据回写到内存,并清除dirty标志
  3. Zero:将cache中的数据清0.

Points的定义:其描述的是操作cache的范围

  1. Point of Coherency (PoC) :instruction、data、TLB访问一致性的点
  2. Point of Unification (PoU) :agents访问内存一致性的点
  3. Point of Persistence (PoP) :和FEATDPB、FEATDPB2 feature相关
  4. Point of Deep Persistence (PoDP) :访问memory一致性的点

image503afe0e05f0a6da.png

5.2 cache一致性指令的使用示例

image9706e604c41f95b6.png

5.3 操作系统中软件维护cache一致性的API

在操作系统中,我们只需要调用相关的API即可,也无需牢记以上的维护cache一致性的命令。

比如在Linux Kernel 操作Cache的API如下所示:

linux/arch/arm64/mm/cache.S
linux/arch/arm64/include/asm/cacheflush.h


void __flush_icache_range(unsigned long start, unsigned long end);

int  invalidate_icache_range(unsigned long start, unsigned long end);

void __flush_dcache_area(void *addr, size_t len);

void __inval_dcache_area(void *addr, size_t len);

void __clean_dcache_area_poc(void *addr, size_t len);

void __clean_dcache_area_pop(void *addr, size_t len);

void __clean_dcache_area_pou(void *addr, size_t len);

long __flush_cache_user_range(unsigned long start, unsigned long end);

void sync_icache_aliases(void *kaddr, unsigned long len);

void flush_icache_range(unsigned long start, unsigned long end)

void __flush_icache_all(void)
0

评论 (0)

取消