经典的ARM处理器高速缓存工作原理:
注意:如果TLB失中,Page Table Walk(PTW)电路不走Cache,直接从物理内存中查找页表。
一个8路组相联的高速缓存的结构示意图:
基本参数包括:
- 地址,根据Cache的设计不同,地址可能是虚拟地址VA(VIVT),物理地址PA(PIPT),或者物理地址和虚拟地址都要用到(VIPT)。
- 高速缓存行,高速缓存中最小的访问单元,包括一小段主存储器中的数据,常见的高速缓存行是32或者64字节。
- 索引域,高速缓存地址编码的一部分,用于索引和查找数据在高速缓存的哪一行。
- 组,由相同索引域的高速缓存行组成,组相连的情况下,在refill和替换时,可以选中组内任意一路来进行。组的个数等于索引条目数,也等于每一路的行数。
- 路,在组相联的高速缓存中,高速缓存由大小相同的几个块组成。路数表示同一组内高速缓存行的个数。对于只有一路的高速缓存,其实就是直接映射的高速缓存。多路设计可以减少高速缓存颠簸(cache thrashing).
- 标记,高速缓存地址编码的一部分,通常是高速缓存地址的高位部分,用于判断高速缓存缓存行的数据的地址是否和处理器寻找的地址一致。
- 偏移量,高速缓存中的偏移量,用于按照字节或者字准确定位要访问的数据内容。
组中有路,路中有组,一路中的行数等于组数,一组中的行数等于路数,缓存行是最小寻址单元。
在cortex-a7和cortex-a9的处理器中,可以看到32KB大小的四路组相连高速缓存,下面来分析一下这个高速缓存的结构:
高速缓存的总大小为32KB,并且是4路的,所以每一路的大小为8KB.
高速缓存行的大小为32字节所以每一路包含的高速缓存行数量如下:
所以在高速缓存编码的地址中,Bit[4:0]用于选择高速缓存行中的32B的数据,其中Bit[4:2]可以用于寻址8个字,Bit[1:0]可以寻址每个字中的字节。Bit[12:5]一共8位用于在索引域中选择每一路上的高速缓存行。剩下的Bit[31:13]用作标记域。
如果任意两个va命中同一个组的不同路中,则一定满足如下同余关系:
从另一个角度看,cache本身是拥有足够的信息可以推算出每个cache line对应的物理内存的,这样可以保证在全CACHE回写时,不需要用户提供地址信息。比如通过TAG以及自身索引和 数据在cacheline中的偏移,就可以组合成对应的物理地址进行回写。
另外,在启用虚拟内存情况下,CPU发出的虚拟地址的低12位代表的是访问的物理内存的页内偏移,并且这12个BIT不会在虚拟地址转换到物理地址的过程中发生改变和翻译,利用这一点,TLB查询和缓存的访问可以并行。举例来说,使用12BIT的页内偏移作为索引定位到具体的组和组内字节(CACHE LINE),此时将得到CACHE对应路数的TAG,如果CACHE是PIPT组织起来的,则同一时刻,可以利用VA的剩余BIT查找TLB条目,得到物理页号,然后利用此物理页号和上面得到的几路TAG做对比,比中的那一路即CACHE命中。这样查找CACHE和TLB可以并行操作。
举例来说,常见的page size是4KB(2^12),所以physical address和virtual address的0 - 11 bit为相同数值。利用这特性,只要在这12 bit内分配cache index和cache offset bit数,就能够确保一个physical address data只会在同一个cache line上。
如此一来,cache size就会被限制在2^12 * multi-way。像是physical page size为4KB(2^12),cache line为64(2^6)bytes,采用four-way associative cache,则cache size大小就是4KB * 4 = 16KB。
下图是一个VIPT的CACHE的的查询示意图:
可以把一个N行M路的cache想象成一个很大的停车场,加入每辆车的车牌号均为数字,要求每辆车必须停在车牌号 mod 总行数的那一行的任意一个车位上,也就是说,所有停在同一行的车辆要求对行数模同余,则其结构与CACHE比较类似。
OR1200 ICACHE架构
![](https://i-blog.csdnimg.cn/blog_migrate/2f4e34be22ba77d14deebd04dd65dc1e.png)
和ICACHE相关的特殊寄存器为ICBIR(Instruction Cache Block Invalidate Register).即指令块无效寄存器,且是不可读,只可写的寄存器,如果向ICBIR写入一个地址,设为Aaddr,那么将会使得ICACHE目录表中第Aaddr[12:4]项的V设置为0。
![](https://i-blog.csdnimg.cn/blog_migrate/7b3ecc180bdd916cb2098234a7af344b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0bb48f95c981ff51e9b4de83e6c302f7.png)
需要注意的是,系统中是没有为CACHE分配物理地址的,无法被寻址。
Intel处理器的CACHE版图结构
体系结构中的内存一致性测试
在Linux上设计如下图所示的测试方案:
- 用户态测试代码基于普通malloc分配一片堆内存,并且初始化一些特征数据。
- 借助一个用户子自定义模块驱动开放的设备节点,将buffer地址传递给内核驱动,内核驱动通过get_user_page接口获取到malloc 内存映射的页面。
- 用vmap/kmap将第二步得到的页面在内核重新映射,映射到一个内核地址,使内核可以访问。
- 内核通过重新映射的内核地址确认特征数据是否match用户态写的内容。如果不符合,则说明cache一致性在当前架构下是可见的。
![](https://i-blog.csdnimg.cn/blog_migrate/f598b426f5fdcd55b8d544e446580e8c.png)
x86测试:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/blkdev.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <asm/cacheflush.h>
#define MISC_NAME "miscdriver"
static int temp_data = 0;
static int misc_open(struct inode *inode, struct file *file)
{
printk("misc_open.\n");
return 0;
}
static void page_count_output(struct page** page, int cnt)
{
int i;
for(i = 0; i < cnt; i ++)
{
printk("%s line %d, page count %d, page map count %d.\n", __func__, __LINE__, page_count(page[i]), page_mapcount(page[i]));
}
}
int dump_memory(unsigned int *p, int len)
{
int i = 0;
for(i = 0; i < len; i ++)
{
if(i % 16 == 0)
printk("\n 0x%px:", p + i);
printk("0x%08x ", p[i]);
}
printk("\n");
return 0;
}
static long misc_ioctl( struct file *file, unsigned