利用MMU通过访问虚拟内存来实现流水灯
来源:互联网 发布:java多线程讲解 编辑:程序博客网 时间:2024/05/06 23:24
实验目的:
利用MMU通过访问虚拟内存来实现流水灯
预备知识:
(1):熟悉协处理器的作用,CP15中的十六个寄存器,以及寄存器中的位格式和含义,相关的指令(MRC和MCR);
(关于协处理器的指令,在《arm体系结构与编程》这本书上说的比较好)
(2): 熟悉嵌入式汇编语法 (这方面的知识,在我上面的博客里已经有过介绍,这里不再赘述);
实验原理:
内存管理单元(Memory Management Unit)简称MMU。它的功能是:(1)将虚拟内存转化为物理内存;(2)设置访问权限;(3)设置Cache和Write Buffer。 若没有MMU,即程序只能直接使用物理内存,若几个应用程序同时运行,这样会导致内存泄漏,造成程序的混乱,同时有了MMU,对于CPU,其发出的虚拟地址将 会变大(对于arm9,CPU可以发出4G的虚拟内存地址) 。
在基于ARM的嵌入式应用系统中,存储系统通常是通过系统控制协处理器CP15完成的,在具体的各种存储管理机制中,可能还会用到其他的一些技术,如在MMU中除了CP15,还有使用页表和高速缓存(很重要,注意理解)等
在MMU中的地址变换中,有四种地址映射方法:段(section)、大页(large page)、小页(small page)、极小页(tiny page)。
虚拟地址到物理地址的索引,涉及到两种索引:一级页表索引、二级页表索引。
其中段用的一级页表索引,大页、小页和极小页用的的二级索引。
虚拟地址到物理地址的大概转换过程如下:
a. 根据给定的虚拟地址找到一级页表中的条目;
b.如果此条目是段描述符,则返回物理地址,转换结束;
c,否则如果此条目是二级页表描述符,继续利用虚拟地址在此二级页表中找到下一个条目;
d,如果这第二个条目是页描述符,则返回物理地址,转换结束;
e,其他情况出错。
补充:这里地址转换图我不在此补充,希望大家自己参考arm体系结构与编程,上面的图解已经很详细了。
注意:条目中的每个位的含义:
例如 在一级页表中描述符的格式(段的条目)
3112 11 10 9 85 4 3 2 1 0
[section base adress| 00 00 0 0 0 ] [ AP ] 1 [ DOMAIN ] 1 C B 1 0
段的物理地址的基址 AP 规定怎样进行检查 domain规定对某个内存是否检查 C规定是否启动cache B规定是否启动Write Buffer ,最后两位(10)
代表的是段映射。 在一级页表中,最后两位01代表的是粗页条目, 11代表的是细页条目。
例如 在一级页表中描述符的格式(粗页的条目)
3112 11 10 9 85 4 3 2 1 0
[Corse page table base address ] [ AP ] 1 [ DOMAIN ] 1 C B 0 1
二级页表的虚拟页表的基址
例如 在一级页表中描述符的格式(细页的条目)
3112 11 10 9 85 4 3 2 1 0
[Fine page table base address ] [ AP ] 1 [ DOMAIN ] 1 C B 0 1
二级虚拟页表的基址
注意:在地址转换工程中,如有二级页表转换(粗页和细页中的条目是跟一级页表是差不多的)。通过MMU来实现虚拟内存的访问一般步骤:
(1)建表格;(2)将表格的基址(TTB)告诉MMU (即将TTP保存到CP15的寄存器C2中) (2)启动MMU
实验代码:第一部分代码:head.S 和 init.c主要是完成一些初始化化工作,第二部分代码:leds.c 是通过虚拟内存的访问,来实现点亮一个灯
head.S:
@*************************************************************************@ File:head.S@ 功能:设置SDRAM,将第二部分代码复制到SDRAM,设置页表,启动MMU,@ 然后跳到SDRAM继续执行@************************************************************************* .text.global _start_start: ldr sp, =4096 @ 设置栈指针,,以下都是C函数,调用前需要设好栈,将sp指向stepping的顶部, bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启 bl memsetup @ 设置存储控制器以使用SDRAM bl copy_2th_to_sdram @ 将第二部分代码复制到SDRAM bl create_page_table @ 设置页表 bl mmu_init @ 启动MMU ldr sp, =0xB4000000 @ 重设栈指针,指向SDRAM顶端(使用虚拟地址) ldr pc, =0xB0004000 @ 跳到SDRAM中继续执行第二部分代码 @ ldr pc, =mainhalt_loop: b halt_loop@问题一:这里sp为什么是0xB4000000@分析:SDRAM的物理内存地址范围是0x30000000~0x33ffffff,要将虚拟地址0xB0000000~0xBffffff映射到物理地址0x30000000~0x33ffffff@CPU在执行第二块代码的时候,pc就在SDRAM内了,故将sp重设为指向SDRAM的顶部@问题二:ldr pc, =0xB0004000 ,这里为什么是0xB0004000 @分析:本程序是使用段映射,只用到一级页表。32位CPU的虚拟地址空间达到4GB,一级页表中使用4096个描述符来表示这4GB空间@(每个描述符对应1MB的虚拟地址空间)每个描述符占用4个字节,所以一级页表占16KB。本例使用SDRAM的开始16KB来存放一级页表。@所以剩下的内存开始物理地址为0x30004000,然而在支持第二部分代码(即leds.c)的时候MMU已经启动,故应该使用虚拟内存.由于@虚拟地址0xB0000000~0xBffffff映射到物理地址0x30000000~0x33ffffff,虚拟内存0xB0004000对应的物理内存是0x30004000.所以@PC这个时候应该指向0xB0004000
init.c:
/* * init.c: 进行一些初始化,在Steppingstone中运行 * 它和head.S同属第一部分程序,此时MMU未开启,使用物理地址 */ /* WATCHDOG寄存器 */#define WTCON (*(volatile unsigned long *)0x53000000)/* 存储控制器的寄存器起始地址 */#define MEM_CTL_BASE 0x48000000/* * 关闭WATCHDOG,否则CPU会不断重启 */void disable_watch_dog(void){ WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可}/* * 设置存储控制器以使用SDRAM */void memsetup(void){ /* SDRAM 13个寄存器的值 */ unsigned long const mem_cfg_val[]={ 0x22011110, //BWSCON 0x00000700, //BANKCON0 0x00000700, //BANKCON1 0x00000700, //BANKCON2 0x00000700, //BANKCON3 0x00000700, //BANKCON4 0x00000700, //BANKCON5 0x00018005, //BANKCON6 0x00018005, //BANKCON7 0x008C07A3, //REFRESH 0x000000B1, //BANKSIZE 0x00000030, //MRSRB6 0x00000030, //MRSRB7 }; int i = 0; volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE; for(; i < 13; i++) p[i] = mem_cfg_val[i];}/* * 将第二部分代码复制到SDRAM * 将steppingstone中2048到4096空间中(leds.c就存在这一部分中) *从mmu.lds中的AT(2048)中可以知道,第二部分代码leds.c从steppingstone处开始存放 *应该运行的位置从0xB0004000开始 */void copy_2th_to_sdram(void) //将steppingstone的2048~4096(不包括4096)的代码复制到SDRAM{//因为前64MB的内存被一级页表占用了//段的一个条目是1M 对于cpu的4GB寻址范围,需要4GB/1M=4096个一级页表条目。 一个条目占4B 所以4096*4B=16KB。//所以剩下的内存开始地址是0x30004000 unsigned int *pdwSrc = (unsigned int *)2048; unsigned int *pdwDest = (unsigned int *)0x30004000; while (pdwSrc < (unsigned int *)4096) // { *pdwDest = *pdwSrc; pdwDest++; pdwSrc++; }}/* * 设置页表 */void create_page_table(void){/* * 用于段描述符的一些宏定义 */ /*设置 例如 在一级页表中描述符的格式(段的条目) 31121110 9 85 4 3 2 1 0[section base adress ] [ AP ] 1 [ DOMAIN ] 1 C B 1 0段的物理地址的基址 AP 规定怎样进行检查 domain规定对某个内存是否检查 C规定是否启动cache B规定是否启动Write Buffer ,最后两位(10) */#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后仍能运行第一部分的程序,(同时也是保证在启动MMU之前和启动MMU瞬间,虚拟地址连续并一致) * 将0~1M的虚拟地址映射到同样的物理地址 */ //建立第一个段条目:将虚拟地址映射到物理地址,这里虚拟和物理地址都是0开始,一样// virtuladdr = 0; physicaladdr = 0; *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \ MMU_SECDESC_WB; /* * 0x56000000是GPIO寄存器的起始物理地址, * GPFCON和GPFDAT这两个寄存器的物理地址0x56000050、0x56000054, * 为了在第二部分程序中能以地址0xA0000050、0xA0000054来操作GPFCON、GPFDAT, * 把从0xA0000000开始的1M虚拟地址空间映射到从0x56000000开始的1M物理地址空间 */ //建立第二个段条目:将虚拟地址0xA0000000映射到物理地址0x56000000//不设置cache和write Buffer virtuladdr = 0xA0000000; physicaladdr = 0x56000000; *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \ MMU_SECDESC; /* * SDRAM的物理地址范围是0x30000000~0x33FFFFFF, * 将虚拟地址0xB0000000~0xB3FFFFFF映射到物理地址0x30000000~0x33FFFFFF上, * 总共64M,涉及64个段描述符 */ / /建立若干个段条目:将虚拟地址0xA0000000映射到物理地址0x56000000//设置cache和write Buffer virtuladdr = 0xB0000000; physicaladdr = 0x30000000; while (virtuladdr < 0xB4000000) { *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \ MMU_SECDESC_WB; virtuladdr += 0x100000; physicaladdr += 0x100000; }}/* * 启动MMU */void mmu_init(void){ unsigned long ttb = 0x30000000;// MRC和MCR中的讲解:在《ARM休系架构与编程》中// 在我的博客中,嵌入式汇编已经讲解了/**/__asm__( "mov r0, #0\n" "mcr p15, 0, r0, c7, c7, 0\n" /* 使无效ICaches和DCaches */ /*在CP15中,第一个常数都是0,最后一个协处理寄存器和最后一个参数(option2)的组合决定了该命令的具体功能, 不过默认情况下是C0,0*/ "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 = 页表基址 */ /*这里的%0是引用第0个“占位符” “占位符”是输入变量和输出变量,其是这些变量时第几个由他们在输入和输出变量中的位置决定*/ /*入式汇编程序规定把输出和输入寄存器按统一顺序编号,顺序是从输出寄存器序列从左到右从上到下以“%0”开始,分别记为%0、%1···%9 /*从下面两个冒号开始,由于输出变量没有,按照从输出寄存器序列开始从左到右从上到下的原则,TTB是第0个参数*/ /*所以这里%0代表的就是0x30000000*/ "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:
#define GPFCON(*(volatile unsigned long*)0xA0000050)#define GPFDAT (*(volatile unsigned long*)0xA0000054)/*上面是要使用虚拟地址的在执行led.c时,就启动MMU了,使用了虚拟地址,所以在对GPFCON和GPFDAT访问的时候,CPU要通过MMU。*//* * delay函数加上“static inline”是有原因的, * 这样可以使得编译leds.c时,wait嵌入main中,编译结果中只有main一个函数。 * 于是在连接时,main函数的地址就是由连接文件指定的运行时装载地址。 * 而连接文件mmu.lds中,指定了leds.o的运行时装载地址为0xB4004000, * 这样,head.S中的“ldr pc, =0xB4004000”就是跳去执行main函数。 * volatile 是防止该代码被优化 */static inline void delay (unsigned long loops){ __asm__ volatile ( "1:\n" "subs %0, %1, #1\n" @loops=loops-1; "bne 1b":"=r" (loops):"0" (loops));}/*上面是个延时函数,再次涉及到嵌入式汇编的语法知识即loops=loops 1: subs loops,,loops,#1 bne 1b // :"=r" (loops) // :"0" (loops)*/int main(){ unsigned long i=0;while(1){ GPFCON=0x00000100; for(i=0;i<3;i++) { delay(300000); delay(300000); GPFCON=GPFCON<<2; GPFDAT=0x00000000; }} return 0;}
在未经本人允许的情况下,拒绝转载!!
- 利用MMU通过访问虚拟内存来实现流水灯
- FS2410 开发板上启用 MMU 实现虚拟内存管理[转]
- MMU和虚拟内存管理
- 花样流水灯(利用C51实现多种花样)
- Qt利用代码实现流水灯的效果
- 虚拟内存,MMU/TLB,PAGE,Cache
- MMU虚拟内存到物理内存
- 数组实现流水灯
- 通过设置p3p头来实现跨域访问cookie
- 通过设置p3p头来实现跨域访问cookie
- 通过设置P3P头来实现跨域访问COOKIE
- 通过设置P3P头来实现跨域访问COOKIE
- 通过设置P3P头来实现跨域访问COOKIE
- 通过设置P3P头来实现跨域访问COOKIE
- 通过设置P3P头来实现跨域访问COOKIE
- 通过设置P3P头来实现跨域访问COOKIE
- 通过设置P3P头来实现跨域访问COOKIE
- 通过设置P3P头来实现跨域访问COOKIE
- eclipse.ini 优化配置
- 二叉树的递归创建,以及二叉查找树查找的建立 和遍历查找的比较
- ZBar的简单使用
- iOS的音量
- Jquery ajax 返回string类型加result.d原因
- 利用MMU通过访问虚拟内存来实现流水灯
- 关于unix高级环境编程 编译时的err_sys和err_quit错误
- js和meta刷新页面
- [LeetCode] Minimum Depth of Binary Tree
- spring3 的restful API RequestMapping介绍
- leetcode之Path Sum II
- Java数组在内存中是什么样的?
- Android 控件滚动条的隐藏
- MyEclipse中web项目引用java项目