利用MMU通过访问虚拟内存来实现流水灯

来源:互联网 发布:java多线程讲解 编辑:程序博客网 时间:2024/05/06 23:24

实验目的:

              利用MMU通过访问虚拟内存来实现流水灯

预备知识:

           (1):熟悉协处理器的作用,CP15中的十六个寄存器,以及寄存器中的位格式和含义,相关的指令(MRC和MCR);

                         (关于协处理器的指令,在《arm体系结构与编程》这本书上说的比较好)  

           (2):  熟悉嵌入式汇编语法  (这方面的知识,在我上面的博客里已经有过介绍,这里不再赘述);

                     

实验原理:

             内存管理单元(Memory Management Unit)简称MMU。它的功能是:(1)将虚拟内存转化为物理内存;(2)设置访问权限;(3)设置Cache和Write Buffer。 若没有MMU,即程序只能直接使用物理内存,若几个应用程序同时运行,这样会导致内存泄漏,造成程序的混乱,同时有了MMU,对于CPU,其发出的虚拟地址将 会变大(对于arm9,CPU可以发出4G的虚拟内存地址)  。

  在基于ARM的嵌入式应用系统中,存储系统通常是通过系统控制协处理器CP15完成的,在具体的各种存储管理机制中,可能还会用到其他的一些技术,如在MMU中除了CP15,还有使用页表和高速缓存(很重要,注意理解)等


 在MMU中的地址变换中,有四种地址映射方法:段(section)、大页(large page)、小页(small page)、极小页(tiny page)。

虚拟地址到物理地址的索引,涉及到两种索引:一级页表索引、二级页表索引。

 其中段用的一级页表索引,大页、小页和极小页用的的二级索引。

虚拟地址到物理地址的大概转换过程如下:

                   a. 根据给定的虚拟地址找到一级页表中的条目;

           b.如果此条目是段描述符,则返回物理地址,转换结束;

   c,否则如果此条目是二级页表描述符,继续利用虚拟地址在此二级页表中找到下一个条目;

   d,如果这第二个条目是页描述符,则返回物理地址,转换结束;

                   e,其他情况出错。

 补充:这里地址转换图我不在此补充,希望大家自己参考arm体系结构与编程,上面的图解已经很详细了。

 注意:条目中的每个位的含义:

                   例如 在一级页表中描述符的格式(段的条目)

                                                      3112 11 10 9 85 4  3   2   1    0 

                                                      [section base adress| 00  00 0 0 0         ]   [  AP  ]    1     [ DOMAIN ]     1       C      B      1    0           

                                                       段的物理地址的基址                                       AP 规定怎样进行检查     domain规定对某个内存是否检查            C规定是否启动cache     B规定是否启动Write Buffer ,最后两位(10)

                       代表的是段映射。  在一级页表中,最后两位01代表的是粗页条目,   11代表的是细页条目。


   例如 在一级页表中描述符的格式(粗页的条目)

                                                      3112 11 10  9 85      4   3   2     1    0 

                                                      [Corse page table base address           ]   [  AP  ]     1      [ DOMAIN ]     1       C      B      0     1           

                                                       二级页表的虚拟页表的基址                  


  例如 在一级页表中描述符的格式(细页的条目)

                                                      3112 11 10  9 85      4   3   2     1    0 

                                                      [Fine page table base address           ]   [  AP  ]     1      [ DOMAIN ]     1       C      B      0     1           

                                                       二级虚拟页表的基址                  

注意:在地址转换工程中,如有二级页表转换(粗页和细页中的条目是跟一级页表是差不多的)。

通过MMU来实现虚拟内存的访问一般步骤:

(1)建表格;(2)将表格的基址(TTB)告诉MMU (即将TTP保存到CP15的寄存器C2中) (2)启动MMU


实验代码:第一部分代码:head.S 和 init.c主要是完成一些初始化化工作,第二部分代码:leds.c 是通过虚拟内存的访问,来实现点亮一个灯

head.S:

@*************************************************************************@ File:head.S@ 功能:设置SDRAM,将第二部分代码复制到SDRAM,设置页表,启动MMU,@       然后跳到SDRAM继续执行@*************************************************************************       .text.global _start_start:    ldr sp, =4096                       @ 设置栈指针,,以下都是C函数,调用前需要设好栈,将sp指向stepping的顶部,    bl  disable_watch_dog               @ 关闭WATCHDOG,否则CPU会不断重启    bl  memsetup                        @ 设置存储控制器以使用SDRAM    bl  copy_2th_to_sdram               @ 将第二部分代码复制到SDRAM    bl  create_page_table               @ 设置页表    bl  mmu_init                        @ 启动MMU    ldr sp, =0xB4000000                 @ 重设栈指针,指向SDRAM顶端(使用虚拟地址)    ldr pc, =0xB0004000                 @ 跳到SDRAM中继续执行第二部分代码    @ ldr pc, =mainhalt_loop:    b   halt_loop@问题一:这里sp为什么是0xB4000000@分析:SDRAM的物理内存地址范围是0x30000000~0x33ffffff,要将虚拟地址0xB0000000~0xBffffff映射到物理地址0x30000000~0x33ffffff@CPU在执行第二块代码的时候,pc就在SDRAM内了,故将sp重设为指向SDRAM的顶部@问题二:ldr pc, =0xB0004000  ,这里为什么是0xB0004000  @分析:本程序是使用段映射,只用到一级页表。32位CPU的虚拟地址空间达到4GB,一级页表中使用4096个描述符来表示这4GB空间@(每个描述符对应1MB的虚拟地址空间)每个描述符占用4个字节,所以一级页表占16KB。本例使用SDRAM的开始16KB来存放一级页表。@所以剩下的内存开始物理地址为0x30004000,然而在支持第二部分代码(即leds.c)的时候MMU已经启动,故应该使用虚拟内存.由于@虚拟地址0xB0000000~0xBffffff映射到物理地址0x30000000~0x33ffffff,虚拟内存0xB0004000对应的物理内存是0x30004000.所以@PC这个时候应该指向0xB0004000

 init.c:
/* * init.c: 进行一些初始化,在Steppingstone中运行 * 它和head.S同属第一部分程序,此时MMU未开启,使用物理地址 */ /* WATCHDOG寄存器 */#define WTCON           (*(volatile unsigned long *)0x53000000)/* 存储控制器的寄存器起始地址 */#define MEM_CTL_BASE    0x48000000/* * 关闭WATCHDOG,否则CPU会不断重启 */void disable_watch_dog(void){    WTCON = 0;  // 关闭WATCHDOG很简单,往这个寄存器写0即可}/* * 设置存储控制器以使用SDRAM */void memsetup(void){    /* SDRAM 13个寄存器的值 */    unsigned long  const    mem_cfg_val[]={ 0x22011110,     //BWSCON                                            0x00000700,     //BANKCON0                                            0x00000700,     //BANKCON1                                            0x00000700,     //BANKCON2                                            0x00000700,     //BANKCON3                                              0x00000700,     //BANKCON4                                            0x00000700,     //BANKCON5                                            0x00018005,     //BANKCON6                                            0x00018005,     //BANKCON7                                            0x008C07A3,     //REFRESH                                            0x000000B1,     //BANKSIZE                                            0x00000030,     //MRSRB6                                            0x00000030,     //MRSRB7                                    };    int     i = 0;    volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;    for(; i < 13; i++)        p[i] = mem_cfg_val[i];}/* * 将第二部分代码复制到SDRAM *  将steppingstone中2048到4096空间中(leds.c就存在这一部分中) *从mmu.lds中的AT(2048)中可以知道,第二部分代码leds.c从steppingstone处开始存放 *应该运行的位置从0xB0004000开始 */void copy_2th_to_sdram(void)        //将steppingstone的2048~4096(不包括4096)的代码复制到SDRAM{//因为前64MB的内存被一级页表占用了//段的一个条目是1M 对于cpu的4GB寻址范围,需要4GB/1M=4096个一级页表条目。 一个条目占4B 所以4096*4B=16KB。//所以剩下的内存开始地址是0x30004000    unsigned int *pdwSrc  = (unsigned int *)2048;    unsigned int *pdwDest = (unsigned int *)0x30004000;        while (pdwSrc < (unsigned int *)4096)       //    {        *pdwDest = *pdwSrc;        pdwDest++;        pdwSrc++;    }}/* * 设置页表 */void create_page_table(void){/*  * 用于段描述符的一些宏定义 */   /*设置  例如 在一级页表中描述符的格式(段的条目)  31121110   9   85 4 3  2   1   0[section base adress     ]      [    AP  ]   1   [    DOMAIN    ]       1        C        B        1   0段的物理地址的基址               AP 规定怎样进行检查 domain规定对某个内存是否检查            C规定是否启动cache     B规定是否启动Write Buffer ,最后两位(10) */#define MMU_FULL_ACCESS     (3 << 10)   /* 访问权限 */#define MMU_DOMAIN          (0 << 5)    /* 属于哪个域 */#define MMU_SPECIAL         (1 << 4)    /* 必须是1 */#define MMU_CACHEABLE       (1 << 3)    /* cacheable */#define MMU_BUFFERABLE      (1 << 2)    /* bufferable */#define MMU_SECTION         (2)         /* 表示这是段描述符 */#define MMU_SECDESC         (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | \                             MMU_SECTION)#define MMU_SECDESC_WB      (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | \                             MMU_CACHEABLE | MMU_BUFFERABLE | MMU_SECTION)#define MMU_SECTION_SIZE    0x00100000    unsigned long virtuladdr, physicaladdr;    unsigned long *mmu_tlb_base = (unsigned long *)0x30000000;        /*     * Steppingstone的起始物理地址为0,第一部分程序的起始运行地址也是0,     * 为了在开启MMU后仍能运行第一部分的程序,(同时也是保证在启动MMU之前和启动MMU瞬间,虚拟地址连续并一致)     * 将0~1M的虚拟地址映射到同样的物理地址     */          //建立第一个段条目:将虚拟地址映射到物理地址,这里虚拟和物理地址都是0开始,一样//    virtuladdr = 0;    physicaladdr = 0;    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \                                            MMU_SECDESC_WB;    /*     * 0x56000000是GPIO寄存器的起始物理地址,     * GPFCON和GPFDAT这两个寄存器的物理地址0x56000050、0x56000054,     * 为了在第二部分程序中能以地址0xA0000050、0xA0000054来操作GPFCON、GPFDAT,     * 把从0xA0000000开始的1M虚拟地址空间映射到从0x56000000开始的1M物理地址空间     */          //建立第二个段条目:将虚拟地址0xA0000000映射到物理地址0x56000000//不设置cache和write Buffer    virtuladdr = 0xA0000000;    physicaladdr = 0x56000000;    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \                                            MMU_SECDESC;    /*     * SDRAM的物理地址范围是0x30000000~0x33FFFFFF,     * 将虚拟地址0xB0000000~0xB3FFFFFF映射到物理地址0x30000000~0x33FFFFFF上,     * 总共64M,涉及64个段描述符     */         / /建立若干个段条目:将虚拟地址0xA0000000映射到物理地址0x56000000//设置cache和write Buffer    virtuladdr = 0xB0000000;    physicaladdr = 0x30000000;    while (virtuladdr < 0xB4000000)    {        *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \                                                MMU_SECDESC_WB;        virtuladdr += 0x100000;        physicaladdr += 0x100000;    }}/* * 启动MMU */void mmu_init(void){    unsigned long ttb = 0x30000000;// MRC和MCR中的讲解:在《ARM休系架构与编程》中// 在我的博客中,嵌入式汇编已经讲解了/**/__asm__(    "mov    r0, #0\n"    "mcr    p15, 0, r0, c7, c7, 0\n"    /* 使无效ICaches和DCaches */    /*在CP15中,第一个常数都是0,最后一个协处理寄存器和最后一个参数(option2)的组合决定了该命令的具体功能,      不过默认情况下是C0,0*/        "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 = 页表基址 */    /*这里的%0是引用第0个“占位符”  “占位符”是输入变量和输出变量,其是这些变量时第几个由他们在输入和输出变量中的位置决定*/    /*入式汇编程序规定把输出和输入寄存器按统一顺序编号,顺序是从输出寄存器序列从左到右从上到下以“%0”开始,分别记为%0、%1···%9    /*从下面两个冒号开始,由于输出变量没有,按照从输出寄存器序列开始从左到右从上到下的原则,TTB是第0个参数*/    /*所以这里%0代表的就是0x30000000*/    "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) );}

leds.c:

#define GPFCON(*(volatile unsigned long*)0xA0000050)#define GPFDAT  (*(volatile unsigned long*)0xA0000054)/*上面是要使用虚拟地址的在执行led.c时,就启动MMU了,使用了虚拟地址,所以在对GPFCON和GPFDAT访问的时候,CPU要通过MMU。*//* * delay函数加上“static inline”是有原因的, * 这样可以使得编译leds.c时,wait嵌入main中,编译结果中只有main一个函数。 * 于是在连接时,main函数的地址就是由连接文件指定的运行时装载地址。 * 而连接文件mmu.lds中,指定了leds.o的运行时装载地址为0xB4004000, * 这样,head.S中的“ldr pc, =0xB4004000”就是跳去执行main函数。 * volatile 是防止该代码被优化 */static inline void delay (unsigned long loops){    __asm__ volatile (            "1:\n"            "subs %0, %1, #1\n"          @loops=loops-1;            "bne 1b":"=r" (loops):"0" (loops));}/*上面是个延时函数,再次涉及到嵌入式汇编的语法知识即loops=loops 1:    subs loops,,loops,#1    bne 1b  //  :"=r" (loops)  //  :"0" (loops)*/int main(){        unsigned long i=0;while(1){        GPFCON=0x00000100;           for(i=0;i<3;i++)        {  delay(300000);                  delay(300000);           GPFCON=GPFCON<<2;          GPFDAT=0x00000000;        }}    return 0;}


                                                          在未经本人允许的情况下,拒绝转载!!




                

                     

  


  

 

     

1 0
原创粉丝点击