weiqi7777

基于S3C6410的ARM11学习(十五) MMU来了

0
阅读(3657)

    如今,已经是到了main的世界了。下面就要来体验ARM11MMU,这个东西在STM32中是没有的。之前在核心初始化过程中,是将MMU功能给关闭的。那是因为那个时候,操作的都是物理地址,所以需要将MMU关闭。

    MMU,内存管理单元。主要是有两个作用:

1、  将虚拟地址转化为物理地址

2、  内存的访问权限管理

         clip_image002

         上面这个图就说明了MMU的作用。

         有三个任务在运行,运行的地址都是0x400000,如果不进行处理,这肯定是不行的。所以加入了MMUMMU其实就是一个页表。将虚拟地址通过查表的方式,对应到物理地址去。虽然三个任务的运行地址都是0x400000,但是这个地址是虚拟地址,在页表中,将每个任务的虚拟地址对应到不同的物理地址,这样,就实现了各个任务虽然是同一个虚拟地址,但是在不同的物理地址中运行。

         所以要使用MMU,就首先要建立这个页表,这个页表是谁建立的,就是我们自己建立的。建立虚拟内存对应的物理内存的页表,这样,MMU才能从页表中去找到虚拟地址对应的物理地址。

         MMU有一级转换和二级转换,其实就是查几次表。对于一级转换,就是只查一次页表,就确定了物理地址。这个在S3C6410中,方式是段映射。对于二级转换,就要查两次页表或者三次,才能确定物理地址,这个在S3C6410中。有两种方式,一种细页方式,一种粗页方式。这两种的区别就是每一页对应的大小不一样而已。

 


         clip_image004

         上图是一级转换描述符。通过虚拟地址的[31:20],找到对应的转换描述符,这个转换描述符其实就是一个存在ram中的32位的一个数据。通过判断这个描述符的最后两位,判断是什么转换。如果是段转换,就按照段转换的格式去转化物理地址。如果是页转换,就在去找第二级的页表,然后再按照二级页表的格式去转化物理地址。


         首先看下一级转换页表的段转换。

clip_image006

  一级页表总包含4096个转换描述符。因为是通过虚拟地址的31:20位寻址的。12位能够表示的数最大是4096,所以说一级页表总共有4096个转换符。页表的首地址就是TTB,这个地址是很重要的,因为有了这个地址才能知道页表是存在什么地方的,才能通过偏移找到对应的描述符。所以,是有一个寄存器来保存这个地址的,这个寄存器就在CP15c2寄存器。

  通过虚拟地址的高12位偏移,找到页表中的对应描述符,判断描述符的最后两位,确定是什么转换。10的话就表示是段转换。

  判断是段转换后,将描述符的高12位取出,这个就是物理的基地址,在和虚拟地址的后20位拼接,就得到对应的物理地址。

       对于段转换,每一个段代表物理的1MB地址。为什么是1MB了。因为总共是有4096个描述符,而ARM1132位的,所以能够表示的空间是4G,这样除下来,不就每一段就表示1MB空间了。

        

         二级转换的原理也是一样的。只不过需要多查几次表。这里就大致画个细页的转换图。

clip_image008

         就是通过不断的查表,然后得到物理地址。

 

         目前学习是基于段转换的学习,所以下面就说下段转换。先来看看一级页表描述符。

clip_image010

         最后两位来表示是什么转换。10是段转换。

         B:表示是否使能write buffer

         C:  表示是否开启cache

         XN

clip_image012

         Domain: 用来说明该段属于哪一个域,ARM11共有16个域,每个域可以设定不同的权限。将段分配到某个域,就使这段的权限和设定的域权限一样。

         P:表示段区间有ECCARM11不支持,所以这一位为0.

         AP:访问权限,这个配合域,说明该段地址的访问权限。

         TEX:这个目前不是很清楚作用。

         APX: 提供额外的访问权限。

         S:      表示区域是否共享。0不共享,1共享。这个手册上说,是为了多核使用的。目前用不到,设置为0.

         nG:

clip_image014

         NS:

         Section base address:这个就是段的物理地址的高12位。

 


下图是访问权限

clip_image016

        为了让空间能够读写,所以APXAP[1:0]的值设置为011.

 

        这里设置的访问权限,还要配合设置的域。ARM11支持16个域,由CP15C3来设置。在ARM11核手册195页,有说明:

clip_image018

         寄存器的每两个位对应一个区域,设置不同的值,对访问权限也不一样。设置为0010的话,以访问就产生异常,01的话,需要检查MMU中设置的权限。设置为11,就是不进行访问权限的判断。都认为是可以访问的。

         这里为了简单,通通设置为11。所以这个寄存器的值就是0xffffffff

 

         下面就要进行程序的设计了。

         第一步,就是建立页表。都使用段映射的方式,那么就要将段映射的描述符写入到相应的内存中去。

         首先将页表放在ddr的第一个位置,也就是0x50000000。所以需要将这个地址写入到TTB中。

         这次就通过虚拟地址实现灯闪烁,所以需要建立GPIO寄存器的虚拟地址到物理地址映射。

         其次,开启MMU后,使用的就是虚拟地址了,所以还要建立程序段的页表,不然一旦开启MMUCPU去取指令,这个时候发出的就是虚拟地址,但是又没有建立虚拟地址到物理地址的映射表。这样的话,就取不到指令了,程序就崩溃了。所以还需要对代码区建立映射页表。代码区的映射就是将虚拟地址直接转换为物理地址使用。

 

         先确定使用GPIO的两个寄存器地址

clip_image020

         就更改前面8位的值。下面就要将这个地址给映射到0x7F000000区域去。

clip_image022

         代码也是比较简单的。首先申明一个ttb指针,这个指针就指向一级表的基地址,也就是ddr的首地址,0x50000000。然后再申明两个变量,一个表示虚拟地址,一个表示物理地址。

         根据前面的一级段描述符格式,往对应的内存单元写入数据即可。段描述符的高12位是物理地址的高12位,所以首先有一个物理地址和0xFFF00000与的操作,将物理地址的高12位写入到段描述符中。

         后面的位就进行相应的配置即可。看看这个定义的宏。

clip_image024

         设置为访问权限是11,也就是不进行访问权限的检查。将域设置为0CACHEwrite BUFFER都给关掉。因为这只是对一个寄存器的操作,没有必要使用CACHEwrite BUFFER

         这里用宏定义的话,程序的易读性好好得多,当然也可以直接给这个描述符赋值,不用这种宏定义的方式,但是这样程序易读性就差了,因为要去查手册才能知道你赋这个值有什么用。

         这里使用*(ttb + (vaddr >> 20))来对对应的页描述符进行赋值。在一级页表中,是通过虚拟地址的高12位来决定偏移是多少。高12位也就是位移20位,所以这里就是加上虚拟地址右移20位的值,也就是取出高12位的值。注意,这里是指针操作,假设这个时候虚拟地址的高12位值是5,那么写的地址不是0x50000005,而是0x50000014。因为指针的数据类型是long型,4个字节,所以加1,地址就会加4,加5,地址就会加5*4=20

         这样,就建立好了GPIO的页表。访问虚拟地址0xc7000000,也就是访问物理地址0x7f000000。整个段空间是1Mb

 

         接着就要建立代码区的页表。

         这个时候,代码已经是在内存中运行的了。所以,直接将内存的所有地址空间进行映射,空间是64MB大小。

clip_image026

这里使用的宏

clip_image028

  其实就是多一个开启CACHEWRITE BUFFER

 

  内存映射是映射一片区域,从0x50000000地址开始到0x5400000064M的虚拟地址给映射到0x50000000地址开始到0x5400000064M物理地址上去。这样,即使使用虚拟地址去取ddr中数据,也是能够取到的。

  因为一个段是映射的1MB空间,所以总共需要64个段,十六进制也就是0x40。所以在while里面判断是虚拟地址要小于0x54000000。再者,每一个段表示1MB空间,所以虚拟地址和物理地址都要加1MB。也就是0x100000

 

  这样,就将页表给建立好了。如果还需要使用其他地址区域,就都得为这些区域建立映射表。


  下面就要来开启MMU了。

之前说过,有一个TTB是很重要的,保存的是一级页表的首地址。首先就应该先要设置这个TTB。往CP15C2寄存器里面写入这个值就好了,因为这个寄存器就是保存的TTB

然后是设置域的权限,将域的权限设置为11,不进行访问权限检查。设置CP15c3寄存器。

最后打开MMU。设置CP15c1寄存器组的c0

 

是对CP15进行操作,用C语言是实现不了的,使用嵌入汇编。

clip_image030

  这样,就设置好了MMU,并打开了MMU。之后使用的地址都是虚拟地址了。而且该虚拟地址必须是要在页表中建立的,不然的话,就会产生异常。

 

   后面使用虚拟地址来让灯闪烁。

clip_image032

       代码和之前代码一样,不过只是地址变量,寄存器使用的地址是虚拟地址了,也不是物理地址了。

clip_image034

 

       总结一下,MMU的作用其实就是地址的转换,将虚拟地址转换为物理地址。不过在bootloader开发阶段,这个MMU是不使用的。因为在这阶段,要配置大量的寄存器来对硬件进行一些初始化,如果使用MMU的话,就要对这些寄存器的地址都要建立页表,使CPU能够访问到。这就比较麻烦了。不过在跑操作系统的时候,这个又是很需要的。这让内存管理变得方便。进程都使用虚拟地址,操作系统对这些虚拟地址进行映射到具体的物理地址上去,就感觉每个进程都是可以访问到4G空间的。

 

对比STM32

       STM32没有MMU,只有MPU,对内存保护。相当于实现了MMU的一半功能。