嵌入式学习-MMU

。_饼干妹妹 提交于 2020-04-03 12:04:12

原文:https://blog.csdn.net/u011003120/article/details/51812188

参考:

https://blog.csdn.net/groundhappy/article/details/54889677

 

MMU 存储器管理单元,在之前因为是操作物理地址,不需要MMU,因此是处于关闭状态的,而这次则是打开MMU并且使用MMU.

一、MMU的作用

  • 1.将虚拟地址转化为物理地址
  • 2.进行访问权限的管理

 

 

看上图可以得知,有三个运行的程序,他们的虚拟地址都为0x400000,但是若要使用物理地址,他们的物理地址不能够相同,因此就需要一个机制,使他们的相同的虚拟地址对应不同的物理地址,这个机制就是上图中的Page tables(即页表),虚拟地址通过查表的方式对应到不同的物理地址上。

二、地址转化
首先需要知道的是,以段(Section,1M)的方式进行转换时只用到一级页表,而页(Page)的方式进行转换时用到两级页表,有粗也转换和细页转换两种,页的大小有3种:大页(64KB)、小页(4KB)和极小页(1KB)。

1.地址转化总体分析

 

 

整个地址转换的过程分为了两步,为一级转换和二级转换。
虚拟地址的[31:20]位作为一个表的索引,表的名字为translation table,即TTB,如果表的后两位为00,则为无效的转换,如上图,如果后两位为01,则表示第二级转换为粗页方式转换,如果为10,则表示接下来会按照段的方式转换,若是11,则第二级为细页方式转换。

2.如何找到一级页表
要想找到一级页表,首先需要知道的是一级页表的地址,即TTB,它是保存在CP15的C2寄存器中,

 

 

看上面偷来的图^_^,一共有4096个转换描述符,即虚拟地址的[31:20]一共12位的最大寻址空间,虚拟地址的[31:20]再加上TTB,就是相对应的描述符,这样就找到了虚拟地址对应的描述符。

3.段式转化简单分析
还是上面的图,找到了虚拟地址相对应的地址描述符之后,描述符的[31:20]位便是物理地址的[31:20]位,虚拟地址的[19:0]位便是物理地址的[19:0]位,
至于页的转换方式,则不再细讲。

4.TTB
MMU要自动进行虚拟地址到物理地址的转化,首先要找到一级页表,而一级页表的基地址(TTB:translation table base)则是保存在CP15的C2寄存器中。因此,当程序员创建好相应的页表后,需要将页表基地址写入该寄存器。
把TTB 的值写入CP15 C2寄存器中后,MMU工作的时候,会从C2中取出TTB的值,因此MMU就会知道这张表的基地址,MMU就会工作了。
这张表是放在内存里面的,
这张表是工程师事先建立好的

三MMU的配置与使用
在本次课程中使用段式转化的方式,因此需要做以下几个工作:
建立一级页表,写入TTB,打开MMU.

本实验是通过点亮LED来完成的,LED的寄存器地址是

#define GPMCON (volatile unsigned long *)0x7F008820
#define GPMDAT (volatile unsigned long *)0x7F008824 

因此,本实验的目的就是将LED的物理地址映射为虚拟地址

#define GPMCON (volatile unsigned long *)0xA0008820
#define GPMDAT (volatile unsigned long *)0xA0008824 

1.建立一个一级页表

 

 

 如上图,对描述符的每一个寄存器进行讲解。

[1:0]   最后两位固定为10 即使用段式的方式
[2]      B 是否使用write buffer
[3]      C 是否使用CACHE
[4]      XN设置为1
[8:5]   Domain,用来说明该段是属于16个域中的哪一个域,
[9]      P表示段区间有ECC,ARM11不支持
[11:10]   AP:访问权限,这个配合域,说明该段地址的访问权限。
[14:12]   TEX   略
[15]    APX 略
[16]    S  表示是否共享
[17]    nG 略
[18]    0 
[19]   nS 略

各个寄存器的值如下:

#define MMU_FULL_ACCESS     (3 << 10)   /* 访问权限 [11:10]*/
#define MMU_DOMAIN          (0 << 5)    /* 属于哪个域 [8:5]*/
#define MMU_SPECIAL         (1 << 4)    /* 必须是1 [4]*/
#define MMU_CACHEABLE       (1 << 3)    /* cacheable  [3]*/
#define MMU_BUFFERABLE      (1 << 2)    /* bufferable [2]*/
#define MMU_SECTION         (2)         /* 表示这是段描述符  [1:0]*/
#define MMU_SECDESC         (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_SECTION)

建立页表

void creat_page_table()
{
    unsigned long *ttb = (unsigned long *)0x50000000; //表在内存的基地址处
    unsigned long vaddr; //虚拟地址
    unsigned long paddr; //物理地址

    vaddr = 0xa0000000; //虚拟地址
    paddr = 0x7F000000;
    *(ttb + (vaddr >> 20)) = (paddr&0xfff00000) | MMU_SECDESC;
    //*(ttb + (vaddr >> 20))  为表项的位置 
    //(paddr&0xfff00000) 获取高12位数据
    //MMU_SECDESC  访问led的gpio很简单,就不需要cache和buffer

}

*(ttb + (vaddr >> 20)) 即为页表的描述符,就是上面所说的 TTB + 虚拟地址[31:20]位,建立了上述的页表后,访问虚拟地址0xA0008824 通过页表,即可找到物理地址0x7F008824。

2设置TTB和使能mmu
设置TTB,其实就是将基地址写入CP15的C2寄存器中,因此使用汇编将其写入。
设置访问权限,就是将域的权限设置为0B11,即不进行权限检查,主要是设置CP15的C3寄存器
使能MMU,即打开MMU

void mmu_init()
{
    __asm__(

    /*设置TTB*/
    "ldr    r0, =0x50000000\n"                  
    "mcr    p15, 0, r0, c2, c0, 0\n"    

    /*不进行权限检查*/
    "mvn    r0, #0\n"                   
    "mcr    p15, 0, r0, c3, c0, 0\n"    


   /*使能MMU*/
    "mrc    p15, 0, r0, c1, c0, 0\n"    
    "orr    r0, r0, #0x0001\n"          
    "mcr    p15, 0, r0, c1, c0, 0\n"    
    : 
    : 
  );
}

3.内存的映射
关于内存映射这一点不太清楚,大致的理解就是,将虚拟地址和物理地址的 0x50000000 - 0x540000000 这一段进行映射,这样直接操作虚拟地址就相当于操作物理地址。

    //映射内存
    vaddr = 0x50000000;
    paddr = 0x50000000;  //其虚拟地址和物理地址是一致的
    while (vaddr < 0x54000000)  //映射64mb
    {
        *(ttb + (vaddr >> 20)) = (paddr & 0xFFF00000) | MMU_SECDESC_WB;
        vaddr += 0x100000;
        paddr += 0x100000;
    }   

全部代码

/********************************************
*file name: main.c
*author   : stone
*date     : 2016.6.30
*function : MMU进行相关的操作
*********************************************/
#define GPMCON (volatile unsigned long *)0xA0008820  //虚拟地址
#define GPMDAT (volatile unsigned long *)0xA0008824 

#define MMU_FULL_ACCESS     (3 << 10)   /* 访问权限 [11:10]*/
#define MMU_DOMAIN          (0 << 5)    /* 属于哪个域 [8:5]*/
#define MMU_SPECIAL         (1 << 4)    /* 必须是1 [4]*/
#define MMU_CACHEABLE       (1 << 3)    /* cacheable  [3]*/
#define MMU_BUFFERABLE      (1 << 2)    /* bufferable [2]*/
#define MMU_SECTION         (2)         /* 表示这是段描述符  [1:0]*/
#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)

void creat_page_table()
{
    unsigned long *ttb = (unsigned long *)0x50000000; //表在内存的基地址处
    unsigned long vaddr; //虚拟地址
    unsigned long paddr; //物理地址

    vaddr = 0xa0000000; //虚拟地址
    paddr = 0x7F000000;
    *(ttb + (vaddr >> 20)) = (paddr&0xfff00000) | MMU_SECDESC;
    //*(ttb + (vaddr >> 20))  为表项的位置 
    //(paddr&0xfff00000) 获取高12位数据
    //MMU_SECDESC  访问led的gpio很简单,就不需要cache和buffer

    //映射内存
    vaddr = 0x50000000;
    paddr = 0x50000000;  //其虚拟地址和物理地址是一致的
    while (vaddr < 0x54000000)  //映射64mb
    {
        *(ttb + (vaddr >> 20)) = (paddr & 0xFFF00000) | MMU_SECDESC_WB;
        vaddr += 0x100000;
        paddr += 0x100000;
    }   

}

void mmu_init()
{
    __asm__(

    /*设置TTB*/
    "ldr    r0, =0x50000000\n"     /* 页表的基地址   */         
    "mcr    p15, 0, r0, c2, c0, 0\n"    

    /*不进行权限检查*//*cp15 c3 domain 控制权限*/
    "mvn    r0, #0\n"                   
    "mcr    p15, 0, r0, c3, c0, 0\n"    

   /*使能MMU*/
    "mrc    p15, 0, r0, c1, c0, 0\n"    
    "orr    r0, r0, #0x0001\n"          
    "mcr    p15, 0, r0, c1, c0, 0\n"    
    : 
    : 
  );
}



int gboot_main()
{
    //*(GPMCON) = 0x1111;
    //*(GPMDAT) = 0x00;


    //1.建立页表
    creat_page_table();

    //2.写入TTB



    //3.使能
    mmu_init(); 

    *(GPMCON) = 0x1111;
    *(GPMDAT) = 0x00;   

    return 0;

}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!