分类: LINUX
2011-05-24 16:46:32
谨以此文纪念过往的岁月
一.前言
在嵌入式中cache的作用很重要,其用于加速数据和指令的获取,但是其也有一定的麻烦就是内存中数据改变而cache中的数据并没有改变,这就发生了传说中的不一致性。对于我们初学者而言对这个概念比较迷茫,本文就来看看cache是什么,cache到底是怎么工作的以及其的一些机制。是了解cache的很好网站。
二.cache之义
在现代CPU的主频能够达到几百兆,而主存的的存储周期仍然只有10~100ms,如果所有的数据以及指令都存放在主存中,其会大大制约系统的速度。所以cache和buffer就出现了,这两个比主存更加靠近CPU,其存取的速度远远大于主存。Cache就是负责了数据和指令的暂时性存储,用于CPU的加速。
三.cache的工作原理以及
cache的使用其实很简单,CPU发出一主存地址时,先会访问cache,如果cache中有该地址的数据(该数据不仅指数据还指指令,我们默认采用数据和指令统一cache),则从cache中获取数据,如果没有的话,就从主存中读取数据同时将数据以及其后的块(cache line)的数据搬运到cache中留给下次使用,因为cache是以cache line为单位与主存进行数据交换的。如果cache中的被填满了,则cache会采用一定的策略将部分数据替换出,很简单吧。
在cache中主要是要理解cache的地址转换原理,就是CPU发出的主存地址如何转换层cache的索引。
1. 全相联映像方式
主存的任意一块可以映射到cache中的任一块。如果cache的块数为C,而主存的块数为M,则映射关系有C*M种。如果采用目录索引的方式来存放映射关系,而目录表的容量为C。就是用C个index来保存每一个cache line中所对应的主存的cache line。
2. 直接映像方式
主存的任意一块只能映射到cache的特定一块。其映射公式如:
b = B mod C(b cache中块号,B 主存中的块号,C cache中块容量)
3. 组相联映像方式
该方式是上述两种办法的联合。把主存和cache按同样的大小划分成set,每一个set有相同的块数。组与组之间采用直接映像,而两个对应的组内部采用全相联映像方式。在ARM中采用该方式。以cache的块大小为2(L),则同一块的各地址的的位[31:L]是相同的,如果cache中组的大小位2(S),则虚拟地址的位[L+S-1,L]用于选择cache中的某个组,虚拟地址中其他位[31:L+S]则包含一些标志位。
四.存储的一致性
也许对于我们新手而言,cache的一致性比较难以理解,怎么会出现CPU读取的数据or指令与主存中的值不同。对于没有cache和write buffer是不可能出现的,因为所有数据的存储和读取只有一个地方主存,而在cache的系统中就不一样了,因为CPU往往会读取cache中的数值,而非主存中的数值,在主存的数据发生改变而cache所对应的数值并没有发生改变,这时候就发生了数据不一致性的情况。
不一致性可能的三种情况:
1. 虚拟地址和物理地址的映射发生改变。
2. 独立的数据和指令cache容易出现数据和指令的cache不一致性。
3. DMA导致的不一致性。
五.arm1176的cache地址转换
在地址转换中比较难以理解的是tag和index。下面的图形会让我们更好的理解前面的两个概念。
先来理解VIPT和VIVT的概念,vipt值virtually index physically tag指index的值是从虚拟地址中抽取,而tag的索引从物理地址中抽取,而vivt同理。也许干巴巴的说明很难理解,那就用模拟的C语言来说明估计会好理解很多。
以16K D cache, 4-ways ,cache line 32bytes为例。先假设主存物理地址0x50000000~0x50004000这段地址内容在cache中,并且一一对应。
Unsigned long tag_index[4][128]={
{ 0x50000040, 0x50000060, 0x50000080,…},
{0x50001000,0x50001020, 0x50001040, 0x50001060, 0x50001080,…},
{0x50002000,0x50002020, 0x50002040, 0x50002060, 0x50002080,…}
{0x50003000,0x50003020, 0x50003040, 0x50003060, 0x50003080,…}
};
Unsigned char data[4][1024][4] ={
{{0,1,2,3},{1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7},{5,6,7,8}…},
{{0,1,2,3},{1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7},{5,6,7,8}…},
{{0,1,2,3},{1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7},{5,6,7,8}…},
{{0,1,2,3},{1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7},{5,6,7,8}…},
}
上述数组中的每一个值代表了tag sram中的每一个tag。4代表set,128 = 16K/4/32bytes即line number。我们以虚拟地址0xC0000023为例,其物理地址为0x50000023。
其对应的byte=0xC0000023&0x1F = 0x3 ,index=(0xC0000023&0xFE0)>>5 = 1,tag =(0x50000023& 0xFFF)>>12 = 0x50000
那cache是如何索引的呢,其传输首先是0xC0000023这个虚拟地址计算其index = 1,则在4个set中查找index为1的tag这四个tag分别为0x50000020,0x50001020,0x50002020, 0x50003020,同时该虚拟地址会送入MMU将该虚拟地址转换层物理地址为0x50000023,在将物理地址计算出来后,将物理地址的tag位(bit[31:12])与上述tag进行比较,得出该物理地址所对应的set为0,即tag_index[0][1]满足该虚拟地址。之后CPU则会获取data[0][0xC0000023&0xFFF]处得数据。也许有人会疑惑为什么不全用vaddr来实现转换。这个就涉及了一个aliasing的问题,当一个虚拟地址对应多个物理地址时,就会出现问题了。为什么,嘿嘿用脚趾头想吧!
六.cache操作
以flush_cache_all为例来开始我们追寻cache操作之路。
#define flush_cache_all() __cpuc_flush_kern_all()
关于上面的宏定义最后是那个函数的执行,我们不看了,我直接说吧,是cache-v6.S中的
ENTRY(v6_flush_kern_cache_all)
mov r0, #0
mcr p15, 0, r0, c7, c14, 0 @ D cache clean+invalidate
mcr p15, 0, r0, c7, c5, 0 @ I+BTB cache invalidate
mov pc, lr
上面的函数很简单,但是也不简单,为什么呢,这里有一个CP15协处理器,其主用主要是协助CPU存储管理。没有办法,CPU也不是万能的,俗话还说一个好汉三个帮啊。这里面我们也不扩展开,只看关于cache的内容。关于mcr和mrc的的使用请看http://blog.chinaunix.net/space.php?uid=24517893&do=blog&id=253685 ,此处不多加赘述。
对于cache的操作主要是flush_cache_all和flush_dcache_page前者将cache清空,后者则是将某一页的数据写会主存。