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或者清零的動作。