您的位置:首页 > 其它

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或者清零的动作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: