OpenRisc-40-or1200的MMU模块分析
引言
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页,也就是0x801000。所以其地址空间是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分配的虚拟地址为0x2001,物理地址为:0x803001。
10》假设TLB的cache entry数量是64,映射方式是直接映射,也就是说通过VPN进行模运算就可以得到TLB的索引地址。
2>MMU的工作机制
有了上面的假设,那么MMU是如何工作的呢?
MMU的功能主要是虚实地址转换,PTE的cache(也就是TLB)。其具体过程,如下图所示:
其工作过程如下:
为了实现虚实地址转换,OS为每页创建了一个页表项,每个虚拟地址也分成了两部分,分别是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)寄存器),得到进程demo的页表的存放的开始地址(0x600000),然后得到对应的PTE的地址(0x600008)根据TLB的偏移0x2,查看对应的TLB的第2个cache line,如果匹配,则进一步和MR(mach register)中的VPN比较,如果也匹配,好,恭喜你,TLB hit,并将对应的PTE(pte_2)的PPN(0x803)和INDEX(0x001)组合成物理地址,传给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的地址
2》从PTE中得到PPN
3》PPN与INDEX组合,得到物理地址
4》从上面的分析,可以看出,CPU对PTE的访问是非常频繁的,为了加快速度,将部分PTE放到cache里面,这个cache就是TLB。
3,or1200的MMU模块
对MMU的工作机制了解清楚之后,再来分析or1200的MMU的实现,我想就会容易一些吧。
IMMU和DMMU机制类似,这里只分析DMMU。
1>DMMU的参数
通过or1200_define.v中,关于DMMU的配置如下:
从中我们可以看出:
1》MR(匹配寄存器),TR(转换寄存器)各个域含义的定义:
2》页大小是8KB(13bit)
3》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]
2>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来完成。感兴趣可以直接参考linux的mmu的部分代码。
5,参考文献
1,ORPSoC RTL code