S3C2440的MMU

  MMU:memory management unit 存储管理单元。主要的作用有2个:权限管理 和 地址映射。

一、功能

  (1) 权限管理

  简单说就是,进程之间不能访问对方的地址空间,如强制访问则报错,程序崩溃但不会影响到其它进程。出现的提示是“访问非法地址0xxxxxx”。

  (2)地址映射

  即完成虚拟地址到物理地址的转换工作。

二、MMU的地址映射原理

  首先,观察一个CPU<--->MMU<--->存储管理器<--->外接存储器(以SDRAM为例)框图。

   

  连接1处,即CPU发出的地址。站在CPU角度分析,不会区分虚拟地址和物理地址。如果MMU没有使能的话,那么CPU发出的地址就直接到达存储管理器,是物理地址。而如果MMU使能的情况,CPU发出地址为虚拟地址,经过MMU转换为物理地址。并送到存储管理器。

  连接2处,即MMU转换完成(如果MMU使能)的物理地址,或者存储管理器接收的物理地址。因此,存储管理器操作的都是物理地址。

  连接3处,即经过操作选择不同的Bank通过器件的选通功能完成的。

三、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两个存储器

posted @ 2013-11-04 13:18  dudu1990  阅读(1192)  评论(0编辑  收藏  举报