GNU LD之一LMA和VMA
2015-11-19 22:08
344 查看
MIPS 处理器存储器结构
项目当中使用的是一颗MIPS CPU,存储空间是标准的MIPS内存分配,内存被划分为几个部分,概括如下:Boot room, boot code存储空间;
iram, code 存储空间;
dram,data存储空间;
也就是说code和data有各自独立的存储空间,分开放置。
我们平常用gcc和ld生成一个可执行文件的时候,例如在命令行输入gcc -o test test.c,生成的可执行文件是一个文件哦,也就是说code和data都在一份可执行文件里面。我们把这份可执行文件烧写到flash里面,然后cpu再从flash里面取指令执行。
可是在前面,我们明明规划了code和data都各自独立的存储空间啊。有心人会问,是的啊,你讲的没错,可是这是怎么一回事呢?
两种处理器架构的区别
好,那我们回到开始,先从为什么会有code和data各自规划一块存储空间的概念。这其实得从“冯诺依曼结构”和“哈佛”结构说起。“冯诺依曼结构”,是指程序和数据存储空间并不是分开的,而是在一块存储器里面,所以程序和数据的访问位宽是相等的。
“哈佛结构”,是指程序和数据存储空间是分开的,各自有一块存储器,所以程序和数据的访问位宽可以不相等。
现在的处理器基本上都是属于上面2种架构,例如x86, arm, mips等。
说完这个,一切都清楚了。对了,我所用的MIPS恰好是“哈佛结构”的啰!
LMA和VMA
那么“哈佛结构”的处理器,明明生成的可执行文件,也就是通常所说的bin文件,只有一份啊,所以程序和数据都在同一份bin文件的,例如test.bin。我们将test.bin烧入到flash之后。在哈佛架构的处理器上,这份可执行文件是怎么执行的呢?
好问题。
我们知道对于一个C程序,在其编译链接时,代码会放在text段,常量是存储在rodata段,初始化的全局变量或者初始化的静态变量的值会放在data段,未初始化的全局变量或者静态变量会放在bss段。
而字符串指针变量例如char *string = "abcdef",字符串"abcdef"是存储在rodata段,string这个指针变量的值为字符串"abcdef"的地址,也就是rodata段中的某个地址, string变量是存储在data段(data段中某个“单元房间”里面放置的是string指针变量的值,也就是字符串常量"abcdef"的地址)。
text段和rodata段,都是存放在room中,而data和bss开始是存放在bin file中,但在C程序的main函数开始跑之前,是需要被搬运到RAM中的。
所以我们需要在bootloader中,用汇编语言写一段代码,将bin file中的data段copy到RAM中,bss段不必搬,只需要将bss段在RAM中的地址区间清零就可以了。
然后再将sp指针指向RAM的最高地址,不过SP指针一般有对齐的要求,例如8byte对齐等。
在这个搬运的过程中,就会涉及到LMA和VMA的知识了。
LMA就是load address,也就是加载地址;
VMA就是virtual address,也就是运行地址。
具体是什么意思。例如我们刚才讲到的test.bin,那么程序和数据都会按顺序存储在里面啊,顺序请参考http://www.cnblogs.com/ironx/p/4954845.html中的“目标文件在其存储器映像文件中的布局”。
在那篇文章的对应章节中,描述的就是LMA也就是程序和数据在bin文件中的存储地址,VMA也就是data和bss段在RAM中的运行地址。
而我们在bootloader中的汇编代码里面,需要将data段copy到RAM中,并清零bss段。
这个时候,汇编代码会把data段以及sdata段从其LMA处,copy到VMA处,也就是从bin文件的存储地址复制到RAM中的运行地址处。
一个典型的bootloader搬运代码如下所示:
.extern _fbss .extern _ebss .extern main .section ".boot","ax" .set noreorder .set noat .globl _start .ent _start #define DRAM_BASE 0xa0000000 #define DRAM_SIZE 0x00001800 #boot start _start: li s0, 0xffff li s1, 0xffff li v0, 0xffff li v1, 0xffff li a0, 0xffff li a1, 0xffff li a2, 0xffff li a3, 0xffff nop #copy .data to dram _copy_data: li s0, _fdata li s1, _edata li v0, DRAM_BASE 1: lw v1, 0(s0) sw v1, 0(v0) addiu s0, 4 addiu v0, 4 blt s0, s1, 1b #clear bss clear_bss: li s0, _fbss li s1, _ebss li v0, 0 1: sw v0, 0(s0) addiu s0, 4 blt s0, s1, 1b nop clr_num: li v0, 0xa0001800 move sp, v0 jal main nop loop: la v0, loop j v0 nop .set reorder .end _start
既然bootloader会用到LMA和VMA,那么LMA和VMA在哪里定义呢,就是在ld脚本中啦,ld脚本就规定程序和数据在bin文件里面是什么存储的,以及运行时在rom和ram中是怎么存储的文件。
我的ld脚本如下:
OUTPUT_FORMAT("elf32-bigmips","elf32-bigmips","elf32-littlemips") ENTRY(_start) MEMORY { room : ORIGIN = 0xbfc00000, LENGTH = 0x1000 iram : ORIGIN = 0x90000000, LENGTH = 0x4000 dram : ORIGIN = 0xa0000000, LENGTH = 0x1800 debugsram : ORIGIN = 0xb9000000, LENGTH = 0x10000 } SECTIONS { .boot 0x90000000 : { *.*(.boot) } > iram .=ALIGN(0x4); .text : { _ftext = .; *.*(.text) } > iram .rdata ALIGN(0x4) : { *.*(.rdata) } > iram .rodata ALIGN(0x4) : { *.*(.rodata) } > iram _etext = .; .data 0xa0000000: { *.*(.data) } > dram AT>iram .sdata ALIGN (0x4) : { *.*(.sdata) } > dram AT>iram .sbss ALIGN (0x4) : { _fbss = .; *(dynsbss)*(.sbss)*(.sbss.*)*(.scommon) } > dram AT>iram .bss ALIGN (0x4) : { *(.dynbss)*(.bss)*(.bss.*) *(COMMON) } > dram AT>iram _fdata = LOADADDR(.data); _edata = _fdata + SIZEOF(.data) + SIZEOF(.sdata); _ebss = _fbss + SIZEOF(.sbss) + SIZEOF(.bss); _end = .; PROVIDE (end = .); }
上面的boot汇编代码,会引用ld脚本中的LMA和VMA地址,然后进行copy或者清零的动作。
相关文章推荐
- 转:在java中使用dom4j解析xml
- Java 对象集合数据导出到Excel
- matlab读取视频VideoReader类 mmreader
- opencv 漫水填充运用
- Solr之——整合mmseg4j中文分词库
- 改变运行脚本的命令窗口标题
- nyoj--18--The Triangle(dp水题)
- android之actionbar 入门
- 实现淘宝局部方法效果
- nyoj--18--The Triangle(dp水题)
- 第一个Sprint冲刺第九天
- UI_触摸事件
- JqueryEasyUI引入,及初试
- ajax请求webservice
- 10.Spring MVC4.1-异步请求处理(包含兼容浏览器的服务器端推送)
- HDU 5313 Bipartite Graph
- 贪吃蛇小游戏(JAVA)
- StateMachine
- 使用Node.js的socket.io模块开发实时web程序
- 二叉树的中序遍历(lintcode)(递归和非递归)