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

该例程有5个文件构成:
head.s-------------入口程序
mmu.lds-----------连接文件
init.c---------------初始化文件
makefile-----------编译连接
leds.c--------------主程序
由入口函数开始:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@*************************************************************************@ 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:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//file: init.cvoid 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++; }} |
|
1
2
3
4
5
|
//file: mmu.ldsSECTIONS { 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:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
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的开头处.
最能代表 页表结构的一句是:
|
1
|
*(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的页表基址寄存器
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
/* * 启动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) );} |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// leds.c: 循环点亮4个LED /* 属于第二部分程序,此时MMU已开启,使用虚拟地址 */#define GPFCON (*(volatile unsigned long *)0xA0000050) // 物理地址0x56000050对应虚拟地址 0xA000 0050#define GPFDAT (*(volatile unsigned long *)0xA0000054) // 物理地址0x56000054对应虚拟地址 0xA000 0054#define#define#define/* * 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;} |
链接文件:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//file: mmu.ldsobjs := head.o init.o leds.oall : $(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和通用寄存器之间设置的存储器, 可以把正在执行的指令附近的指令或数据从主存调入这个存储器来提高速度
MMU_段式映射的更多相关文章
- MMU段式映射(VA -> PA)过程分析
MMU:内存管理单元. CPU寻址的方式: 未使用MMU:CPU发出地址(PA) 直接内存寻址(SDRAM or DDRx). 使用MMU :CPU发出地址(VA) MMU接收CPU发来的地址 经过 ...
- PPC MPC85xx e500学习笔记
powerpc的内存体系结构 E500内核中包含内存管理单元MMU,其包含两个查找表(TLB0 Transaction Lookside Buffer)和TLB1来实现虚拟地址和物理地址的转化,其中T ...
- Linux在IA-32体系结构下的地址映射
1.概览 2.逻辑地址到线性地址 逻辑地址到线性地址的映射在IA-32体系结构中又被称为段式映射.如上图所示,段式映射我们首先需要获取逻辑地址和段选择符,段选择符用于获取GDT中段的基地址,将逻辑地址 ...
- Linux内存管理之地址映射
写在前面:由于地址映射涉及到各种寄存器的设置访问,Linux对于不同体系结构处理器的地址映射采用不同的方法,例如对于i386及后来的32位的Intel的处理器在页式映射时采用的是2级页表映射,而对于I ...
- Linux从逻辑地址到物理地址
转自:http://blog.chinaunix.net/uid-24774106-id-3427836.html 我们都知道,动态共享库里面的函数的共享的,这也是动态库的优势所在,就是节省内存.C ...
- linux内核地址mapping
linux内核采用页式存储管理,虚拟地址空间划分成固定大小的页面,由MMU(memory manager unit)在运行时将virtual address mapping to (或者说是变化成)某 ...
- “段寄存器”的故事[转](彻底搞清内存段/elf段/实模式保护模式以及段寄存器)
http://blog.csdn.net/michael2012zhao/article/details/5554023 一. 段寄存器的产生 段寄存器的产生源于Intel 8086 CPU体系结构中 ...
- U-Boot内存管理
如<Linux内核内存管理架构>一文中提到,linux内核中的内存管理支持内存地址映射.内存分配.内存回收.内存碎片管理.页面缓存等众多功能.但U-Boot做为启动引导程序,其核心功能就是 ...
- 查看内核页表kernel_page_tables (aarch32)
作者 彭东林 pengdonglin137@163.com 平台 Linux-4.10.17 Qemu + vexpress-ca9 概述 通过配置内核,会在/sys/kernel/deb ...
随机推荐
- koa2使用&&中间件&&angular2的koa托管
文章导航 1,koa2使用: 2,写中间件: 3,koa2路由配置angular2; 一.先上代码,一篇,看完koa2中大多基础方法: const Koa=require('koa'); const ...
- appium自动化测试(一)
一. appium的引入 二. adb adb(Android Debug Brige)是用来连接安卓手机和PC端的调试桥梁,通过adb服务,在PC端命令行界面对手机或者模拟器进行全面的操作 安装: ...
- st表模板
http://blog.csdn.net/insistgogo/article/details/9929103 这篇博客讲解的很详细了,求区间最大值也可以用st表,时间复杂度O(n log(n)),查 ...
- 临时开启Chrome 67拖放crx安装扩展方法
打开Chrome的设置: chrome://flags/#extensions-on-chrome-urls 更改为enabled,应用后重起浏览器,就可以往 chrome://extensions ...
- android多国语言使用
多国语言:在res目录下建立不同名称的values文件来调用不同的语言包 Values文件汇总如下: 中文(中国):values-zh-rCN 中文(台湾):values-zh-rTW 中文(香港): ...
- 【spark】示例:连接操作
我们有这样两个文件 任务:找出用户评分平均值大于4的电影. 我们看两个文件结果,第一个文件有电影的ID和名字,第二个文件有电影的ID和所有用户的评分 对于任务结果所需要的数据为电影ID,电影名字,平均 ...
- LeetCode OJ:Longest Common Prefix(最长公共前缀)
Write a function to find the longest common prefix string amongst an array of strings. 求很多string的公共前 ...
- [置顶]
Android 适配真要命?
原始尺寸场景 相信大家对上面也有所有耳闻另外就是如何计算屏幕的密度一般都是按照勾股定理例如中等屏幕密度 480^2+800^2开根号 然后除以当前屏幕尺寸3.5-4.2之间尺寸. 对于刚出来的那些An ...
- Mark: admob for delphi xe4 integrated 80% -done!-95% to do more test
Todo: admob 整合. Integrated Admob with Delphi xe4. 2013-06-28 !done! 2013-07-01 Notice: You should ...
- Ubuntu 中 java 环境 (sunjdk) 的配置 (附详细说明)
暑假以来为了鼓捣双系统废了很大的劲儿,本来一股脑想装 CentOS,无奈怎么处理分区引导都不能成功地与 Win8 共存,最终用 Ubuntu 一句 "检测到系统上有 Windows Boot ...