MMU可以提供逻辑地址到物理地址的转换,对于应用程序可以提供超过CPU寻址范围的逻辑地址到物理地址的转换,不同的应用程序使用不同的逻辑地址可以对应到相同的物理地址。另外MMU对于CPU的地址空间(不止是内存空间)以页或块的方式提供保护(具体表现为可设置为 只读,不可用,nocache)。
对于系统而言,利用MMU的页保护机制是比较有意义的。使用MMU的页保护机制必须安装VXVMI组件,并定义INCLUDE_MMU_FULL宏以打开该功能应用。页保护一般有以下方法:
1.预先定义划分全局地址池。
2.动态修改全局地址池属性。
3.划分私有地址池。
4。保护任务的堆栈。
对于任何错误的地址访问方式(包括访问未被标识为VM_STATE_VALID的地址空间以及对标识为VM_STATE_WRITABLE_NOT的地址空间进行写操作),均会触发异常中断,系统异常处理任务会给出造成异常的任务名和指令。
1. 预先定义划分全局地址池
在BSP的sysLib.c文件中定义数组sysPhysMemDesc用于预先划分全局地址池。对于系统在运行过程中要使用的地址池,就系统而言是指系统内存,PCI设备所使用的Memory,I/O空间,FLASH空间,NvRam空间,串口地址空间,以及任何使用的CPU地址范围内的空间,都必须定义在该数组中,否则一旦访问到未在sysPhysMemDesc中定义的地址就会导致CPU异常。因为对于未在sysPhysMemDesc中定义的地址空间MMU都认为不可用(VALID_NOT),因此必须事先确定系统启动时所使用的地址范围。sysPhysMemDesc表定义如下:
PHYS_MEM_DESC sysPhysMemDesc[] =
{
{/*定义中断矢量表*/
(void *) LOCAL_MEM_LOCAL_ADRS,
(void *) LOCAL_MEM_LOCAL_ADRS,
RAM_LOW_ADRS,
VM_STATE_MASK_VALID |VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
VM_STATE_VALID | VM_STATE_WRITABLE | VM_STATE_CACHEABLE_NOT
},
{/*定义系统内存*/
(void *) RAM_LOW_ADRS,
(void *) RAM_LOW_ADRS,
LOCAL_MEM_LOCAL_ADRS+ LOCAL_MEM_SIZE- RAM_LOW_ADRS,
VM_STATE_MASK_VALID |VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
VM_STATE_VALID | VM_STATE_WRITABLE | VM_STATE_CACHEABLE
},
{/*定义网口PCI内存空间*/
(void *) 0x9d000000,
(void *) 0x9d000000,
0x400000,
VM_STATE_MASK_VALID |VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
VM_STATE_VALID | VM_STATE_WRITABLE | VM_STATE_CACHEABLE_NOT
},
{/*定义CPU寄存器空间*/
(void *) 0xFcf00000,
(void *) 0xFcf00000,
0x100000,
VM_STATE_MASK_VALID |VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
VM_STATE_VALID | VM_STATE_WRITABLE | VM_STATE_CACHEABLE_NOT
},
{/*定义PCI I/O地址空间*/
(void *) 0xFe000000,
(void *) 0xFe000000,
0x100000,
VM_STATE_MASK_VALID |VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
VM_STATE_VALID | VM_STATE_WRITABLE |VM_STATE_CACHEABLE_NOT
},
{/*定义8240的PCI地址寄存器地址空间*/
(void *) 0xFFC00000,
(void *) 0xFFC00000,
0x100000,
VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE| VM_STATE_MASK_CACHEABLE,
VM_STATE_VALID | VM_STATE_WRITABLE | VM_STATE_CACHEABLE_NOT
},
{/*定义8240的PCI数据寄存器地址空间*/
(void *) 0xFFE00000,
(void *) 0xFFE00000,
0x100000,
VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE| VM_STATE_MASK_CACHEABLE,
VM_STATE_VALID | VM_STATE_WRITABLE | VM_STATE_CACHEABLE_NOT
},
#ifdef INCLUDE_FLASH
{/*FLASH地址空间*/
(void *) FLASH_ADRS,
(void *) FLASH_ADRS,
FLASH_SIZE,
VM_STATE_MASK_VALID |VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
VM_STATE_VALID | VM_STATE_WRITABLE | VM_STATE_CACHEABLE_NOT
},
#endif /* INCLUDE_FLASH */
};
对于内存空间的低端,由于需要存放中断矢量表,所以必须在表中定义,虽然在上表中的第一项将中断矢量表定义成可写,但在BSP中通过定义INCLUDE_PROTECT_VEC_TABLE宏(打开对intVecTableWriteProtect函数的调用),使得中断矢量表只读,要改写只能通过intConnect进行,从而实现对中断矢量表的保护。对于代码段的保护BSP通过定义INCLUDE_PROTECT_TEXT宏(打开对vmTextProtect函数的调用),使得代码段只读实现保护。
对于内存中的其它部分,在上表中的第二项定义,虽然所有内存都被分配到该表项中,但这些内存需要根据需要动态划分,如下图。
对于私有地址池并不一定要划分在内存最高端,在内存中划出任何一段都可以作为私有内存。甚至任何一段未定义的内存区都可以作为私有内存。
2.动态修改全局地址池属性
对于预先划分好的全局地址池,在程序运行期间可以动态改变任何一段的属性(只读,not_cacheable,无效)。通过vmStateSet函数设置。函数如下:
STATUS vmStateSet
(
VM_CONTEXT_ID context, /*context - NULL == currentContext */
void *pVirtual, /* virtual address to modify stateof */
intlen, /* len of virtual space to modifystate of */
UINT stateMask, /* state mask */
UINT state /* state */
)
其中对于context若在程序运行中开辟了多个地址池,则每个任务都需要记录自己的每个地址池的context,以及全局地址池的context,利用vmCurrentSet进行切换自己当前使用的内存区域。对于stateMask和state有以下值:
StateMask值 | State值 |
VM_STATE_MASK_VALID | VM_STATE_VALID或VM_STATE_VALID_NOT |
VM_STATE_MASK_WRITABLE | VM_STATE_WRITABLE或 VM_STATE_WRITABLE_NOT |
VM_STATE_MASK_CACHEABLE | VM_STATE_CACHEABLE或VM_STATE_CACHEABLE_NOT |
3.划分私有地址池
对于重要的任务可以在地址池中划出一块私有地址池,任务所使用的内存或地址空间都从该池中取得,这样的话其它任务对该地址的读写都会造成CPU异常错,从而避免系统崩溃,至少可以在调试阶段快速定位问题。
划分私有地址池步骤:
1. valloc-从系统内存池中取一块地址用作私有地址池,若不使用valloc函数也可以直接指定未被定义成系统内存池的物理地址空间作为私有地址池。
2. vmMap-将取得的内存映射成当前任务的私有地址池。
3. vmStateSet-设置私有地址池属性
4. vmCurrentSet-将该私有地址池设成当前使用的地址池
5. memPartCreate-从地址池中划出地址区
6. memPartAlloc-从地址区中分配内存
由于私有地址池可以是从系统内存池中划分出来的,所以虽然两者的逻辑地址不同,却指向同一个物理地址,造成如下的情况:
对于这个私有地址池,既可以通过私有逻辑地址访问,也可以通过系统逻辑地址访问,因此失去了私有的意义,解决这个问题的办法就是在完成私有地址映射后通过vmStateSet函数将这段内存在系统地址池中设成NOT_VALID。禁止通过系统逻辑地址访问。当然如果私有地址池不是从系统地址池中划分出来的话,则该地址本来就是NOT_VALID,因此也无需作上面的操作了。
4。保护任务的堆栈。
保护任务栈的方法
1. 用taskInit创建任务,
由于taskInit可以指定任务栈的起始地址,所以不使用taskSpawn而使用taskInit创建任务。
2. 在任务堆栈前后各分配一个页面作为栈前页和栈后页。
3. 调用vmStateSet设置栈前页和栈后页为VM_STATE_WRITABLE_NOT。
如此可以排查部分栈越界的问题(越界正好落在栈前后页内)。
保护任务堆的方法
1. 不用malloc分配内存。
2. 用GetUB获得内存(会在堆前分配一个页面作为保护页面,B100中对该页面设置为VM_STATE_WRITABLE),
3. 调用vmStateSet设置该页面为VM_STATE_WRITABLE_NOT。)
使用该方法保护,需要系统有大容量内存,因为即使分配32字节的内存,也要多消耗4k(一个页面大小)的内存。