基于S3C6410的ARM11学习(十五) MMU来了
0赞如今,已经是到了main的世界了。下面就要来体验ARM11的MMU,这个东西在STM32中是没有的。之前在核心初始化过程中,是将MMU功能给关闭的。那是因为那个时候,操作的都是物理地址,所以需要将MMU关闭。
MMU,内存管理单元。主要是有两个作用:
1、 将虚拟地址转化为物理地址
2、 内存的访问权限管理
上面这个图就说明了MMU的作用。
有三个任务在运行,运行的地址都是0x400000,如果不进行处理,这肯定是不行的。所以加入了MMU,MMU其实就是一个页表。将虚拟地址通过查表的方式,对应到物理地址去。虽然三个任务的运行地址都是0x400000,但是这个地址是虚拟地址,在页表中,将每个任务的虚拟地址对应到不同的物理地址,这样,就实现了各个任务虽然是同一个虚拟地址,但是在不同的物理地址中运行。
所以要使用MMU,就首先要建立这个页表,这个页表是谁建立的,就是我们自己建立的。建立虚拟内存对应的物理内存的页表,这样,MMU才能从页表中去找到虚拟地址对应的物理地址。
MMU有一级转换和二级转换,其实就是查几次表。对于一级转换,就是只查一次页表,就确定了物理地址。这个在S3C6410中,方式是段映射。对于二级转换,就要查两次页表或者三次,才能确定物理地址,这个在S3C6410中。有两种方式,一种细页方式,一种粗页方式。这两种的区别就是每一页对应的大小不一样而已。
上图是一级转换描述符。通过虚拟地址的[31:20],找到对应的转换描述符,这个转换描述符其实就是一个存在ram中的32位的一个数据。通过判断这个描述符的最后两位,判断是什么转换。如果是段转换,就按照段转换的格式去转化物理地址。如果是页转换,就在去找第二级的页表,然后再按照二级页表的格式去转化物理地址。
首先看下一级转换页表的段转换。
一级页表总包含4096个转换描述符。因为是通过虚拟地址的31:20位寻址的。12位能够表示的数最大是4096,所以说一级页表总共有4096个转换符。页表的首地址就是TTB,这个地址是很重要的,因为有了这个地址才能知道页表是存在什么地方的,才能通过偏移找到对应的描述符。所以,是有一个寄存器来保存这个地址的,这个寄存器就在CP15的c2寄存器。
通过虚拟地址的高12位偏移,找到页表中的对应描述符,判断描述符的最后两位,确定是什么转换。10的话就表示是段转换。
判断是段转换后,将描述符的高12位取出,这个就是物理的基地址,在和虚拟地址的后20位拼接,就得到对应的物理地址。
对于段转换,每一个段代表物理的1MB地址。为什么是1MB了。因为总共是有4096个描述符,而ARM11是32位的,所以能够表示的空间是4G,这样除下来,不就每一段就表示1MB空间了。
二级转换的原理也是一样的。只不过需要多查几次表。这里就大致画个细页的转换图。
就是通过不断的查表,然后得到物理地址。
目前学习是基于段转换的学习,所以下面就说下段转换。先来看看一级页表描述符。
最后两位来表示是什么转换。10是段转换。
B:表示是否使能write buffer
C: 表示是否开启cache
XN:
Domain: 用来说明该段属于哪一个域,ARM11共有16个域,每个域可以设定不同的权限。将段分配到某个域,就使这段的权限和设定的域权限一样。
P:表示段区间有ECC,ARM11不支持,所以这一位为0.
AP:访问权限,这个配合域,说明该段地址的访问权限。
TEX:这个目前不是很清楚作用。
APX: 提供额外的访问权限。
S: 表示区域是否共享。0不共享,1共享。这个手册上说,是为了多核使用的。目前用不到,设置为0.
nG:
NS:
Section base address:这个就是段的物理地址的高12位。
下图是访问权限
为了让空间能够读写,所以APX和AP[1:0]的值设置为011.
这里设置的访问权限,还要配合设置的域。ARM11支持16个域,由CP15的C3来设置。在ARM11核手册195页,有说明:
寄存器的每两个位对应一个区域,设置不同的值,对访问权限也不一样。设置为00或10的话,以访问就产生异常,01的话,需要检查MMU中设置的权限。设置为11,就是不进行访问权限的判断。都认为是可以访问的。
这里为了简单,通通设置为11。所以这个寄存器的值就是0xffffffff。
下面就要进行程序的设计了。
第一步,就是建立页表。都使用段映射的方式,那么就要将段映射的描述符写入到相应的内存中去。
首先将页表放在ddr的第一个位置,也就是0x50000000。所以需要将这个地址写入到TTB中。
这次就通过虚拟地址实现灯闪烁,所以需要建立GPIO寄存器的虚拟地址到物理地址映射。
其次,开启MMU后,使用的就是虚拟地址了,所以还要建立程序段的页表,不然一旦开启MMU,CPU去取指令,这个时候发出的就是虚拟地址,但是又没有建立虚拟地址到物理地址的映射表。这样的话,就取不到指令了,程序就崩溃了。所以还需要对代码区建立映射页表。代码区的映射就是将虚拟地址直接转换为物理地址使用。
先确定使用GPIO的两个寄存器地址
就更改前面8位的值。下面就要将这个地址给映射到0x7F000000区域去。
代码也是比较简单的。首先申明一个ttb指针,这个指针就指向一级表的基地址,也就是ddr的首地址,0x50000000。然后再申明两个变量,一个表示虚拟地址,一个表示物理地址。
根据前面的一级段描述符格式,往对应的内存单元写入数据即可。段描述符的高12位是物理地址的高12位,所以首先有一个物理地址和0xFFF00000与的操作,将物理地址的高12位写入到段描述符中。
后面的位就进行相应的配置即可。看看这个定义的宏。
设置为访问权限是11,也就是不进行访问权限的检查。将域设置为0。CACHE和write BUFFER都给关掉。因为这只是对一个寄存器的操作,没有必要使用CACHE和write 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大小。
这里使用的宏
其实就是多一个开启CACHE和WRITE BUFFER。
内存映射是映射一片区域,从0x50000000地址开始到0x54000000这64M的虚拟地址给映射到0x50000000地址开始到0x54000000这64M物理地址上去。这样,即使使用虚拟地址去取ddr中数据,也是能够取到的。
因为一个段是映射的1MB空间,所以总共需要64个段,十六进制也就是0x40。所以在while里面判断是虚拟地址要小于0x54000000。再者,每一个段表示1MB空间,所以虚拟地址和物理地址都要加1MB。也就是0x100000。
这样,就将页表给建立好了。如果还需要使用其他地址区域,就都得为这些区域建立映射表。
下面就要来开启MMU了。
之前说过,有一个TTB是很重要的,保存的是一级页表的首地址。首先就应该先要设置这个TTB。往CP15的C2寄存器里面写入这个值就好了,因为这个寄存器就是保存的TTB。
然后是设置域的权限,将域的权限设置为11,不进行访问权限检查。设置CP15的c3寄存器。
最后打开MMU。设置CP15的c1寄存器组的c0。
是对CP15进行操作,用C语言是实现不了的,使用嵌入汇编。
这样,就设置好了MMU,并打开了MMU。之后使用的地址都是虚拟地址了。而且该虚拟地址必须是要在页表中建立的,不然的话,就会产生异常。
后面使用虚拟地址来让灯闪烁。
代码和之前代码一样,不过只是地址变量,寄存器使用的地址是虚拟地址了,也不是物理地址了。
总结一下,MMU的作用其实就是地址的转换,将虚拟地址转换为物理地址。不过在bootloader开发阶段,这个MMU是不使用的。因为在这阶段,要配置大量的寄存器来对硬件进行一些初始化,如果使用MMU的话,就要对这些寄存器的地址都要建立页表,使CPU能够访问到。这就比较麻烦了。不过在跑操作系统的时候,这个又是很需要的。这让内存管理变得方便。进程都使用虚拟地址,操作系统对这些虚拟地址进行映射到具体的物理地址上去,就感觉每个进程都是可以访问到4G空间的。
对比STM32:
STM32没有MMU,只有MPU,对内存保护。相当于实现了MMU的一半功能。