MMU简介1.概念MMU是MemoryManagementUnit的缩写,中文名是内存管理单元,它是中央处理器(CPU)中用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权。虚拟地址映射为物理地址内存访问授权另一个概念:内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。2.术语虚拟地址空间——又称逻辑地址空间,CPU看到的使用的地址空间。32位CPU可以使用0~0xFFFFFFFF(4G)的地址空间。物理地址空间——实际的内存空间,例如一个256M的内存,实际地址空间也就是0~0x0FFFFFFF(256M)。VA——virtualaddress,虚拟地址PA——physicaladdress,物理地址3.背景内存有限内存保护内存碎片1)内存有限——交换程序很大,而内存不够一次装入太大的程序。在磁盘中分配虚拟内存。进程在内存中执行,可以暂时从内存中交换(swap)出去到备份存储上,当需要时再调回到内存中。2)内存保护不同进程使用不同的物理地址空间,必须防止不同进程使用同一块内存产生冲突。内存保护最基本的思路是操作系统为不同进程分配不同的地址空间。可以是逻辑地址,也可以是实际物理地址。(但如果是实际物理地址,操作系统还要负责实际物理地址的分配调度,任务过重,因此设计MMU减轻操作系统负担,负责逻辑地址到物理地址的映射。)3)内存碎片最初操作系统的内存是连续分配的,即进程A需要某一大小的内存空间,如200KB,操作系统就需要找出一块至少200KB的连续内存空间给进程A。随着系统的运行,进程终止时它将释放内存,该内存可以被操作系统分配给输入队列里的其他等待内存资源的进程。可以想象,随着进程内存的分配和释放,最初的一大块连续内存空间被分成许多小片段,即使总的可用空间足够,但不再连续,因此产生内存碎片。一个办法是不再对内存空间进行连续分配。这样只要有物理内存就可以为进程进行分配。而实际上,不进行连续分配只是相对的,因为完全这样做的代价太大。现实中,往往定出一个最小的内存单元,内存分配是这最小单元的组合,单元内的地址是连续的,但各个单元不一定连续。这样的内存小单元有页和段。当然,分段和分页也会产生碎片,但理论上每个碎片的大小不超过内存单元的大小。另外,操作系统分配给进程的逻辑地址就可以是连续的,通过MMU的映射,就可以充分利用不连续的物理内存。4.MMU原理分页页表查找(映射)以S3C2440的MMU为例,(ARM920T内核,三星产)MVA——modifiedvirtualaddress,变换后的虚拟地址。一般是在虚拟地址上再标记上进程号。4.1分页机制虚拟地址最终需要转换为物理地址才能读写实际的数据,通过将虚拟地址空间和物理空间划分为同样大小的空间(段或页),然后两个空间建立映射关系。在虚拟地址空间上划分的空间单元称为页(page,较大的则称为段section)。在物理地址空间上划分的同样大小的空间单元称为页帧(pageframe,或叫页框)。Linux上地址空间单元大小为4k。由于虚拟地址空间远大于物理地址,可能多块虚拟地址空间映射到同一块物理地址空间,或者有些虚拟地址空间没有映射到具体的物理地址空间上去(使用到时再映射)。S3C2440最多会用到两级页表,以段(Section,1M)的方式进行转换时只用到一级页表,以页(Page)的方式进行转换时用到两级页表。页的大小有3种:大页(64KB),小页(4KB),极小页(1KB)。MVA,32位。TTBbase代表一级页表的地址将它写入协处理器CP15的寄存器C2(称为页表基址寄存器)即可。一级页表的地址使用[31:14]存储页表基址,[13:0]为0。一级页表使用4096个描述符来表示4GB空间,每个描述符对应1MB的虚拟地址,存储它对应的1MB物理空间的起始地址,或者存储下一级页表的地址。每个描述符占4个字节,格式如下:使用MVA[31:20]来索引一级页表(20-31一共12位,2^12=4096,所以是4096个描述符)。4.2转换机制①页表基址寄存器位[31:14]和MVA[31:20]组成一个低两位为0的32位地址,MMU利用这个地址找到段描述符。②取出段描述符的位[31:20](段基址,sectionbaseaddress),它和MVA[19:0]组成一个32位的物理地址(这就是MVA对应的PA)4.3TLB从MVA到PA的转换需要访问多次内存,大大降低了CPU的性能,有没有办法改进呢?程序执行过程中,用到的指令和数据的地址往往集中在一个很小的范围内,其中的地址、数据经常使用,这是程序访问的局部性。由此,通过使用一个高速、容量相对较小的存储器来存储近期用到的页表条目(段、大页、小页、极小页描述符),避免每次地址转换都到主存中查找,这样就大幅提高性能。这个存储器用来帮助快速地进行地址转换,成为转译查找缓存(TranslationLookasideBuffers,TLB)当CPU发出一个虚拟地址时,MMU首先访问TLB。如果TLB中含有能转换这个虚拟地址的描述符,则直接利用此描述符进行地址转换和权限检查,否则MMU访问页表找到描述符后再进行地址转换和权限检查,并将这个描述符填入TLB中,下次再使用这个虚拟地址时就直接使用TLB用的描述符。若转换成功,则称为命中。Linux系统中,目前的命中率高达90%以上,使分页机制带来的性能损失降低到了可接收的程度。若在TLB中进行查表转换失败,则退缩为一般的地址变换,概率小于10%。5.Linux内存管理补充概念逻辑地址线性地址物理地址5.1逻辑地址转线性地址在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节)。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index)。Linux中逻辑地址等于线性地址。为什么这么说呢?因为Linux所有的段(用户代码段、用户数据段、内核代码段、内核数据段)的线性地址都是从0x00000000开始,长度4G,这样线性地址=逻辑地址+0x00000000,也就是说逻辑地址等于线性地址了。Linux主要以分页的方式实现内存管理。5.2线性地址转物理地址在保护模式下,控制寄存器CR0的最高位PG位控制着分页管理机制是否生效,如果PG=1,分页机制生效,需通过页表查找才能把线性地址转换物理地址。如果PG=0,则分页机制无效,线性地址就直接做为物理地址。分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页(page),每页包含4k字节的地址空间。这样每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表(pagetable)。注意,为了实现每个任务的平坦的虚拟内存,每个任务都有自己的页目录表和页表。为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。32位的线性地址被分成3个部分:最高10位Directory页目录表偏移量,中间10位Table是页表偏移量,最低12位Offset是物理页内的字节偏移量。页目录表的大小为4k(刚好是一个页的大小),包含1024项,每个项4字节(32位),项目里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。页表的大小也是4k,同样包含1024项,每个项4字节,内容为最终物理页的物理内存起始地址。每个活动的任务,必须要先分配给它一个页目录表,并把页目录表的物理地址存入cr3寄存器。页表可以提前分配好,也可以在用到的时候再分配。还是以mov0x80495b0,%eax中的地址为例分析一下线性地址转物理地址的过程。假设要转换的线性地址就是0x80495b0。转换的过程是由CPU自动完成的(应该是通过MMU),Linux所要做的就是准备好转换所需的页目录表和页表(假设已经准备好,给页目录表和页表分配物理内存的过程很复杂,后面再分析)。内核先将当前任务的页目录表的物理地址填入cr3寄存器。线性地址0x80495b0转换成二进制后是00001000000001001001010110110000,最高10位0000100000的十进制是32,CPU查看页目录表第32项,里面存放的是页表的物理地址。线性地址中间10位0001001001的十进制是73,页表的第73项存储的是最终物理页的物理起始地址。物理页基地址加上线性地址中最低12位的偏移量,CPU就找到了线性地址最终对应的物理内存单元。我们知道Linux中用户进程线性地址能寻址的范围是0~3G,那么是不是需要提前先把这3G虚拟内存的页表都建立好呢?一般情况下,物理内存是远远小于3G的,加上同时有很多进程都在运行,根本无法给每个进程提前建立3G的线性地址页表。Linux利用CPU的一个机制解决了这个问题。进程创建后我们可以给页目录表的表项值都填0,CPU在查找页表时,如果表项的内容为0,则会引发一个缺页异常,进程暂停执行,Linux内核这时候可以通过一系列复杂的算法给分配一个物理页,并把物理页的地址填入表项中,进程再恢复执行。当然进程在这个过程中是被蒙蔽的,它自己的感觉还是正常访问到了物理内存。5.3物理内存管理(页管理)Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数个4k(在i386体系结构中)大小的页,从而分配和回收内存的基本单位便是内存页了。利用分页管理有助于灵活分配内存地址,因为分配时不必要求必须有大块的连续内存,系统可以东一页、西一页的凑出所需要的内存供进程使用。虽然如此,但是实际上系统使用内存时还是倾向于分配连续的内存块,因为分配连续内存时,页表不需要更改,因此能降低TLB的刷新率(频繁刷新会在很大程度上降低访问速度)。内核分配物理页面时为了尽量减少不连续情况,采用了“伙伴”关系(Buddy算法)来管理空闲页面。设置了一个mem_map[]数组管理内存页面page,其在系统初始化时由free_area_init()函数创建。数组元素是一个个page结构体,每个page结构体对应一个物理页面typedefstructpage{structpage*next;structpage*prev;structinode*inode;unsignedlongoffset;structpage*next_hash;atomic_tcount;unsignedflags;unsigneddirty,age;structwait_queue*wait;structbuffer_head*buffers;unsignedlongswap_unlock_entry;unsignedlongmap_nr;}mem_map_t;把内存中所有页面按照2n划分,其中n=0~5,每个内存空间按1个页面、2个页面、4个页面、8个页面、16个页面、32个页面进行六次划分。划分后形成了大小不等的存储块,称为页面块,简称页块。包含1个页面的页块称为1页块,包含2个页面的称为2页块,依此类推。每种页块按前后顺序两两结合成一对Buddy“伙伴”。系统按照Buddy关系把具有相同大小的空闲页面块组成页块组,即1页块组、2页块组……32页块组。每个页块组用一个双向循环链表进行管理,共有6个链表,分别为1、2、4、8、16、32页块链表,分别挂到free_area[]数组上。位图数组标记内存页面使用情况,第0组每一位表示单个页面使用情况,1表示使用,0表示空闲,第2组每一位表示比邻的两个页面的使用情况,依次类推。当一对Buddy的两个页面块中有一个是空闲的,而另一个全部或部分被占用时,该位置1。两个页面块都是空闲,或都被全部或部分占用时,对应位置0。内存分配和释放过程内存分配时,系统按照Buddy算法,根据请