MMU:memory management unit 存储管理单元。主要的做用有2个:权限管理 和 地址映射。算法
1、功能编程
(1) 权限管理架构
简单说就是,进程之间不能访问对方的地址空间,如强制访问则报错,程序崩溃但不会影响到其它进程。出现的提示是“访问非法地址0xxxxxx”。dom
(2)地址映射spa
即完成虚拟地址到物理地址的转换工做。操作系统
2、MMU的地址映射原理code
首先,观察一个CPU<--->MMU<--->存储管理器<--->外接存储器(以SDRAM为例)框图。blog
链接1处,即CPU发出的地址。站在CPU角度分析,不会区分虚拟地址和物理地址。若是MMU没有使能的话,那么CPU发出的地址就直接到达存储管理器,是物理地址。而若是MMU使能的状况,CPU发出地址为虚拟地址,通过MMU转换为物理地址。并送到存储管理器。进程
链接2处,即MMU转换完成(若是MMU使能)的物理地址,或者存储管理器接收的物理地址。所以,存储管理器操做的都是物理地址。内存
链接3处,即通过操做选择不一样的Bank经过器件的选通功能完成的。
3、MMU的地址转换过程
注意本文讲解的都是基于裸跑纯硬件的,并无复杂操做系统(如Linux)的支持,网上搜到的不少博客都是对有操做系统的讨论。
MIPS架构中很是简单,其MMU对转换过程知足下述公式:虚拟地址 = 0xA000 0000 + 物理地址
ARM架构的CPU中较为复杂。分为两种段和页。其中页方式又能够细分为大页、小页、极小页。本文主要讲的是段方式的转换。
所谓段映射就是创建一个表项,一般一个段的大小规定为1M。
例如CPU的地址总线是32位的,那么就意味着,访问的地址空间能够达到2的32次方,也就是4G空间。根据段的大小规定1M,因此应该创建的表项就是4G/1M=4096项。具体解释可参见下图
若是创建如图所示的表项,那么,若是CPU须要访问的地址在0~1M,那么MMU就会在第1个表项中,取出实际物理地址。而后交给存储管理器。固然,若是访问形成冲突的状况下,须要必定的算法来处理。
那么最后能够讲解一个简单的MMU段方式的映射的流程:
第1步,创建段表项。
在哪建?能够在任意能寻址到的位置(固然是内存了),因为咱们一般是在SDRAM中运行程序,因此建议在SDRAM前4K完成此项任务。
须要注意,在创建表项以前,请勿使能MMU。这样,此时咱们使用的CPU发出的都是物理地址。
创建哪些表项,这与应用需求相关,但都应该包括SDRAM的物理地址到虚拟地址的映射过程。由于最后程序都是要在SDRAM上运行。而后,若是你应用程序中须要使用到哪些寄存器,那么就须要创建寄存器实际物理地址到虚拟地址的映射。
注意上述的虚拟地址,能够自行指定,如寄存器的虚拟地址的均可以自行定义。而SDRAM的虚拟地址,则须要与连接脚本保持一致。由于连接脚本上的地址不就是程序应该运行在的地址么,是CPU发出的地址,此时它是虚拟地址,因此咱们须要利用此虚拟地址与实际的物理地址创建映射关系。
第2步,将创建表项的首地址,通知给MMU。具体代码,可参见下述
unsigned long ttb = 0x30000000;//表项地址,即为SDRAM的起始地址 // ARM休系架构与编程 // 嵌入汇编:LINUX内核彻底注释 __asm__( "mov r0, #0\n" "mcr p15, 0, r0, c7, c7, 0\n" /* 使无效ICaches和DCaches */ "mcr p15, 0, r0, c7, c10, 4\n" /* drain write buffer on v4 */ "mcr p15, 0, r0, c8, c7, 0\n" /* 使无效指令、数据TLB */ "mov r4, %0\n" /* r4 = 页表基址 */ "mcr p15, 0, r4, c2, c0, 0\n" /* 设置页表基址寄存器 */ "mvn r0, #0\n" "mcr p15, 0, r0, c3, c0, 0\n" /* 域访问控制寄存器设为0xFFFFFFFF, * 不进行权限检查 */ /* * 对于控制寄存器,先读出其值,在这基础上修改感兴趣的位, * 而后再写入 */ "mrc p15, 0, r0, c1, c0, 0\n" /* 读出控制寄存器的值 */ /* 控制寄存器的低16位含义为:.RVI ..RS B... .CAM * R : 表示换出Cache中的条目时使用的算法,
* 0 = Random replacement;1 = Round robin replacement * V : 表示异常向量表所在的位置, * 0 = Low addresses = 0x00000000;1 = High addresses = 0xFFFF0000 * I : 0 = 关闭ICaches;1 = 开启ICaches * R、S : 用来与页表中的描述符一块儿肯定内存的访问权限 * B : 0 = CPU为小字节序;1 = CPU为大字节序 * C : 0 = 关闭DCaches;1 = 开启DCaches * A : 0 = 数据访问时不进行地址对齐检查;1 = 数据访问时进行地址对齐检查 * M : 0 = 关闭MMU;1 = 开启MMU */ /* * 先清除不须要的位,往下若须要则从新设置它们 */ /* .RVI ..RS B... .CAM */ "bic r0, r0, #0x3000\n" /* ..11 .... .... .... 清除V、I位 */ "bic r0, r0, #0x0300\n" /* .... ..11 .... .... 清除R、S位 */ "bic r0, r0, #0x0087\n" /* .... .... 1... .111 清除B/C/A/M */ /* * 设置须要的位 */ "orr r0, r0, #0x0002\n" /* .... .... .... ..1. 开启对齐检查 */ "orr r0, r0, #0x0004\n" /* .... .... .... .1.. 开启DCaches */ "orr r0, r0, #0x1000\n" /* ...1 .... .... .... 开启ICaches */ "orr r0, r0, #0x0001\n" /* .... .... .... ...1 使能MMU */ "mcr p15, 0, r0, c1, c0, 0\n" /* 将修改的值写入控制寄存器 */ : /* 无输出 */ : "r" (ttb) );
第3步,使能MMU.
总结:
整个过程应该是这样的:
1 上电后,CPU自动将nandflash前4K拷贝到SRAM中。
2 从地址0开始执行代码,完成的内容应该包括了硬件初始化(关看门狗、初始化SDRAM等)。
3 创建表项(关于如何创建表项能够参见上述),由于咱们须要使用MMU。
4 使能MMU
5 而后LDR PC XXX跳转到SDRAM中执行。注意此时,XXX应该是虚拟地址了,由于已经使能MMU。至于XXX的具体值 ,应该参照连接脚本指定的所谓的程序应该位于的地址运行。因此在写连接脚本指定地址时,也应该是个虚拟地址,而不要写实际的物理地址(0x3xxxxx)。
附:
运行一个点灯程序你会发现比不用MMU直接在SDRAM上运行快,这是由于在MMU中有ICache和DCache两个存储器