引言
MMU(memory management unit),无论对于computer architecture designer还是OS designer,都是至关重要的部分,设计和使用的好坏,对性能影响比较大。
MMU,我觉得是硬件和软件配合最密切的部分之一,对于RISC CPU而言,更是这样。
前面,我们对or1200的整体memory hierarchy做了简单分析,了解了cache的映射方式,替换策略,写策略,以及cache的优化等等背景知识,并对or1200的具体实现做了分析。在现实中,cache往往和MMU紧密合作,完成CPU的访存操作。本小节就来分析一下or1200的MMU模块。
1,MMU产生原因
研究一个东西,首先要了解其来龙去脉,MMU也不例外,我们在分析MMU的工作机制之前先介绍一下MMU的产生原因。
当时,主要由两方面的因素导致了MMU的产生:
a,to allow efficient and safe sharing of memory among multiple programs,
b,and to remove the programming burdens of a small, limited amount of main memory.
说明
a,从安全角度出发,确保多进程程序在执行时相互不影响。
b,从程序员的角度出发,采用MMU可以让程序员在编程时少受内容容量的限制。
现在而言,第一个原因占主要。
2,MMU的工作机制
在分析or1200的MMU实现之前,我们有必要先了解MMU的工作机制。
为了更清晰的了解MMU的工作过程,我假设了一个具体的例子,通过这个例子来说明其详细的工作步骤。
1>实例的环境假设
比如,我们编写了一个简单的应用层程序:
/*demo process, base on OS*/ int main() { int test; test = 0x12345678; }
1》假设其进程名称为demo。
2》假设片外SDRAM(内存)大小为32MB。其中内核空间16MB,用户空间16MB。
3》假设OS的内存管理方式是单级页式管理(还可能是段式,或页段式),虚拟地址和物理地址均为32-bit。
既然ps是8KB,也就是说最多需要的PTE的数量是:pte_num=32MB/8KB=4K。假设每个PTE是4Bytes,那么存放这些PTE一共需要的内存大小是:4Bytes X 4K=16KB。
4》假设OS在执行进程demo之前,给她分配的地址空间大小为6页(page size = 8KB),也就是48K。这48KB是连续的,其起始物理地址第0x801页,也就是{19'h801,13'h08}。所以其地址空间是0x801页,0x802页,0x803页,0x804页,0x805页,0x806页,共6页。
既然其进程空间是48KB,每页是8K,也就是说OS需要给demo进程生成6个PTE(page table entry,页表项)。
5》假设这6个PTE存放在kernel空间的第0x600个页,即进程demo的PTE存放开始物理地址是0x600000,虚拟地址假设是0x12345000。
7》假设这进程这6页的地址空间的分配方式是:
代码段,1页;
bss段,1页;
栈,1页;
堆,1页;
数据段,2页。
8》假设进程demo中的变量test的地址在栈段,并且其页内偏移为0x1。
9》假设OS给进程demo中变量test分配的虚拟地址为0x2008,物理地址为:0x1006008。
10》假设TLB的cache entry数量是64,映射方式是直接映射,也就是说通过VPN进行模运算就可以得到TLB的索引地址。
2>MMU的工作机制
有了上面的假设,那么MMU是如何工作的呢?
MMU的功能主要是虚实地址转换,PTE的cache(也就是TLB)。其具体过程,如下图所示:
其工作过程如下:
为了实现虚实地址转换,OS为每页创建了一个页表项(PTE,page table entry,由PPN域和管理域组成),每个虚拟地址也分成了两部分,分别是VPN和INDEX,通过VPN的低6位(因为TLBcache是64 entry)定位到TLB的偏移量。
如果相应的偏移量处TLB hit,那么就可以直接得到对应的PTE,有了PTE,我们就可以得到PTE中的PPN,PPN和INDEX组合在一起,就是物理地址。
如果相应的偏移量处TLB miss或者页面异常,那么MMU产生一个TLB miss或者page fault异常,交给OS完成异常的处理(TLB的更新,和其它操作)。
对于本演示例子来说,变量test的虚拟地址是0x2001,可见其TLB entry偏移量是0x2,页内偏移量是0x1;VPN是0x2。那么,MMU是如何将这个虚拟地址换换成物理地址的呢?
首先,OS会根据进程demo的进程号(PID,OR也有CID(context ID)保存,DTLBWMR中的CID域),和DMMUCR(DMMU 控制寄存器)中的PTBP(page table base pointer),得到进程demo的页表的存放的开始地址(0x600000),然后得到对应的PTE的地址(0x600008)根据TLB的偏移0x2,查看对应的TLB的第2个cache line,如果匹配,则进一步和MR(mach register)中的VPN比较,如果也匹配,好,恭喜你,TLB hit,并将对应的PTE(pte_2)的PPN(0x803)和INDEX(13'b0_0000_0000_1000)组合成物理地址(0x1006008),传给qmem模块,sb模块,biu模块,经dbus_arbiter,memory controller,最终实现读写SDRAM的对应地址。
如果TLB miss(对应的pte_2),那么OS查看异常寄存器,得到具体的异常信息,并最终将pte_2更新到TLB中,重新执行MMU操作,则TLB hit,完成转换过程。
上面的过程,如果用一副图来展示的话,如下所示:
3>关于多级页表
上面介绍的OS的页表是单级的,这样的话,在搜索对应的PTE时需要依次遍历所有的PTE表项,显然比较慢,为了加快搜索速度,linux采用了两级PTE页表。
其基本思想是将所有的PTE进行分组,每一组由一个PTD(PT directory,我自己给起的名字),每个PTD项对应一组PT。
这样,在搜索时,先确定其所在的页表目录,然后只需要遍历本目录中的PTE就可以了。
其操作过程和单级页表相似,如下图所示:
4>小结
上面通过一个例子,说明了MMU的工作机制,大体可概括如下:
1》根据进程ID和VPN得到存放对应页表项的PTE的地址,主要使用CID和PTBP得到页表目录或页表的存放的开始地址。并根据VPN得到具体页表项的地址,从而获得对应的页表项(PTE)。
2》从PTE中得到PPN
3》PPN与INDEX组合,得到物理地址
4》从上面的分析,可以看出,CPU对PTE的访问是非常频繁的,为了加快速度,将部分PTE放到cache里面,这个cache就是TLB。TLB 这个cache的映射方式一般采用direct mapped,而不是fully associative,和set associative。因为TLB一般都很小,在http://blog.csdn.net/rill_zhen/article/details/9491095 这个章节我们提到如果cache很小的时候才用直接映射,延迟小,电路简单。
3,or1200的MMU模块
对MMU的工作机制了解清楚之后,再来分析or1200的MMU的实现,我想就会容易一些吧。
IMMU和DMMU机制类似,这里只分析DMMU。
1>or1200的MMU的特点
a,支持32-bit/64-bit两种宽度的EA(effective address)。
b,物理地址最大支持35-bit,也就是32G的内存。
c,支持三种不同的页大小,32GB(只针对64-bit的EA),16MB,8KB。如果采用第一,二种,则虚实转换通过ATB(area translation buffer);如果采用8KB大小,则通过TLB(translation lookaside buffer)实现虚实转换。
d,最多可支持单级页表,两级页表,三级页表。
e,有强大的页保护机制。
f,支持SMT(simultaneous multi-thread),同时多线程。
2>or1200的MMU的工作机制
MMU,最重要的工作就是实现虚实地址转换,也就是EA到物理地址的转换,关于or1200中的MMU是如何实现转换的呢,如下图所示;
需要说明的是,对于ORPSoC,VMPS是13-bit,也就是page size是8KB。
3>or1200的MMU模块的异常
关于和MMU有关的异常,如下图所示:
4>DMMU的寄存器
通过or1200_define.v中,关于DMMU的配置如下:
从中我们可以看出:
1》MR(匹配寄存器),TR(转换寄存器)各个域含义的定义:
a,MR:mach register
V:valid
CID:context ID,跟linux中的PID相关。
RES:reserved
VPN:virtual page number,与EA匹配的虚拟页号。
b,TR:translate register
CC:cache coherence,表示本页是否强制缓存一致。
CI:cache inhibit,缓存是否禁止。表示本页是不是使用cache,咱们都知道or1200是有dcache的,这个标志就是显示这个8K的内存页是不是要使用dcache。
WBC:write back cache,cache的写策略是write through 还是write back。
WOM:weak ordered memory,允许访存排序(memory ordering)是现在CPU的一个特点。有强序和弱序之分,强序就是访存操作严格遵照指令顺序,典型的标量处理器都是强序的。当进入超标量过程中,为了提高性能,可以对访存进行优化,比如乱序操作,投机操作,预取操作等等,这样就会使得访存操作与指令顺序不严格对应,称之为弱序。弱序会导致数据的不一致,所以在超标量处理器和多核处理器中引入内存屏障指令解决这种不一致问题。更多可参考:
http://en.wikipedia.org/wiki/Memory_ordering#cite_note-table-4
和
手册的7.2章节,258页。
A:active,表示本页是否可以访问。
D:dirty,表示本页是否被修改过。
URE:user read enable,表示本页的访问权限。
UWE:user write enable,也是访问权限。
SRE:supervisor read enable,访问权限。
SWE:supervisor write enable,访问权限。
RES:reserved,保留位。
PPN:physical page number,物理页号,表示本页是内存中的第几个页。PPN和index(页内偏移),就可以确定内存中的某个字节的地址。
2》除了这两个寄存器之外,or1200还有对应的spr,主要有DMMUCFGR(配置寄存器)和DMMUCR(控制寄存器)。
a,关于配置寄存器DMMUCFGR,or1200的手册的319页有介绍,如下所示:
b,关于控制寄存器DMMUCR,手册的265页也有描述,如下所示:
c,最开始我们就说过,MMU的工作是软件和硬件协同工作的模块,所以,除了配置硬件使用的寄存器外,还有专门给OS提供的寄存器,上面介绍的DMMUTLBWMR(匹配寄存器)和DTLBWTRMMU(转换寄存器)就是其中的两个,其实除了这两个,还有很多,比如DMMUPR(data MMU protection register),DTLBEIR(data TLB entry invalid register),DATBMR0~DATBMR3(data area translation buffer mach register),DATBTR0~DATBTR3,DTLBW0MR0~DTLBW0MR127,DTLBW0R0~DTLBW0TR127。
从中我们可以看出:
or1200最多可有的TLB的通道数(NTW,number of TLB way)是4个,
每条通道的条目数(NTS,number of TLB set)是128个,
ATB条目数(NAE,number of ATB entry)是4个。
是否使用控制寄存器,配置寄存器,保护寄存器,TLB条目无效寄存器,以及是使用硬件来更新TLB(HTR,hardware TLB reload)还是软件更新TLB,等都是可以通过DMMUCFGR类配置。
配置完成后,都有对应的寄存器组供OS使用。
这些寄存器,也可以在手册中找到具体的描述,如下所示:
3》页大小是8KB(13bit)
4》TLB entry的数量是64(6bit)
////////////////////////////////////////////// // // Data MMU (DMMU) // // // Address that selects between TLB TR and MR // `define OR1200_DTLB_TM_ADDR 7 // // DTLBMR fields // `define OR1200_DTLBMR_V_BITS 0 `define OR1200_DTLBMR_CID_BITS 4:1 `define OR1200_DTLBMR_RES_BITS 11:5 `define OR1200_DTLBMR_VPN_BITS 31:13 // // DTLBTR fields // `define OR1200_DTLBTR_CC_BITS 0 `define OR1200_DTLBTR_CI_BITS 1 `define OR1200_DTLBTR_WBC_BITS 2 `define OR1200_DTLBTR_WOM_BITS 3 `define OR1200_DTLBTR_A_BITS 4 `define OR1200_DTLBTR_D_BITS 5 `define OR1200_DTLBTR_URE_BITS 6 `define OR1200_DTLBTR_UWE_BITS 7 `define OR1200_DTLBTR_SRE_BITS 8 `define OR1200_DTLBTR_SWE_BITS 9 `define OR1200_DTLBTR_RES_BITS 11:10 `define OR1200_DTLBTR_PPN_BITS 31:13 // // DTLB configuration // `define OR1200_DMMU_PS 13 // 13 for 8KB page size `define OR1200_DTLB_INDXW 6 // 6 for 64 entry DTLB 7 for 128 entries `define OR1200_DTLB_INDXL `OR1200_DMMU_PS // 13 13 `define OR1200_DTLB_INDXH `OR1200_DMMU_PS+`OR1200_DTLB_INDXW-1 // 18 19 `define OR1200_DTLB_INDX `OR1200_DTLB_INDXH:`OR1200_DTLB_INDXL // 18:13 19:13 `define OR1200_DTLB_TAGW 32-`OR1200_DTLB_INDXW-`OR1200_DMMU_PS // 13 12 `define OR1200_DTLB_TAGL `OR1200_DTLB_INDXH+1 // 19 20 `define OR1200_DTLB_TAG 31:`OR1200_DTLB_TAGL // 31:19 31:20 `define OR1200_DTLBMRW `OR1200_DTLB_TAGW+1 // +1 because of V bit `define OR1200_DTLBTRW 32-`OR1200_DMMU_PS+5 // +5 because of protection bits and CI // // Cache inhibit while DMMU is not enabled/implemented // // cache inhibited 0GB-4GB 1'b1 // cache inhibited 0GB-2GB !dcpu_adr_i[31] // cache inhibited 0GB-1GB 2GB-3GB !dcpu_adr_i[30] // cache inhibited 1GB-2GB 3GB-4GB dcpu_adr_i[30] // cache inhibited 2GB-4GB (default) dcpu_adr_i[31] // cached 0GB-4GB 1'b0 // `define OR1200_DMMU_CI dcpu_adr_i[31]
5>DMMU模块代码分析
DMMU部分主要有两个模块:dmmu_top和dmmu_tlb。
1》dmmu_top
这个是DMMU的顶层模块,这里只将核心代码列了出来:
// // Physical address is either translated virtual address or // simply equal when DMMU is disabled // assign qmemdmmu_adr_o = dmmu_en ? {dtlb_ppn, dcpu_adr_i[`OR1200_DMMU_PS-1:0]} : dcpu_adr_i;
从中可以看出最终的物理地址的组成部分,为PPN(19-bit)和虚拟地址的index(13-bit),共32bit。
如果DMMU禁止的话,DMMU则直接将虚拟地址传给qmem模块。
2》dmmu_tlb
这个是PTE的cache模块,主要一个MR和TR两个寄存器完成虚实转换。
// // Assign outputs from Match registers // assign {vpn, v} = tlb_mr_ram_out;
// // Assign outputs from Translate registers // assign {ppn, swe, sre, uwe, ure, ci} = tlb_tr_ram_out;
从中可以看出,PPN来源于DMMU的寄存器,这组寄存器由OS负责管理。
4,小结
本小节,我们对MMU的工作机制做了介绍,最后对or1200的DMMU的具体实现做了分析。这部分的代码还是很少的,所以说完成MMU的工作,or1200的硬件做的工作还是不多,主要靠OS来完成。可以这么说,or1200只是提供了一堆寄存器,剩下的工作就是kernel如何使用这一堆寄存器来完成MMU,从这个角度来看,RISC CPU崇尚‘简单’的精神,体现的淋漓尽致。所以要想真正弄懂MMU,还是要仔细看kernel的代码,感兴趣可以直接参考linux的mmu的部分代码,获得更详细的内容。
5,参考文献
1,ORPSoC RTL code
2,OpenRISC arch-manual-v1.0