当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 讲师博文 >
裸奔之MMU
时间:2018-08-16作者:华清远见

一、MMU的介绍

MMU全称Memory Management Unit,中文称内存管理单元

主要有两个功能:
        A.将虚拟地址转换成实际的物理地址
        B.对物理内存设置访问权限

二、MMU的工作过程

在s3c2410中MMU是由协处理器(cp15)控制的,s3c2410/s3c2440多会用到两级页表:以段(Section,1MB)的方式进行转换时只用到一级页表,以页(page)的方式进行转换时用到两级页表。页的大小有3种:大页(64KB),小页(4KB),极小页(1KB)。

明确一个概念:
        条目也称为"描述符"(Descriptor),有:段描述符,大页描述符,小页描述符,极小页描述符----它们保存段、大页、小页或极小页的起始物理地址;粗页表描述符、细页表描述符---他们保存二级页表的物理地址

转换过程如下:
        (1) 根据给定的虚拟地址找到一级页表中的条目
        (2)如果此条目是段描述符,则返回物理地址,转换结束
        (3)如果此条目是二级页表描述符,继续利用虚拟地址在二级页表中找到下一个条目;
        (4)如果这第二个条目是叶描述符,则返回物理地址,转换结束;
        (5)其他情况出错

注意:这里面所有的转换过程都是由MMU完成的

以段的方式映射实例说明:

例如:虚拟地址 0xa0004000
       nbsp; 注意:当MMU打开以后,所有的地址都会被MMU拦截,然后将其转换,cpu是不管虚拟地址还是实际物理地址的。

转换如下:

先来看看TTB

简单的来说,它保存了一级页表所存放的实际物理地址,要求16KB对齐,以段的方式映射,4GB的虚拟地址空间,需要段描述符4096个(每个段描述符映射1M空间),没个描述符占用4byte,所以一段的方式映射一级页表占用的空间为16KB。

在这里我们假设,我们的一级页表存放在物理地址:0x30000000.

第一步:
        获得虚拟地址所对应的段描述符所在的地址
        addr = TTB&0xffffc000 | ((viraddr >> 20) << 2 ) = 0x30000000 & 0xfffc000 | ((0xa0004000 >> 20) << 2)= 0x30000000 | (0xa00 << 2) = 0x30002800

第二步:
        从0x30002800取出虚拟地址所对应的段描述符

段描述的构造我们到后面再来讲解,这里我们假设我们把0xa0004000映射到实际的物理地址0x30004000,则这里的[31:20]为0x300

第三步:
        组合成实际的物理地址

phyaddr = 0x300 << 20 | (0xa0004000 & 0xfffff) = 0x30004000

三.实验
        目标:以段的方式映射s3c2410的地址空间,一级页表存放在0x30000000

流程:
        A.计算每个虚拟地址对应段描述符所在的地址(addr),方法如下:

B.构造段描述符

注意:Section base address 存放的是实际的物理地址的[31:20]

C.存放段描述符
        (unsigned int *)addr = section descriptor

D.使能MMU

整个流程比较复杂的就是段描述符的构造,具体的流程大家可以直接看芯片手册,写的很详细

实例代码:

/*Nand 启动sdram的起始地址*/
        #define SRAM_START_ADDR        0x00000000
        /*内存空间地址*/
        #define VMRAM_ADDR_START        0xa0000000
        #define SDRAM_ADDR_END 0x34000000
        /*IO空间地址*/
        #define VMIO_ADDR_START 0xb0000000
        #define PHIO_ADDR_START 0x56000000
        #define PHIO_ADDR_END 0x56010000
        /*用SDRAM起始地址开始的16KB,存放页表*/
        #define PAGE_TABLE_BASE 0x30000000
        /*MASK*/
        #define PAGE_TABLE_BASE_MASK 0xffffc000
        #define VIRADDR_MASK 0xfff00000
        #define PHYADDR_MASK 0xfff00000
        /*页表项内容*/
        #define PAGE_TABLE_SECTION_AP (0x01 << 10)
        #define APGE_TABLE_SECTION_DOMAIN (0x0 << 5)
        #define PAGE_TABLE_SECTION_CACHE_WB (0x0 << 2)
        #define PAGE_TABLE_SECTION_4BIT (1 << 4)
  &nnbsp;     #define PAGE_TABLE_SECTION_TYPE (0x2)
        /*段大小*/
        #define SECTION_SIZE 0x100000
        //根据虚拟地址和页表基地址确定页表项所在的物理地址

unsigned int get_pgtindex_addr(unsigned int viraddr,unsigned int pgtaddr)
        {
        unsigned int addr;
        /*[31:14]页表基地地址
        *[13: 2]虚拟地址>>20位得到的page index
        *[1 : 0]总是为0,因为每一项占用4byte
        */
        addr = (pgtaddr & PAGE_TABLE_BASE_MASK) | (((viraddr & VIRADDR_MASK) >> 20) << 2);
        return addr;
        }

//获取页表项

unsigned int get_page_entry(unsigned int phyaddr)
        {
        unsigned int entry_value;
        /*[31:20]section base address
                *[19:12]
                *[11:10]AP
                *[9]
                *[8:5]Domain
                *[4]:1
                *[3]:C
                *[2]:B
                *[1:0]:Type
                */
        entry_value = (phyaddr & PHYADDR_MASK) | PAGE_TABLE_SECTION_AP |\
        PAGE_TABLE_SECTION_CACHE_WB | PAGE_TABLE_SECTION_4BIT|\
        PAGE_TABLE_SECTION_TYPE;
        return entry_value;
        }

/*创建一级页表:段描述符*/

void create_page_table()
        {
        int i;
        unsigned int pgt_index_addr;
        unsigned int viraddr,phyaddr,pgtaddr;
        /*我们代码的起始运行地址0x00000000在这里需要注意的是:当我们开启MMU后,cpu发出的地址都会被MMU拦截,要想程序正常运行,pc所用的的地址必须是虚拟地址。然而此时cpu执行下一条指令实际运行的地址是物理地址,但是MMU会将此物理地址当作虚拟虚拟地址处理。晕,乱套了。为了解决这个问题,我们通常的做法是,让开启MMU的附近地址指令的虚拟地址和物理地址空间做一个等价的映射。在这里我们将0x00000000开始的1M物理空间映射到0x00000000开始的虚拟地址空间。*/ 
        phyaddr = SRAM_START_ADDR;
        viraddr = phyaddr;
        pgtaddr = PAGE_TABLE_BASE;
        pgt_index_addr = get_pgtindex_addr(viraddr,pgtaddr); 
        *(volatile unsigned int *)pgt_index_addr = get_page_entry(phyaddr);
        #if 1
        /*映射64MSDRAM*/
        for(phyaddr = SDRAM_ADDR_START,viraddr = VMRAM_ADDR_START;\
        phyaddr < SDRAM_ADDR_END;phyaddr += SECTION_SIZE,\
        viraddr += SECTION_SIZE)
        {
                pgtaddr = PAGE_TABLE_BASE;
                pgt_index_addr = get_pgtindex_addr(viraddr,pgtaddr);
                *(volatile unsigned int *)pgt_index_addr = get_page_entry(phyaddr);
        }
        #endif 
        /*映射IO地址空间*/
        phyaddr = PHIO_ADDR_START;
        viraddr = VMIO_ADDR_START;
        pgtaddr = PAGE_TABLE_BASE;
        pgt_index_addr = get_pgtindex_addr(viraddr,pgtaddr);
        *(volatile unsigned int *)pgt_index_addr = get_page_entry(phyaddr); 
        return;
        }

/*
        Care must be taken if the translated address differs from the
        untranslated address as several instructions following the 
        enabling of the MMU may have been prefetched with the MMU off 
        (using physical = virtual address - flat translation) and enabling 
        the MMU may be considered as a branch with delayed execution. A similar
        situation occurs when the MMU is disabled. Consider the following code 
        sequence:
        MRC    p15, 0, R1, c1, C0, 0: Read control rejectio
        ORR    R1, #0x1
        MCR    p15,0,R1,C1, C0,0 ; Enable MMUS
        Fetch    Flat
        Fetch    Flat
        Fetch    Translated
        */

void init_mmu()
        {
        unsigned long mmu_table_base = PAGE_TABLE_BASE;
        asm(
                /*set Translation Table Base(TTB) register*/
                "mrc p15,0,r0,c2,c0,0\n"
                "mov r0,%0\n"
                "mcr p15,0,r0,c2,c0,0\n"
                /*set Domain Access Control register*/
                "mrc p15,0,r0,c3,c0,0\n"
                "mvn r0,#0\n"
                "mcr p15,0,r0,c3,c0,0\n"
                /*Enable MMU*/
                "mrc p15,0,r0,c1,c0,0\n"
                "orr r0, #0x1\n"
                "mcr p15,0,r0,c1,c0,0\n"
                "mov r0,r0\n"
                "mov r0,r0\n"
                "mov r0,r0\n"
                :
                :"r"(mmu_table_base)
                :"r0"
    &nnbsp;           );
        return;
        }
        start.S
        .text
        .global _start
        _start:

#define pWTCON 0x53000000
        #define  CLKDIVN  0x4c000014
        #define  MPLLCON  0x4c000004
        #define  MEMBASE  0x48000000
        #define  SRAM_2_ADDR  2048 
        #define  SDRAM_2_ADDR  0x30004000
        #define  SRAM_SIZE  4096 

start_code:
        @set the cpu to SVC32 mode
        mrs r0,cpsr
        bic r0,r0,#0x1f
        orr r0,r0,#0xd3
        msr cpsr,r0 

@打开指令cache
        mrc p15,0,r0,c1,c0,0
        @orr r0,r0,#0x1000
        mcr p15,0,r0,c1,c0,0

@设置栈指针位置
        ldr sp,=4096
        @关看门狗
        bl disable_watchdog
        @初始化系统时钟
        bl init_sys_clock
        @初始化内存
        bl init_sdram
        @拷贝SRAM的代码到SDRAM
        bl copy_to_sdram
        @创建页表
        bl create_page_table
        @启动MMU 
        bl init_mmu
        @运行led程序
        ldr sp,=0xa3000000 @重设sp指针,mmu之后,@cpu操作的地址都是虚拟地址 
        ldr pc,_main
        halt_loop:
        b halt_loop
        _main:
        .word main
        disable_watchdog:
        @关看门狗,不然cpu会不断重启
        ldr r0,=pWTCON
        mov r1,#0
        str r1,[r0]
        mov pc,lr
        init_sys_clock:
        @目前为止,cpu工作在12MHZ频率下
        @提升cpu工作频率FCLK:HCLK:PCLK=1:2:4
        ldr r0,=CLKDIVN
        mov r1,#3
        str r1,[r0]
        @ifHDIVN=1,must asynchronous buf mode
        mrc p15,0,r0,c1,c0,0
        orr r0,r0,#0xc0000000
        mcr p15,0,r0,c1,c0,0
        @设置MPLL,使cpu工作在202.80MHZ
        ldr r0,=MPLLCON
        ldr r1,=0x000a1031
        str r1,[r0] 
        mov pc,lr

copy_to_sdram:
        ldr  r0,=SRAM_2_ADDR  @第二阶段代码起始地址(2048)
        ldr  r1,=SDRAM_2_ADDR  @第二阶段代码存放的物理地址(0x30004000)

1:
        ldr  r2,[r0],#4
        str  r2,[r1],#4
        cmp  r0,#SRAM_SIZE
        bne  1b
        mov  pc,lr
        init_sdram:
        @初始化sdram
        ldr  r0,=MEMBASE  @13个寄存器的首地址
        adrl  r1,SMRDATA  @13个寄存器值存放的地址
        mov  r2,#52 @13 * 4 = 52 
        add  r2,r2,r1 

1:
        ldr  r3,[r1],#4
        str  r3,[r0],#4
        cmp  r1,r2
        bne  1b
        /*every thing is fine now*/
        mov  pc,lr
        .ltorg  @声明一个数据缓冲池的开始
        SMRDATA:
        .word  0x2201d110        @BWSCON 设置BANK3位宽16,使能nWait,使能UB/LB
        .word  0x0700 @BANKCON0
        .word  0x700 @BANKCON1
        .word  0x700 @BANKCON2
        .word  0x700 @BANKCON3
        .word  0x700 @BANKCON4
        .word  0x700 @BANKCON5
        .word  (3 << 15) + (1 << 0) @BANKCON6
        .word  0x18001 @BANKCON7
        .word  (1 << 23) + (2 << 18) + (1256 << 0) @REFRESH
        .word  (1 << 7) + (1 << 0) @BANKSIZE
        .word  (3 << 4) @MRSRB6
        .word  (3 << 4) @MRSRB7

led.c
        //#include "s3c2410.h"
        /*虚拟地址*/
        #define GPFCON        (*(volatile unsigned long *) 0xb0000050)
        #define GPFDAT        (*(volatile unsigned long *) 0xb0000054)
        //初始化
        static inline void led_init()
        {
        //GPFCON -> [8:15]清零
        GPFCON &= ~(0xff << 8);
        //GPF4 GPF5 GPF6 GPF7设为输出模式
        GPFCON |= 0x55 << 8;
        //输出高低平,关闭四路LED灯
        GPFDAT |= 0xf << 4;
        return;
        }
        //关闭LED
        static inline int led_off()
        {
        GPFDAT |= 0xf << 4;
        return 0;
        }
        //延时函数
        static inline int delay_time(int time)
        {
        int i,j;
        //让两个for循环作为延时
        for(i = 0;i < time;i ++)
        for(j = 0;j < time;j ++);
        return 0;
        }
        //流水灯
        static inline int run_water_led(int count)
        {
        int i = 0;
        while(count --)
        {
        led_off();
        delay_time(500);
        for(i = 4;i < 8;i ++)
        {
                GPFDAT &= ~(0x1 << i);
                delay_time(500);
                }
        }
        return 0;
        }

int main()
        { 
                led_init();
                run_water_led(5); 
                led_off(); 
                delay_time(5000);
                return 0; 
        } 
        Makefile:
        led.bin:start.S led.c
        arm-none-linux-gnueabi-gcc -c start.S -o start.o
        arm-none-linux-gnueabi-gcc -c mmu.c -o mmu.o
        arm-none-linux-gnueabi-gcc -c led.c -o led.o
        #arm-none-linux-gnueabi-ld -Ttext 0x00000000 start.o led.o -o led_elf
        arm-none-linux-gnueabi-ld -Tmap.lds start.o mmu.o led.o -o led_elf
        arm-none-linux-gnueabi-objcopy -O binary -S led_elf led.bin
        cp led.bin /tftpboot
        clean:
        rm -rf *.o led_elf led.bin
        连接脚本(map.lds)
        OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
        /*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
        OUTPUT_ARCH(arm)
        ENTRY(_start)
        SECTIONS
        {
                firtst 0x00000000:
               &nbsnbsp;{
                        start.o
                        mmu.o
                }
                second 0xa0004000:
                AT(2048)
                        {
                                led.o
                        }
        }


发表评论

全国咨询电话:400-611-6270,双休日及节假日请致电值班手机:15010390966

在线咨询: 曹老师QQ(3337544669), 徐老师QQ(1462495461), 刘老师 QQ(3108687497)

企业培训洽谈专线:010-82600901,院校合作洽谈专线:010-82600350,在线咨询:QQ(248856300)

Copyright 2004-2018 华清远见教育科技集团 版权所有 ,京ICP备16055225号,京公海网安备11010802025203号