• 对MMU段式转换的理解


      本文将详细介绍MMU段式转换的过程,并在文末附上一篇讲MMU比较详细的文章。具体什么是MMU,什么时段是转换就不在本文讲了,直接戳文末的链接。

      首先,进行段式转换的条件。我们要拥有一个虚拟地址,还有一级页表,这个一级页表一般是工程师在代码中建立起来的。每一个虚拟地址在这个一级页表中都有对应的表项。我们只需要知道一级页表的基地址,再将虚拟地址的高12位作为偏移,就可以找到该虚拟地址的表项了。从这里就可以看出,前12位相同的虚拟地址们在一级页表中其实都是共用同一个表项的。举个例子:0x56000000~0x560FFFFF这1MB的空间都是使用的同一个表项。

      其次,我们找到了这个表项,那他有什么卵用呢?我们先看看表项的格式,如下图:

      高12位为段的基地址,其他的位的意义参考《ARM920T_TRM1_S》的3.3.4小结,本文重点不在这里,故不再赘述。最重要的就是段的基地址,有了它,我们就能通过将它和虚拟地址的低20位进行拼接,从而得到我们外设需要的物理地址。这里也能看出来,一个段的大小有2^20也就是1MB这么大。

      整个过程简而言之就是:通过虚拟地址的高12位找到一级页表中对应的表项,取该表项的高12位再与虚拟地址的低20位进行拼接,就得到了物理地址,这样就完成了从虚拟地址到物理地址的转换。

      下面说说我在理解这些东西时候产生的误区,希望大家不要再犯:

      1、之前一直误认为表项里面存储的就是物理地址,其实不是这样的,物理地址是将表项的高12位和虚拟地址的低20位进行拼接得来的。表项的低20位还是有别的用处的,比如决定一些权限位,以及是否使用cache,write_buffer等。

      2、一级页表的基地址和段的基地址不是一回事,页表只是存储表项用的,表项记录了一些段的基地址。比如我们控制LED需要用0x56000010和0x56000014这两个地址处的寄存器,他们属于同一个段,段的基地址是0x56000000,而我们可能将页表存储在另外一个地方,比如0x30000000,所以这两个地址是毫无联系的。

      参考代码:

      

    //1、建立一级页表,这里把它放在内存的起始地址,也就是页表的基地址
    
    //2、将虚拟地址的高12位作为偏移,用来寻找该虚拟地址对应的表项
    
    //3、表项的内容:高12位为段的基地址的高12位,低20位为下述定义的宏
    
    //4、物理地址:由段的基地址和虚拟地址的低20位拼接而来
    
    #define GPBCON *((volatile unsigned long*)0xA0000010)
    #define GPBDAT *((volatile unsigned long*)0xA0000014)
    
    #define MMU_SECTION (2 << 0)
    #define MMU_CACHE (1 << 3)
    #define MMU_BUFFER (1 << 2)
    #define MMU_SPECIAL (1 << 4)
    #define MMU_FULL_ACCESS (3 << 10)
    #define MMU_DOMAIN (0 << 5)
    
    #define SECDESC MMU_SECTION | MMU_SPECIAL | MMU_DOMAIN | MMU_FULL_ACCESS
    #define SECDESC_WB MMU_SECTION | MMU_SPECIAL | MMU_DOMAIN | MMU_FULL_ACCESS | MMU_BUFFER | MMU_CACHE
    
    void create_page_table()
    {
        //将一级页表放在内存起始地址
        unsigned long *ttb = (unsigned long*)0x30000000;
    
        unsigned long vaddr, paddr;
    
        //虚拟地址
        vaddr = 0xA0000000;
        //物理地址
        paddr = 0x56000000;
    
        //表项的地址 = 一级页表基地址 + 虚拟地址高12位(作为索引)
        *(ttb + (vaddr >> 20)) = (0x56000000 & 0xfff00000) | SECDESC;        //表项的内容
    
        vaddr = 0x30000000;
        paddr = 0x30000000;
    
        while(vaddr < 0x34000000)
        {
            *(ttb + (vaddr >> 20)) = (paddr & 0xfff00000) | SECDESC_WB;
            vaddr += 0x100000;                //0x100000 = 16^5 = 2^20 = 1MB
            paddr += 0x100000;
    
        }
    }
    
    void mmu_init()
    {
        __asm__(
            "ldr r0, =0x30000000
    "
            "mcr p15, 0, r0, c2, c0, 0
    "
    
            "mvn r0, #0
    "
            "mrc p15, 0, r0, c3, c0, 0
    "
    
            "mrc p15, 0, r0, c1, c0, 0
    "
            "orr r0, r0, #0x0001
    "
            "mcr p15, 0, r0, c1, c0, 0
    "
            :
            :
    
        );
    }
    
    //使用虚拟地址GPBCON, GPBDAT点亮LED
    
    int gboot_main()
    {
    
        //1、建立一级页表
        create_page_table();
        //2、写入TTB,打开MMU
        mmu_init();
    
        GPBCON = 0x15400;
        GPBDAT = 0b11010111111;
    
        return 0;
    }

      附加一篇讲MMU比较详细的文章(转):http://www.cnblogs.com/wrjvszq/p/4246634.html

      如有错误或问题,欢迎指出,转载请注明出处。

                                                              18:09:45

                                                                2015-07-26

      

  • 相关阅读:
    matlab中的开方sqrt用牛顿迭代法实现的代码
    转载 迭代算法实现开平方
    关于verilog中小数直接赋值
    关于verilog中的signed类型
    第二十五篇:使用 sigaction 函数实现可靠信号
    第二十四篇:可靠信号机制
    第二十三篇:信号机制的两个思考
    第二十二篇:信号的接收和处理
    hdu 2918(IDA*)
    hdu 1813(IDA*)
  • 原文地址:https://www.cnblogs.com/51qianrushi/p/4678321.html
Copyright © 2020-2023  润新知