zoukankan      html  css  js  c++  java
  • 内存管理单元编程实例【转】

    转自:http://blog.chinaunix.net/uid-29091195-id-4020919.html

    一、MMU初步了解
        概念:内存管理单元(memory management unit),简称MMU
        主要作用:负责虚拟地址到物理地址之间的转换
                 提供硬件机制的内存访问权限检查
        
        发展由来:由于程序的大小逐步发展到大于内存的大小这一问题的出现
        导致了虚拟存储器的产生,虚拟存储器的基本思想是:数据、堆栈的总
        大小可以超越物理存储器(大于内存的程序在执行的时候只将其中需要
        运行的一部分调入内存,其他部分在外部存储器中)。由于虚拟存储器
        的出现导致cpu发出的虚拟地址不能够直接送至地址总线,这是就需要
        一个将虚拟地址转换物理地址的设备,它就是内存管理单元(MMU)。
        
        32位处理器最大可以虚拟出的地址范围是0~0xffffffff
        
    二、工作特点
        1、没有启用MMU时候,cpu发出的地址是物理地址
        2、启用MMU后,cpu发出的是虚拟地址
        注:另一说法其实是cpu总发出虚拟地址,只不过是在没有进行空间映射
        时候,虚拟地址到物理地址的转换是一种线性转换的机制。
        
        虚拟地址到物理地址的转换方法一般有两种:
        其一、使用一个固定的函数式
        其二、建立一个表格进行映射。(页表由一个个条目组成)
        
        ARM cpu使用页表进行地址转换的过程:
        1)根据给定的虚拟地址找到一级页表中的条目
        2)如果此条目是段描述符,直接返回物理地址,转换结束
        3)否则此条目是二级页表,继续在二级表中查找下一条目
        4)如果此条目是页描述符,则返回物理地址,转换结束
        5)其他的情况,则转换出错
        
        注:有上述的过程不难知道一般的ARM处理器最多用到二级页表。
        
    三、TLB了解
        概念:转译查找缓存(translation lookaside buffer)
        作用:解决MMU每次转换都需查表,降低了cpu性能的问题
        工作过程:当cpu发出一个虚拟地址的时候,MMU会首先访问TLB,如果
        在TLB中有这一虚拟地址的描述符,则直接使用该描述符进行地址转换
        。否则MMU访问页表找到对应的描述符在进行地址转换和权限检查,同时
        使用一定的算法将这一描述填入TLB中。
        
    四、Cache了解
        概念:在主存和cpu的寄存器之间的告诉小容量存储器,简称高速缓冲存
        储器,也有的将其叫做快取
        作用:将正在执行的指令地址附近的一部分指令或者数据从主存调入这
        个存储器,供cpu在一段时间内使用,从而提高程序运行的速度。
        
        一般离cpu越近的cache速度越高,但是其容量一般比较小(按一级、二级
        cache来说,一级更高速,二级容量更大一些)
        
        cache有指令快取(ICache)和数据快取(DCache)
        
    五、mmu编程实例:(实验平台mini2440)
        下面的实验是将GPB的地址0x56000000映射到0xA0000000,然后再对虚拟地址
        进行操作,点亮led的实例,实验目的在于学习mmu的页表建立,以及的段址映射
        的方法。
        
        1、启动代码(start.S)
        .text
        .global _start
        _start:
            ldr sp, =0x4096            @以下是c程序,运行前需要设置栈指针
            bl  disable_watch_dog
            bl  memsetup
            bl  copy_2th_to_sdram
            bl  create_page_table
            bl  mmu_init
            ldr sp, =0xB4000000
            ldr pc, =0xB0000000     @这个地址很重要,他必须是第二阶段代码的重载地址(运行地址)
        halt:
            bl  halt
            
        2、初始化事件(init.c)
        

    点击(此处)折叠或打开

    1. /*
    2.  * init.c: 进行一些初始化,在Steppingstone中运行
    3.  * 它和head.S同属第一部分程序,此时MMU未开启,使用物理地址
    4.  */ 
    5. /* WATCHDOG寄存器 */
    6. #define WTCON (*(volatile unsigned long *)0x53000000)
    7. /* 存储控制器的寄存器起始地址 */
    8. #define MEM_CTL_BASE 0x48000000
    9. /*
    10.  * 关闭WATCHDOG,否则CPU会不断重启
    11.  */
    12. void disable_watch_dog(void)
    13. {
    14.     WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可
    15. }
    16. /*
    17.  * 设置存储控制器以使用SDRAM
    18.  */
    19. void memsetup(void)
    20. {
    21.     /* SDRAM 13个寄存器的值 */
    22.     unsigned long const mem_cfg_val[]={ 0x22011110, //BWSCON
    23.                                             0x00000700, //BANKCON0
    24.                                             0x00000700, //BANKCON1
    25.                                             0x00000700, //BANKCON2
    26.                                             0x00000700, //BANKCON3 
    27.                                             0x00000700, //BANKCON4
    28.                                             0x00000700, //BANKCON5
    29.                                             0x00018005, //BANKCON6
    30.                                             0x00018005, //BANKCON7
    31.                                             0x008C07A3, //REFRESH
    32.                                             0x000000B1, //BANKSIZE
    33.                                             0x00000030, //MRSRB6
    34.                                             0x00000030, //MRSRB7
    35.                                     };
    36.     int i = 0;
    37.     volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;
    38.     for(; i < 13; i++)
    39.         p[i] = mem_cfg_val[i];
    40. }
    41. /*
    42.  * 将第二部分代码复制到SDRAM
    43.  */
    44. void copy_2th_to_sdram(void)
    45. {
    46.     unsigned int *pdwSrc = (unsigned int *)2048;       //这个地址必须是第二阶段代码的加载地址
    47.     unsigned int *pdwDest = (unsigned int *)0x30000000;
    48.     
    49.     while (pdwSrc < (unsigned int *)4096)
    50.     {
    51.         *pdwDest = *pdwSrc;
    52.         pdwDest++;
    53.         pdwSrc++;
    54.     }
    55. }
    56. /*
    57.  * 设置页表
    58.  */
    59. void create_page_table(void)
    60. {
    61. /* 
    62.  * 用于段描述符的一些宏定义
    63.  */ 
    64. #define MMU_FULL_ACCESS (3 << 10) /* 访问权限 */
    65. #define MMU_DOMAIN (0 << 5) /* 属于哪个域 */
    66. #define MMU_SPECIAL (1 << 4) /* 必须是1 */
    67. #define MMU_CACHEABLE (1 << 3) /* cacheable */
    68. #define MMU_BUFFERABLE (1 << 2) /* bufferable */
    69. #define MMU_SECTION (2) /* 表示这是段描述符 */
    70. #define MMU_SECDESC (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | 
    71.                              MMU_SECTION)
    72. #define MMU_SECDESC_WB (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | 
    73.                              MMU_CACHEABLE | MMU_BUFFERABLE | MMU_SECTION)
    74. #define MMU_SECTION_SIZE 0x00100000
    75.     unsigned long virtuladdr, physicaladdr;
    76.     unsigned long *mmu_tlb_base = (unsigned long *)0x30000000;
    77.     
    78.     /*
    79.      * Steppingstone的起始物理地址为0,第一部分程序的起始运行地址也是0,
    80.      * 为了在开启MMU后仍能运行第一部分的程序,
    81.      * 将0~1M的虚拟地址映射到同样的物理地址
    82.      */
    83.     virtuladdr = 0;
    84.     physicaladdr = 0;
    85.     *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | 
    86.                                             MMU_SECDESC_WB;
    87.     /*
    88.      * 0x56000000是GPIO寄存器的起始物理地址,
    89.      * GPFCON和GPFDAT这两个寄存器的物理地址0x56000050、0x56000054,
    90.      * 为了在第二部分程序中能以地址0xA0000050、0xA0000054来操作GPFCON、GPFDAT,
    91.      * 把从0xA0000000开始的1M虚拟地址空间映射到从0x56000000开始的1M物理地址空间
    92.      */
    93.     virtuladdr = 0xA0000000;
    94.     physicaladdr = 0x56000000;
    95.     *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | 
    96.                                             MMU_SECDESC;
    97.     /*
    98.      * SDRAM的物理地址范围是0x30000000~0x33FFFFFF,
    99.      * 将虚拟地址0xB0000000~0xB3FFFFFF映射到物理地址0x30000000~0x33FFFFFF上,
    100.      * 总共64M,涉及64个段描述符
    101.      */
    102.     virtuladdr = 0xB0000000;
    103.     physicaladdr = 0x30000000;
    104.     while (virtuladdr < 0xB4000000)
    105.     {
    106.         *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | 
    107.                                                 MMU_SECDESC_WB;
    108.         virtuladdr += 0x100000;
    109.         physicaladdr += 0x100000;
    110.     }
    111. }
    112. /*
    113.  * 启动MMU
    114.  */
    115. void mmu_init(void)
    116. {
    117.     unsigned long ttb = 0x30000000;
    118. // ARM休系架构与编程
    119. // 嵌入汇编:LINUX内核完全注释
    120. __asm__(
    121.     "mov r0, #0 "
    122.     "mcr p15, 0, r0, c7, c7, 0 " /* 使无效ICaches和DCaches */
    123.     
    124.     "mcr p15, 0, r0, c7, c10, 4 " /* drain write buffer on v4 */
    125.     "mcr p15, 0, r0, c8, c7, 0 " /* 使无效指令、数据TLB */
    126.     
    127.     "mov r4, %0 " /* r4 = 页表基址 */
    128.     "mcr p15, 0, r4, c2, c0, 0 " /* 设置页表基址寄存器 */
    129.     
    130.     "mvn r0, #0 " 
    131.     "mcr p15, 0, r0, c3, c0, 0 " /* 域访问控制寄存器设为0xFFFFFFFF,
    132.                                          * 不进行权限检查 
    133.                                          */ 
    134.     /* 
    135.      * 对于控制寄存器,先读出其值,在这基础上修改感兴趣的位,
    136.      * 然后再写入
    137.      */
    138.     "mrc p15, 0, r0, c1, c0, 0 " /* 读出控制寄存器的值 */
    139.     
    140.     /* 控制寄存器的低16位含义为:.RVI ..RS B... .CAM
    141.      * R : 表示换出Cache中的条目时使用的算法,
    142.      * 0 = Random replacement;1 = Round robin replacement
    143.      * V : 表示异常向量表所在的位置,
    144.      * 0 = Low addresses = 0x00000000;1 = High addresses = 0xFFFF0000
    145.      * I : 0 = 关闭ICaches;1 = 开启ICaches
    146.      * R、S : 用来与页表中的描述符一起确定内存的访问权限
    147.      * B : 0 = CPU为小字节序;1 = CPU为大字节序
    148.      * C : 0 = 关闭DCaches;1 = 开启DCaches
    149.      * A : 0 = 数据访问时不进行地址对齐检查;1 = 数据访问时进行地址对齐检查
    150.      * M : 0 = 关闭MMU;1 = 开启MMU
    151.      */
    152.     
    153.     /* 
    154.      * 先清除不需要的位,往下若需要则重新设置它们 
    155.      */
    156.                                         /* .RVI ..RS B... .CAM */ 
    157.     "bic r0, r0, #0x3000 " /* ..11 .... .... .... 清除V、I位 */
    158.     "bic r0, r0, #0x0300 " /* .... ..11 .... .... 清除R、S位 */
    159.     "bic r0, r0, #0x0087 " /* .... .... 1... .111 清除B/C/A/M */
    160.     /*
    161.      * 设置需要的位
    162.      */
    163.     "orr r0, r0, #0x0002 " /* .... .... .... ..1. 开启对齐检查 */
    164.     "orr r0, r0, #0x0004 " /* .... .... .... .1.. 开启DCaches */
    165.     "orr r0, r0, #0x1000 " /* ...1 .... .... .... 开启ICaches */
    166.     "orr r0, r0, #0x0001 " /* .... .... .... ...1 使能MMU */
    167.     
    168.     "mcr p15, 0, r0, c1, c0, 0 " /* 将修改的值写入控制寄存器 */
    169.     : /* 无输出 */
    170.     : "r" (ttb) );
    171. }

         注:这部分代码是linux应用手册中的实例程序,只是修改了第二阶段的代码拷贝的目的地址

        3、点亮led灯(led.c使用虚拟地址)
        

    点击(此处)折叠或打开

    1. #define GPBCON                (*(volatile unsigned long *)0xA0000010) /* this is a virtual address */ 
    2. #define GPBDAT                (*(volatile unsigned long *)0xA0000014) /* this is a virtual address */ 
    3. #define GPBCON_5678_OUTPUT 0x1<<5*2 | 0x1<<6*2 | 0x1<<7*2 | 0x1<<8*2
    4. #define GPBDAT_5678_LOW        0x0<<5*1 | 0x0<<6*1 | 0x0<<7*1 | 0x0<<8*1
    5. int main(int argc, char *argv[])
    6. {
    7.     GPBCON &= ~(0x3<<5*2 | 0x3<<6*2 | 0x3<<7*2 | 0x3<<8*2);
    8.     GPBCON |= GPBCON_5678_OUTPUT;
    9.     GPBDAT &= ~(0x1<<5*1 | 0x1<<6*1 | 0x1<<7*1 | 0x1<<8*1);
    10.     GPBDAT |= GPBDAT_5678_LOW;    
    11.     GPBDAT |= 0x1<<6*1;
    12.     while (1);
    13.     
    14.     return 0;
    15. }


        4、工程链接脚本(mmu.lds)
        SECTIONS {
            first          0x00000000  :                    { start.o init.o }
            second     0xB0000000  :   AT(2048)    { led.o }
        }

        注:second段的加载地址必须与拷贝代码中的起始地址一致,否则拷贝后代码无法执行
              second段的运行地址(重载地址)必须与启动代码start.S中的跳转地址一致,否则
              pc指针无法正确的跳转到第二阶段执行点亮led灯的程序。               

        5、工程Makefile
        objs := start.o init.o led.o

        mmu.bin : $(objs)
            arm-linux-ld -Tmmu.lds -o mmu_elf $^
            arm-linux-objcopy -O binary -S mmu_elf $@
            arm-linux-objdump -D -m arm mmu_elf > mmu.dis
        
        %.o:%.c
           arm-linux-gcc -Wall -O2 -c -o $@ $<

        %.o:%.S
            arm-linux-gcc -Wall -O2 -c -o $@ $<

        clean:
            rm -f mmu.bin mmu_elf mmu.dis *.o        
        

  • 相关阅读:
    KVCKVO
    音频
    静态库
    百度地图API
    CALayer
    触摸事件
    iOS中打电话、打开网址、发邮件、发短信等
    NSURLSession网络接口
    Quartz2D常见图形的绘制:线条、多边形、圆
    通知中心(NSNotificationCenter)
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/5684822.html
Copyright © 2011-2022 走看看