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.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++; } } |
1
2
3
4
5
|
//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:
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.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和通用寄存器之间设置的存储器, 可以把正在执行的指令附近的指令或数据从主存调入这个存储器来提高速度
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 ...
随机推荐
- python学习笔记(threading多线程)
博主昨天优化了接口框架想着再添加些功能 想到对接口的性能压力测试 在工作过程中之前都是使用的工具 如:loadrunner.jmeter 想着这次准备用python实现对接口的性能压力测试 首先要实现 ...
- P3374 【模板】树状数组 1
树状数组模板题,用cdq分治做 cdq分治: 分:把l到r分成两个区间操作 治:对每个分出来的区间进行操作 但是cdq分治和一般分治不同的是,一般的分治左右两个区间互不干扰,但是cdq分治处理的是区间 ...
- Java 控制台应用程序部署 Linux
http://blog.csdn.net/brushli/article/details/12106339 注意:Windows 下编译的sh脚本,在linux上会存在编码问题,需在 linux 下编 ...
- 获取Activity的返回参数
一.新建一个空的工程 二.在主界面中添加一个按钮和一个TextView组件 三.新建一个空的activity,命名为TheAty,并添加一个按钮和一个EditText组件 四.重写TheAty的源代码 ...
- XMLHttpRequest Level2
本文转自 http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html 感谢作者分享. XMLHttpRequest是一个浏览器 ...
- Python基础学习(第3天)
第6课 循环 1.for 元素 in 序列: statement Python的新函数range():新建一个数列,都是整数,从0开始,下一个元素比上一个元素大1,一直到数列的上限(不包括上限).PS ...
- 【Codeforces】894D. Ralph And His Tour in Binary Country 思维+二分
题意 给定一棵$n$个节点完全二叉树,$m$次询问,每次询问从$a$节点到其它所有节点(包括自身)的距离$L$与给定$H_a$之差$H_a-L$大于$0$的值之和 对整棵树从叶子节点到父节点从上往下预 ...
- Android application testing with the Android test framework
目录(?)[-] Android automated testing 1 How to test Android applications Tip 2 Unit tests vs functional ...
- cowboy实现websocket
使用cowboy实现websocket主要实现以下回调函数 下面的函数返回值要具体弄清楚原因参考 websocket具体协议 主要就是两个部分 握手和数据传输 -export([init/3]). ...
- HDU - 5297:Y sequence (迭代&容斥)
Yellowstar likes integers so much that he listed all positive integers in ascending order,but he hat ...