请选择 进入手机版 | 继续访问电脑版
搜索

151

主题

508

帖子

3567

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
3567
发表于 2014-9-29 21:14:13 8000 浏览 0 回复

浅谈Cache和MMU

    写了点自己对Cache的理解,发出来希望能和大家交流下。
Cache一词来源于法语,其原意是“藏匿处,隐秘的地方”,而自从被应用于计算机科学之后,就已经成为了英语中的一个计算机体系结构专有名词。
Sun Microsystems的前首席科学家Billy Joy,作为BSD unix,csh,vi,NFS,java,TCP/IP等的发明者,他曾经说过,在计算机科学领域,如果没有了cache的发明,其他的一切发明都将失去意义。而正是他,将基于分页的虚拟内存系统引入了Unix,影响了之后所有的新操作系统开发。



   Cache的出现是为了解决CPU日益增长的核心时钟频率以及系统主内存日益落后的素质之间的矛盾。
Cache的基本原理是局部性原理,局部性原理简单说起来就是在前一段时间经常用到的代码,在将来会被再次使用的几率也很高。
我们可以想象倒水这件事,有些水桶只有一个小小的进水口,可是这样会给倒水的人造成烦恼,拿着瓢往里倒水的时候要很仔细地对准,并且还要让水流不太急以避免超出进水口的接受能力。小心地倒完一瓢再去舀一瓢,周而复始。
于是有人说,要解决这个问题!就有聪明人发明了漏斗,上面的口很大,并且能够容纳一部分液体,下面的口较小保证流出的水不超出水桶口的范围。这样就可以舀一瓢水,“哗”地倒进漏斗里,歇会儿等水流差不多了再舀一瓢倒进去,这样即简化了对倒水人的技能要求(要不就得跟卖油翁一样),又提高了效率,可以让操作者节省出来时间去做其他的事情。


Cache对CPU的意义是类似的,主存就是那个带有瓶颈的水桶,如果cpu始终要等待对内存的操作完成然后再进行下一个环节,那么在所有跟内存操作相关的指令执行时,CPU就自动降为接近内存的速度。也许你会认为很多指令是寄存器指令,内存读写在整个程序中所占的比例并不大,但是请不要忘记很恐怖的一点是,所有的指令都在内存中,也就是说每执行一条指令都要去读内存。有了这个前提,我们可以想象,在程序执行的时候,cpu会随时随刻地从内存中读取代码,整条总线会被cpu占住,这样的话总线上的其他master(比如说DMA控制器,I2C-Slave等)就将全部受到影响,由此可见没有Cache的结果就将是灾难性的。有些cache还会跟读写fifo紧密配合,不过目的都是一致的,解决内存瓶颈问题。有人可能会有疑问,多数ARM7内核的芯片是没有cache的,为什么依然能够运行得很快,这是因为IC designer在设计总线结构时动了手脚,将CPU取指的总线同其他部分总线隔离,以避免CPU对其他总线设备的干扰。



Cache按写操作模式来分又分为写穿(write through)和写回(write back)两种,由于写穿模式的效率要比写回模式低很多,所以目前绝大多数应用中都是采用的写回模式。

按用途来说,cache分为ICache和DCache,分别作为指令和数据缓存,在某些老的架构中,指令和数据混用一个cache。
Cache的最小操作单位是行(line),也就是说每次cache的操作必须是以32个字节为单位,不能只选择其中某个字节。而且这32个字节数据的首地址必须是32字节对齐的。每一行的大小并不是一定的,在其发展历史中曾经有过很多种选择,目前比较通用的一个选择是每行32个字节,对于32位risc来说就是4个word。

Cache的每一行有tag和index域用来寻址,这个tag和index的功用会在后面详细介绍。


按寻址方式分,cache可以分为全相联映射,直接映射和多路组相联映射cache,所谓的全相联映射指的是任意一行地址中的数据可以放到任意一个cache line中,cpu都可以根据tag和index准确地寻址到。
举个例子来说明这种方式,比如说从地址0x3100 0000到地址0x3100 001F的这32个字节的数据,如果想做到它可以放到任一行中,那么我们就必须得保证针对于任一行数据,都要提供给它唯一的一个标识符,也就是说在32位系统中,由于地址是32位的,行本身是32个字节,表示行内偏移占用了5位,那么必须得提供27位的标识符(针对本例,这个标识符就得是0x188000),才能保证任一行数据可以放置到任一cache行中,并能够被正确寻址。
而全相联cache的问题不仅仅是需要硬件连线比较多,而且它的效率比较低,在极端情况下可能找一个数据需要寻址整个cache。


直接映射Cache
为了简化cache的设计并提高效率,提出了直接映射的概念,直接映射cache中,对于任一地址,它在cache中有且只有一个line会与之匹配,并且这个line的index是固定的,大大简化了cpu对cache的操作和寻址流程。
        
还是拿上面那个地址为例,假设cache是4k bytes大小的,每一行还是32 bytes,那么0x3100 0000这个地址就肯定出现在cache的第一行(或者说index为0),0x XXXX X020这个地址出现在cache的第二行(index为1), 0x XXXX Xfff这个地址就一定出现在cache的第128行(index为127),这样的话对于任一地址,cpu只需到相应的行上去坚持tag是否符合来决定是hit还是miss。


是这种直接映射的方式也出现了另外一个问题,我们写一个简单的程序来说明(假设CPU内核对读写内存的策略一样,都要经过cache):
Int demo_copy(int* dst, int* src, uint length)
{
    int i = 0;
   
    for(i = length; i > 0; i --)
{
*dst = * src;
    dst ++;
    src ++;
}
}
而如果dst和src的差恰好是4k的整数倍(假设cache是4k大小),而这个拷贝的动作实际上是首先将src的数据load进来,然后再store进dst里面去,而他们的在cache中的index是一样的,就导致了cache行的频繁失效,极大降低了系统的效率。有些人可能认为这只是一种特例,但是随着系统越来越庞大,指令和数据发生这种巧合的可能性大大增加,于是必须在设计中考虑对这种情况的处理。

多路组相联cache的出现就是为了应对这种状况,多路组相联的概念就是将多个并行的直接映射cache集成到一起,这样对于一个index就会有多个行与之相对应,比较每行的tag是否与想要的地址相符合,这样就会大大增加命中的几率,避免了一小段程序中频繁cache失效的问题。
即使是多路组相联的cache也面临着替换的问题,随着系统运行时间的增加,所有的cache行都会被占满,这时再有新地址的数据请求进来,就要将已有的数据替换出去,而将哪一组的相应行替换出去又成为了一个问题,过去采用的方式有轮转替换,随机替换等,目前较多采用的方式被称为LRU(least recently used),也就是将最近最少使用的行替换掉,这也是对局部性原理的一种应用。


按照工作原理来分,cache又有physical index physical tagged, virtual index virtual tagged, physical index virtual tagged等几种分法,我们来一一了解一下。
Physical index physical tagged是一种最容易理解的操作方式,cache针对物理地址进行操作,简单粗暴,而且不会有歧义。但是这种方式的缺陷也很明显,在多进程操作系统中,每个进程拥有自己独立的地址空间,指令和代码都是以虚拟地址的方式存在,cpu发出的memory access的指令都是以虚拟地址的方式发出,这样的话,对于每一个memory access的操作,都要先等待MMU将虚拟地址翻译为物理地址,这是一种串行的方式,效率较低。

Virtual index virtual tagged是纯粹用虚拟地址来寻址,这种方式带来了更多的问题,每一行数据在原有tag的基础上都要将进程标识加上以区分多个进程之间的相同地址,而在处理共享内存时也比较麻烦,共享内存在不同的进程中的虚拟地址不相同,如何同步是个问题。

现在比较多的是采用virtual index physical tagged的方式,virtual index的含义是当cpu发出一个地址请求之后,低位地址去和cache中的index匹配, physical tagged是指虚拟地址的高位地址去和mmu中的页表匹配以拿到物理地址(index和取物理地址这两个过程是并行的),然后用从mmu中取到的物理地址作为tag(或者tag的一部分)去和cache line的tag位匹配,这样既保证了同一地址在cache中的唯一性(有个例外,cache alias)又能将mmu和cache并行工作,提高了效率。这种方式带来的唯一问题就是cache alias,cache alias主要发生在cache大小同内存页框大小不一致时,比如说每路cache的大小是8k,而现在较为常用的页框大小是4k,这两个数字说明的问题分别是这样的:
页框大小为4k意味着mmu在划分虚拟空间时是以4k为粒度,每一个4k对齐的内存区域作为内存分配的最小单位,于是就有了这样的结果,虚拟地址和物理地址的低12位是相同的,因为低12位地址是作为其在4k页框内的偏移,不管它的虚拟地址是什么,它在4k页面内的偏移是一定的。
Cache大小为8k,意味着cache index是用依据13位地址来运算的,而如果以虚拟地址来寻址cache,低13位中就包含了一位虚拟地址,所以这种方式称为virtual index。问题出现了,一个物理地址当它被不同进程使用时,由于mmu分页机制仅能保证其低12位相同,那么第13位很有可能是不同的,这样就造成了一个物理地址在cache中有两个副本,可能会造成同步问题。
针对于cache alias问题,目前的方案是由操作系统来保证,对于同一物理地址在不同进程空间的虚拟地址,他们的虚拟地址的差一定是cache大小的整数倍,也就是说他们的第13位一定是相同的。同时已经有些cpu厂商在开发监视模块,试图在硬件层面解决类似的同步问题。

2.        MMU
MMU的原理和操作相对来说就要比cache直观很多,ARM的MMU和Cache的关系比较特殊,在ARM中如果想要使能cache,必须先使能MMU,没有道理可讲,就是这么规定的。
MMU的意义就是允许程序运行在自己独享的地址空间中,这样就提供了程序动态加载到系统中的可能。
加载一个程序,只需要kernel为它分配相应的资源并在系统中注册之后,这个程序就可以在自己独立的地址空间中运行,不会干扰到其他程序。
而在没有MMU的系统中,每段代码都是要和其他代码共享地址空间,多数的程序都要同内核一起编译,一切内容在编译的时刻就已经决定,大大降低了软件的可延展性。即使有些技术可以实现一定程度上的动态加载,但是这些技术本身也存在着很大的局限,并且显得很诡异。

当CPU发出一个虚拟地址之后,这个地址会同当前的进程id一起交给MMU硬件,MMU会在所有的TLB条目中查询是否有相应的地址映射关系存在,如果miss的话就会激起一个异常,由异常的处理软件在事前分配好的table中查找与此地址相匹配的条目,并决定将哪一条TLB条目替换掉。这样就完成了一次虚拟地址到物理地址的转换。

TLB:Translation lookaside buffer,即旁路转换缓冲,或称为页表缓冲。又称为快表技术。由于“页表”存储在主存储器中,查询页表所付出的代价很大,由此产生了TLB。这也应该算是cache的一种吧。

而至于页表放到哪里,如何查找填充这些问题则在不同架构上有不同的选择,MIPS上实现的是软件填充,而在ARM和X86架构中,实现的方式变成了硬件填充。

回复

使用道具 举报

返回列表
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

登录或注册

官方客服

QQ:2551456065

官方QQ群

195631883

扫一扫关注迅为公众号

群号652692981

 
快速回复 返回顶部 返回列表