【ARM学习笔记】实验四:内存管理单元MMU的虚拟地址映射实验

来源:互联网 发布:嵌入式软件开发培训 编辑:程序博客网 时间:2024/05/01 17:59

        上一篇介绍了MMU的由来与作用,现在我们以JZ2440v2开发板为例做一个MMU的虚拟地址映射实验。




一、有MMU参与的寻址过程简介


        首先需要了解3个总线地址概念:虚拟地址(VA,Virtual Address)、转换后的虚拟地址(MVA,Modified Virtual Address)、物理地址(PA,Physical Address)。

        JZ2440v2使用2片16bit位宽、32MB容量的SDRAM组合成“一片”32bit位宽、64MB容量的内存来使用,之所以说成是一片,是因为CPU只把它们当成一个整体,即单片32bit位宽、64MB容量的SDRAM,前文也介绍过。

        JZ2440v2将这2片内存接在存储管理器的BANK6和BANK7上,所以,对于64MB内存,它占用物理总线地址0x30000000~0x33FFFFFF,为什么是这个值,请参见[ARM嵌入式]三、S3C2440A的存储控制器及启动过程。

        除了存储控制器占用的物理总线地址0x000000000~x40000000以外,0x48000000~0x5FFFFFFF这段地址被各个寄存器使用,此外,使用Nand Flash启动时,片内SRAM占用0x400000000~x48000000这段物理总线地址,其余的物理总线地址均未被使用。

        关于存储控制器和内存管理单元的差别:存储控制器用于直接操控外设,它能根据总线地址在相应的外设上寻址和读写数据,它只使用物理地址PA;而内存管理单元的作用,仅仅是转换虚拟地址和物理地址以及权限控制,不参与外设操控,实际上还是要将转换好的物理地址PA传递给存储控制器。

        作为CPU核,它根本不关心到底接了什么外设,它只管把指令和地址应该扔给谁去处理,在没有启动MMU时,CPU使用物理地址PA,将相应的的物理地址PA交给存储控制器;启动MMU之后,CPU使用虚拟地址VA,在硬件上会自动转换成MVA交给MMU,MMU经过一系列处理转换成PA,然后根据权限,让存储控制器去操作。

        小小提示一下,CPU一上电,默认从0x00000000处开始执行指令,执行完第一条就会按顺序执行下一条指令,CPU可不管到底是VA还是PA,所以关于VA和PA,其实还是由程序员在编写程序就固定在指令里面的。

        关于转换后的虚拟地址(MVA,Modified Virtual Address),它是由硬件自动完成的,其转换关系类似于以下代码:

                if(VA<32M) Then

                        MVA = VA | (PID<<25)

                else

                        MVA = VA

        PID是进程编号,涉及到操作系统进程管理,不做太多说明,在学Linux时会学到,一般情况下,说到的都是MVA,MVA的作用是供cache和MMU使用,在MMU中被转换为PA。

        在MMU启动后,对于转换过程,主要有以下几个特点:

        (1)、CPU核看到的、用到的只是虚拟地址VA。

        (2)、VA在MMU处理之前就已经转换成MVA了,它是通过硬件自动完成的。

        (3)、caches和MMU是看不到VA的,它们直接利用MVA转换得到PA。

        (4)、实际设备看不到VA和MVA,读写它们时使用的是物理地址PA。




二、段映射的寻址过程


        段映射比较简单,本实验也是以段映射为目标进行研究。

        前文曾介绍过段映射,段映射将虚拟空间划分成4096个单元,每个单元占用1MB,每个虚拟空间单元对应物理空间的一个单元,同样也是1MB。

        段描述符的作用就是用来建立这种对应关系,它与虚拟空间的顺序一一对应,每个描述符占用4Byte,4096个描述符占用16KB。

        请看以下两图:


        下图“左上”:为4GB的虚拟空间,图中已经把虚拟空间分割成了4096的单元,每个单元占用1MB。

        下图“中左”:虚拟空间与物理空间的对应关系存储在映射表中,也就是4096个描述符,假设我们将这个4096个描述符存储在SDRAM中,如下图“中左”,分别用来描述对应的4096个虚拟空间单元,每个描述符占用4Byte(32bit),并且一一对应,即,第一个描述符对应虚拟空间中的第一个单元,第二个描述符对应虚拟空间中的第2个单元。

        下图“中右”:为BANK0的第一个1MB,也就是0x0000 0000 - 0x000F FFFF,其中包含内存的片内内存4K SRAM,另外它也是整个物理空间的第一个1MB,所以一般情况,还是需要将虚拟空间的第一块映射到这里。

        下图“右下”:为整个BANK0空间。

        下图“右上”:为整个SDRAM空间。

        上图是实际的存储关系图,段映射表(段映射描述符)每个占用4Byte(32bit)的空间,合计4096×4Byte=16384Byte=16KByte,也就是说,整个段映射表需要占用16KB的内存空间,只要把这个映射表的首地址赋给MMU内部页表基址寄存器,这个值被称作TTB,TTB和MVA组合能够计算出对应相应单元的描述符的地址,在这其中第一个4Byte(32bit)对应虚拟空间里的第一块1MB,第二个4Byte(32bit)对应虚拟空间里的第二块1MB,也就是之前说的一一对应的关系。

        本实验将使用0x30000000~0x30003FFFF这段共计16KB的物理地址用来存储段映射表(段映射描述符)。

        所以映射表中的第一个4Byte,即0x30000000~0x30000003这段4Byte(32bit)的空间,用来描述虚拟空间0x00000000~0x000FFFFF这段1MB的空间与物理空间的对应关系,由于我们的引导程序处于第一个1MB的物理空间中,所以我们将虚拟空间0x00000000~0x000FFFFF这段1MB的空间指向物理空间的第一个1MB,实际上就是物理空间0x00000000~0x000FFFFF这段地址,这里虚拟地址=物理地址

        然后映射表中的第二个4Byte,即0x30000004~0x30000007这段4Byte(32bit)的空间,用来描述虚拟空间0x00100000~0x001FFFFF这段1MB的空间与物理空间的对应关系,图中,对应的是物理空间SDRAM中的0x30300000~0x303FFFFF这段1MB的空间。

        之后的描述符与虚拟空间是一一对应的,每4Byte对应虚拟空间的1MB,一直到最后4Byte,对应虚拟空间的最后1MB,即0xFFF00000~0xFFFFFFFF。




三、有段描述符参与的MVA到PA的转换过程


        前面说到了MMU内部的页表基址寄存器,用来存储映射表的首地址,被称作TTB。

        MMU在收到操作指令和MVA以后,会首先找到对应的描述符,通过它才能得到对应PA,请看下图:


        对于MVA到PA转换过程,以及描述符所扮演的角色,请看下图:

        首先取出TTB中的bit[31:14],在取出MVA中的bit[31:20],组成与MVA对应的描述符的地址,然后找到该描述符,然后取出段描述符的bit[31:20],再与MVA中的bit[19:0],得出的地址就是PA,物理地址。


        需要注意的是:由于每个描述符占用32bit的空间,假设这16KByte的页表存储在SDRAM中,那么TTB的值为0x30000000,第一个描述符的位置在0x30000000~0x30000003这段32bit的空间上,第二个描述符的位置在0x30000004~0x30000007这段32bit的空间上,以此类推,直到最后一个描述符。

        所以在上图中,由TTB和MVA合成的描述符地址中,bit[1:0]永远为0b00,这样一来,MMU就会以32bit为一个单元来读取存储在SDRAM中的页表。

        举例说明的话,假设TTB为:0x30000000,MMU接收到MVA:0x00135262,取出TTB中的bit[31:14]与MVA中的bit[31:20]进行组合:

        TTB中的bit[31:14]=0b 00110000 00000000 00

        MVA中的bit[31:20]=0b 00000000 0001

        组合然后加上bit[1:0]得出:bit[31:2]=0b 00110000 00000000 00000000 00000100

        转换成十六进制,得出MVA:0x00135262对应的描述符的位置为0x30000004,也就是说。0x00100000~0x001FFFFF这段虚拟空间是由第二个描述符控制的。

        成功找到描述符后,然后就需要按照描述符的内容来计算出PA,请看下节内容。

        另外,还需要注意的是:在编写程序是,页表操作部分,也就是生成描述符时,也要按照这种思路来操作。




四、段描述符的基本结构



        需要提示一下:上节讲的是描述符寻址,本节讲的是描述符中存储的内容,即每个描述符中占用的32bit空间的内容。

        关于MMU的作用,也就是映射虚拟地址和权限控制,所以段描述符的结构,包含了物理地址索引,Section base address(bit[31:20])和权限控制AP(bit[11:10]),当然除了这两个以外还有其他功能的设置选项,如域Domain(bit[8:5]),必须为1的bit[4]:MMU_SPECIAL,Cache缓存使能bit[3],Buffer使能bit[2],描述符类型为段:bit[1:0]=0b10。

        一般情况下,AP=0b11,Domain=0b0000,bit[4](MMU_SPECIAL)必须为0b1,Cache缓存使能bit[3]=0b1,Buffer使能bit[2]=0b1,最后是段描述符标识,合并起来,bit[11:0]=0b110000011110,转换成十六进制得到0xC1E。

        另外,由于是1MB对齐,只需要bit[31:20],也就是8位十六进制的前3位,例如物理地址0x3505F5ED,只需要将前3位“350”取出即可。

        所以在上节的例子中,假设我们已经设定描述符使得MVA:0x00100000指向PA:0x32300000这块1MB的物理空间,也就是说,第二个描述符的内容是0x32300C1E,那么上一节中还需要MVA中的bit[19:0]与描述符中的bit[31:20]合并,才能得到物理地址PA:0x32335262,这样的话,对于虚拟地址MVA:0x00135262的操作,实质上是对物理地址PA:0x32335262的操作。




五、页映射、页表


        除了段映射之外,还有页映射。

        而页表就是之前所说的映射表,而页表中的每个条目就是描述符,页表分为二级,段映射只用到一级页表,而页映射用到了二级页表。

        页的大小有3种:大页(64KB)、小页(4KB)、极小页(1KB)。

        对于描述符,段描述符、大页描述符、小页描述符、极小页描述符——他们保存段、大页、小页、极小页的起始物理地址;粗页描述符、细页描述符——它们保存二级页表的物理地址。




六、协处理器简介


        在一个MCU中,如S3C2440中,除了有一个ARM920T的CPU核外,还有若干个协处理器。协处理器也是一个微处理器,它被用来帮助主CPU完成一些特殊操作,比如浮点计算等。

        MMU、TLB、Cache实质上就是一个协处理器。




七、访问权限、域以及TLB、Cache简介


        内存的访问权限检查是MMU的主要功能之一,简单的说,他就是决定一块内存是否允许读,是否允许写。这由CP15寄存器C3(域访问控制器)、描述符的域(Domain)、CP15寄存器C1的R/S/A位、描述符的AP位等联合作用,而对于CP15寄存器,需要采用汇编进行控制,暂时不做太多说明,理解即可。

        对于虚拟地址映射,使得MMU的成为CPU的瓶颈,间接影响到了CPU的性能发挥,为了尽可能减少对CPU性能的损耗,所以引入了TLB和Cache,用来缓存指令和数据,这样对于重复操作,极大的节约操作时间,使得CPU有更多的时间处理其他操作,这样就提高了CPU的操作性能,这里只是简单介绍,理解即可。




八、实验:段映射测试


        共计4个源代码文件,分别是Makefile、mmu.lds、head.S、init.c。

        其中,head.S是汇编代码,用来初始化栈指针,然后调用init.c中的函数。

        init.c中有disable_watch_dog(关闭看门狗)、memsetup(初始化SDRAM)、create_page_table(在SDRAM中创建页表)、mmu_init(配置和启动MMU)、key_led_on(使用虚拟地址验证程序)这5个函数。

        Makefile是编译配置文件,它会根据mmu.lds中的参数将head.S编译成机器代码从0x00000000开始存放,将init.c编译成机器代码存放在head.S之后。


        init.c中create_page_table函数用来生成页表,由于程序需要从0x00000000处开始执行,程序中只设置了VA:0x00000000对应PA:0x00000000、VA:0xC0000C00对应PA:0x56000000

        mmu_init函数用来启动MMU,它是一个C语言内嵌汇编代码的函数,因MMU是协处理器,所以使用汇编代码操作。

        key_led_on函数用来测试虚拟地址,程序之中完全使用虚拟地址操作JZ2440v2上的LED灯,按下EINT0点亮D10,按下EINT2点亮D11,按下EINT11点亮D12。




<完结>