首先來看下面的鏈接腳本文件:
ENTRY(_start)
;指定輸出可執行文件的起始代碼段爲_start.
SECTIONS
{
.= BOOTADDR ; bootloader的開始地址/
.= ALIGN(4); 代碼以4字節對齊
.text :;指定代碼段
{
cpu/arch/start.o (.text) ; bootloader中的text段
*(.text) ;其它text段
}
.= ALIGN(4)
.rodata :{*(.rodata)};指定只讀數據段
.= ALIGN(4);
.data :{*(.data)};指定讀/寫數據段
.= ALIGN(4);
__bss_start =.; 把__bss_start賦值爲當前位置,即bss段的開始位置
.bss :{*(.bss)}; 指定bss段
_end =.; 把_end賦值爲當前位置,即bss段的結束位置
}
需要指出的是,鏈接腳本中所描述的輸出段地址爲虛擬地址VMA(VirtualMemoryAddress)。這裏的“虛擬地址”僅指映像文件執行時,各輸出段所重定位到相應的存儲地址空間,與映像文件燒寫到的實際的地址無關(即映像的加載地址)。因此,上面的鏈接腳本實際上指定了Bootloader映像在執行時,將被重定位到BOOTADDR開始的存儲地址空間,以保證在相關位置對符號進行正確引用,使程序正常運行。
假設這裏指定BOOTADDR= 0x0。以ARM爲例,ARM處理器復位後總是從0x0地址取第1條指令,因此只需把BOOTADDR設置爲0,再把編譯後生成的可執行二進制文件下載到ROM的0x0地址開始的存儲空間,程序便可正常引導;但是,一旦在鏈接時指定映像文件從0x0地址開始,那麼Bootloader就只能在0x0地址開始的ROM空間內運行,而無法拷貝到SDRAM空間運行實現快速引導。當然,搬運代碼最後的跳轉語句可以寫成絕對地址,如jmp 0x10000,這樣可以正確的跳到RAM中的0x10000地址處,但當執行繼續執行碰到其他符號地址計算,或全局數據訪問的時候,由於此時不是位置無關代碼,此時地址的計算需要查詢map表,但是map表中的地址仍然在ROM空間中,所以還會跳回ROM空間,另外,還會有其他問題,如動態內存申請等。
有了位置無關代碼,只需修改鏈接腳本文件的BOOTADDR=0x10000即可,即將整個鏡像文件都映射到RAM空間,但是bootloader最開始的搬運代碼必須是位置無關的代碼,這樣雖然搬運代碼被映射到RAM地址空間,但它在0x0開始的ROM中也能正確執行,搬運代碼最後的跳轉語句就可以跳轉到某個標號了,因爲此時標號的地址已經被映射到了ram空間,之後的代碼執行將沒有任何問題。
當然,可以將搬運代碼和搬運完成跳轉到的代碼分段映射,即搬運代碼映射到ROM地址空間,其他代碼映射到RAM空間。但是這樣會存在一個問題:生成的bin文件變得非常大。生成的bin文件將會按照映射到地址空間來生成,如果ROM空間與RAM空間地址不連續,假設ROM地址空間爲0x0 ~ 0x1000 , RAM地址空間爲0x10000~0x20000,那麼, 0x1000~0x10000之間的地址空間都被填充爲0,除非在生成bin文件時重新進行拼裝。
如何編寫位置無關代碼呢?
引用同一位置無關段或相對位置固定的另一位置無關段中的符號時,必須是基於PC的符號引用,即使用相對於當前PC的偏移量來實現跳轉或進行常量訪問。
1.位置無關的程序跳轉。使用相對跳轉指令實現程序跳轉。指令中所跳轉的目標地址用基於當前PC的偏移量來表示,與鏈接時分配給地址標號的絕對地址值無關,因而代碼可以在任何位置進行跳轉,實現位置無關性。
2.位置無關的常量訪問。在應用程序中,經常要讀寫相關寄存器以完成必要的硬件初始化。爲增強程序的可讀性,利用EQU僞指令對一些常量進行賦值,但在訪問過程中,必須實現位置無關性。
3. 使用絕對地址進行跳轉,一般是在不同的位置無關代碼段之間跳轉。
最後,總結一下位置無關代碼段的優點:
1.簡化設計,方便實現系統的快速引導。位置無關代碼可以避免在引導時進行地址映射,並方便地跳轉到RAM中實現快速引導
2.實現復位處理智能化。位置無關的代碼可以被加載到任意地址空間運行
3.便於調試。Bootloader的調試通常也是一個繁瑣的過程,使用位置無關代碼,則可以將映像文件加載到RAM中進行調試,這既能真實地反映程序從ROM中 進行系統引導的情況,又可以避免頻繁燒寫程序存儲器。