文档章节

MMU_段式映射

季风_落地窗
 季风_落地窗
发布于 2014/05/04 15:32
字数 1902
阅读 4338
收藏 4

首先, 段式映射的示意图如下:


该例程有5个文件构成:

head.s-------------入口程序

mmu.lds-----------连接文件

init.c---------------初始化文件

makefile-----------编译连接

leds.c--------------主程序

由入口函数开始:

@*************************************************************************
@ File:head.S
@ 功能:设置SDRAM,将第二部分代码复制到SDRAM,设置页表,启动MMU,
@       然后跳到SDRAM继续执行
@*************************************************************************       

.text
.global _start
_start:
    ldr sp, =4096                       @ 设置栈指针,以下都是C函数,调用前需要设好栈
    bl  disable_watch_dog               @ 关闭WATCHDOG,否则CPU会不断重启
    bl  memsetup                        @ 设置存储控制器以使用SDRAM
    bl  copy_2th_to_sdram               @ 将第二部分代码复制到SDRAM   --- 注释 1
    bl  create_page_table               @ 设置页表                   --- 注释 2                       
    bl  mmu_init                        @ 启动MMU                    --- 注释 3
    ldr sp, =0xB4000000                 @ 重设栈指针,指向SDRAM顶端(使用虚拟地址)
    ldr pc, =0xB0004000                 @ 跳到SDRAM中继续执行第二部分代码
halt_loop:
    b   halt_loop

注释1:

//file: init.c
void copy_2th_to_sdram(void)
{
    unsigned int *pdwSrc  = (unsigned int *)2048;
    unsigned int *pdwDest = (unsigned int *)0x30004000;
    
    while (pdwSrc < (unsigned int *)4096)
    {
        *pdwDest = *pdwSrc;
        pdwDest++;
        pdwSrc++;
    }
}

//file: mmu.lds
SECTIONS { 
  firtst    0x00000000 : { head.o init.o }
  second    0xB0004000 : AT(2048) { leds.o }
}

由mmu.lds可知, 第二部分的代码链接地址为2048, 加载地址为 0xB000 4000. 而init.c中将第二段的代码放到了 0x3000 4000. 我们利用mmu将 物理地址0x3000 4000 映射到 虚拟地址0xB000 4000. 

注释2:

void create_page_table(void)
{

/* 
 * 用于段描述符的一些宏定义
 */ 
#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后仍能运行第一部分的程序,
     * 将0~1M的虚拟地址映射到同样的物理地址
     */
    virtuladdr = 0;
    physicaladdr = 0;
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
                                            MMU_SECDESC_WB;

    /*
     * 0x56000000是GPIO寄存器的起始物理地址,
     * GPBCON和GPBDAT这两个寄存器的物理地址0x56000050、0x56000054,
     * 为了在第二部分程序中能以地址0xA0000050、0xA0000054来操作GPFCON、GPFDAT,
     * 把从0xA0000000开始的1M虚拟地址空间映射到从0x56000000开始的1M物理地址空间
     */
    virtuladdr = 0xA0000000;
    physicaladdr = 0x56000000;
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
                                            MMU_SECDESC;

    /*
     * SDRAM的物理地址范围是0x30000000~0x33FFFFFF,
     * 将虚拟地址0xB0000000~0xB3FFFFFF映射到物理地址0x30000000~0x33FFFFFF上,
     * 总共64M,涉及64个段描述符
     */
    virtuladdr = 0xB0000000;
    physicaladdr = 0x30000000;
    while (virtuladdr < 0xB4000000)
    {
        *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
                                                MMU_SECDESC_WB;
        virtuladdr += 0x100000;                //段描述符对应1M空间, 所以每次加0x100000
        physicaladdr += 0x100000;
    }
}

其中 mmu_tlb_base 被定义unsigned long 类型, 刚好占4byte 它和页表描述符大小相同. mmu_tlb_base的值为 0x3000 0000, 表示一级页表被放置在了SDRAM的开头处.  

最能代表 页表结构的一句是:

 *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | MMU_SECDESC_WB;

MVA的[31:20]等于页表索引(table index), 对应的代码是  (virtualaddr >> 20)

PA[31:20]等于段描述符的[31:20], 对应的代码是 (physicaladdr & 0xFFF00000)

简单来说就是根据MVA和TTB寄存器找到一级页表中的段描述符, 该段描述符里存放的就是实际的物理地址, 我们的程序只是 1. 配置了 TTB寄存器 2. 把物理地址存放到了段描述符里

还有, 我们映射的区域是0x3000 0000 到 0xB000 0000, 为什么代码从0x3000 4000开始存放呢? 因为页表项最多有4096个, 每个4byte, 共16k, 所以前16k 保留, 防止覆盖掉页表区域.

注释3:

mmu部分不详细解释他的各种配置了, 最重要的是将我们定好的页表基址0x3000 0000 ,  写入cp15的页表基址寄存器

/*
 * 启动MMU
 */
void mmu_init(void)
{
    unsigned long ttb = 0x30000000;

__asm__(
    "mov    r0, #0\n"
    "mcr    p15, 0, r0, c7, c7, 0\n"    /* 使无效ICaches和DCaches */
    
    "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 = 页表基址 */
    "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: 循环点亮4个LED
 /* 属于第二部分程序,此时MMU已开启,使用虚拟地址
 */

#define GPFCON      (*(volatile unsigned long *)0xA0000050)     // 物理地址0x56000050对应虚拟地址 0xA000 0050
#define GPFDAT      (*(volatile unsigned long *)0xA0000054)     // 物理地址0x56000054对应虚拟地址 0xA000 0054

#define	GPF4_out	(1<<(4*2))
#define	GPF5_out	(1<<(5*2))
#define	GPF6_out	(1<<(6*2))

/*
 * wait函数加上“static inline”是有原因的,
 * 这样可以使得编译leds.c时,wait嵌入main中,编译结果中只有main一个函数。
 * 于是在连接时,main函数的地址就是由连接文件指定的运行时装载地址。
 * 而连接文件mmu.lds中,指定了leds.o的运行时装载地址为0xB4004000,
 * 这样,head.S中的“ldr pc, =0xB4004000”就是跳去执行main函数。
 */
static inline void wait(unsigned long dly)
{
    for(; dly > 0; dly--);
}

int main(void)
{
	unsigned long i = 0;

	GPFCON = GPF4_out|GPF5_out|GPF6_out;		// 将LED1,2,4对应的GPF4/5/6三个引脚设为输出

	while(1)
	{
		wait(30000);
		GPFDAT = (~(i<<4));	 	// 根据i的值,点亮LED1,2,4
		if(++i == 8)
			i = 0;
	}

	return 0;
}

链接文件:

//file: mmu.lds
objs := head.o init.o leds.o

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

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

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

代码执行示意:

最后说一下TLB和cache:

TLB: 上述地址转换只经过了一级转换, 但是每次读/写数据都要访问两次sdram, 第一次取地址, 第二次读写数据. 如果是两级页表转换的话则需要每次读/写都要访问三次sdram, 为了提高访问效率使用TLB来存储要用到的若干条页表条目(段/大页/小页/极小页描述符).通常在启动mmu之前先使无效整个TLB, 

改变页表时, 使无效所涉及的虚拟地址对应的TLB中的条目

cache: 在cpu和通用寄存器之间设置的存储器, 可以把正在执行的指令附近的指令或数据从主存调入这个存储器来提高速度


本文转载自:http://liu1227787871.blog.163.com/blog/static/20536319720125742346279/

季风_落地窗
粉丝 4
博文 16
码字总数 16829
作品 0
杨浦
程序员
私信 提问
MMU的作用及工作过程

http://blog.csdn.net/leishangwen/article/details/27088305?utmsource=tuicool&utm_medium=referral 以下内容摘自《步步惊芯——软核处理器内部设计分析》一书的第10章 MMU剖析 MMU的作用及...

ilmfmly
2017/05/19
0
0
x86的段页式存储管理

x86页表项中有一个U/S位,表征是否允许一般用户访问,页表项的位明显是基于页式存储管理的,然而访存系统mmu是怎么知道访问者目前是u还是s呢?是u还是s是在哪里设置的呢?根据又是什么呢?其...

晨曦之光
2012/04/10
165
0
memory management unit

MMU即内存管理单元,它负责虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查。MMU使得每个用户进程拥有自己独立的地址空间,并通过内存访问权限的检查保护每个进程所用的内存不被...

peng_258
2017/10/25
0
0
内存虚拟化技术,具体的实现方法有哪两种?

引入内存虚拟化技术后,内存系统中存在3种地址。 机器地址(Machine Address,MA):真实硬件的机器地址,在地址总线上可以见到的地址信号。 虚拟机物理地址(Guest Physical Address,GPA)...

zlw东南风
2017/10/23
0
0
关于MMU映射 建表的问题

#define MMU_FULL_ACCESS (3 << 10) /* 访问权限 */ #define MMU_DOMAIN (0 << 5) /* 属于哪个域 */ #define MMU_SPECIAL (1 << 4) /* 必须是1 */ #define MMU_CACHEABLE (1 << 3) /* cachea......

徐长远
2013/03/02
1K
5

没有更多内容

加载失败,请刷新页面

加载更多

golang-字符串-地址分析

demo package mainimport "fmt"func main() {str := "map.baidu.com"fmt.Println(&str, str)str = str[0:5]fmt.Println(&str, str)str = "abc"fmt.Println(&s......

李琼涛
今天
4
0
Spring Boot WebFlux 增删改查完整实战 demo

03:WebFlux Web CRUD 实践 前言 上一篇基于功能性端点去创建一个简单服务,实现了 Hello 。这一篇用 Spring Boot WebFlux 的注解控制层技术创建一个 CRUD WebFlux 应用,让开发更方便。这里...

泥瓦匠BYSocket
今天
6
0
从0开始学FreeRTOS-(列表与列表项)-3

FreeRTOS列表&列表项的源码解读 第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像。 在FreeRTOS中,列表与列表项使用得非常多,是FreeRTOS的一个数...

杰杰1号
今天
4
0
Java反射

Java 反射 反射是框架设计的灵魂(使用的前提条件:必须先得到代表的字节码的 Class,Class 类 用于表示.class 文件(字节码)) 一、反射的概述 定义:JAVA 反射机制是在运行状态中,对于任...

zzz1122334
今天
5
0
聊聊nacos的LocalConfigInfoProcessor

序 本文主要研究一下nacos的LocalConfigInfoProcessor LocalConfigInfoProcessor nacos-1.1.3/client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java p......

go4it
昨天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部