GNU LD之一LMA和VMA


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搬運代碼如下所示:

 1   .extern    _fbss
 2   .extern    _ebss
 3   .extern     main
 4   .section ".boot","ax"
 5   .set noreorder
 6   .set noat 
 7   .globl  _start
 8   .ent    _start    
 9 
10 #define DRAM_BASE    0xa0000000
11 #define DRAM_SIZE    0x00001800
12 
13 #boot start
14 _start:
15     li    s0, 0xffff
16     li    s1, 0xffff
17     li    v0, 0xffff
18     li    v1, 0xffff
19     li    a0, 0xffff
20         li      a1, 0xffff
21     li    a2, 0xffff
22     li    a3, 0xffff
23         nop
24 #copy .data to dram
25 _copy_data:
26     li    s0, _fdata
27     li    s1, _edata
28     li    v0, DRAM_BASE
29 1:    lw      v1, 0(s0)
30     sw    v1, 0(v0)
31     addiu    s0, 4
32     addiu    v0, 4
33     blt    s0, s1, 1b        
34 #clear bss               
35 clear_bss:
36     li    s0, _fbss
37     li    s1, _ebss
38     li    v0, 0
39 1:    sw    v0, 0(s0)
40     addiu   s0, 4
41     blt    s0, s1, 1b
42     nop
43 clr_num:
44            li      v0, 0xa0001800
45            move     sp, v0
46     jal    main
47     nop
48 loop:
49     la    v0, loop
50     j       v0    
51     nop
52          .set    reorder  
53           .end    _start

既然bootloader會用到LMA和VMA,那么LMA和VMA在哪里定義呢,就是在ld腳本中啦,ld腳本就規定程序和數據在bin文件里面是什么存儲的,以及運行時在rom和ram中是怎么存儲的文件。

我的ld腳本如下:

 1 OUTPUT_FORMAT("elf32-bigmips","elf32-bigmips","elf32-littlemips")
 2 ENTRY(_start)
 3 MEMORY
 4 {
 5     room : ORIGIN = 0xbfc00000, LENGTH = 0x1000
 6     iram : ORIGIN = 0x90000000, LENGTH = 0x4000
 7     dram : ORIGIN = 0xa0000000, LENGTH = 0x1800
 8     debugsram : ORIGIN = 0xb9000000, LENGTH = 0x10000
 9 }
10 SECTIONS
11 {
12   .boot 0x90000000 :           
13   { 
14     *.*(.boot) 
15   } > iram
16   
17   .=ALIGN(0x4);
18   .text :         
19   { 
20     _ftext = .;
21       *.*(.text) 
22   } > iram
23   .rdata  ALIGN(0x4)   :     
24   { 
25     *.*(.rdata) 
26   } > iram  
27   .rodata  ALIGN(0x4)   :     
28   { 
29     *.*(.rodata) 
30   } > iram
31   _etext = .;
32 
33   .data 0xa0000000:    
34   { 
35        *.*(.data) 
36   } > dram AT>iram
37   .sdata ALIGN (0x4) :
38   {
39     *.*(.sdata)
40   } > dram AT>iram
41   .sbss  ALIGN (0x4) :         
42   { 
43     _fbss = .; 
44     *(dynsbss)*(.sbss)*(.sbss.*)*(.scommon) 
45   } > dram AT>iram
46   .bss   ALIGN (0x4) :         
47   { 
48     *(.dynbss)*(.bss)*(.bss.*) *(COMMON) 
49   } > dram AT>iram
50   _fdata = LOADADDR(.data);
51   _edata = _fdata + SIZEOF(.data) + SIZEOF(.sdata);
52   _ebss = _fbss + SIZEOF(.sbss) + SIZEOF(.bss); 
53   _end = .;
54   PROVIDE (end = .);
55 }

上面的boot匯編代碼,會引用ld腳本中的LMA和VMA地址,然后進行copy或者清零的動作。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM