鏈結和重定位是嵌入式c中很重要的部分,對於這一塊掌握的越精細越好。
指令分為兩種:
在程式設計編譯鏈結過程會給程式乙個執行位址,而且必須給編譯聯結器指定這個位址,最後得到的二進位制程式是和指定的鏈結位址相關的,這個位址叫做」鏈結位址」。
所以我們在程式編譯時其實就已經知道程式將來執行時的位址,這個位址叫做」執行位址」,執行位址和鏈結位址相關,但是不一定是同乙個,程式執行時必須放在指定的鏈結位址下,否則不能執行,這些程式指令就是位置相關**,我們之前使用的ld鏈結器指定的「-ttext 0x0」就是這個作用,意味著這個程式將來會放在0x0位址去執行。
但是有個別的指令可以和鏈結位址無關,這些**在實際執行時放在**都可以正常執行,這些指令就是位置無關指令。
鏈結位址和執行位址可能相同也可能不同,例如我們的「-ttext 0x0」期望在0x0位址執行,但是實際上我們程式是在記憶體位址0xd0020010位置執行的,這兩個位址看起來不同,但實際上是同乙個,因為s5pv210內部做了對映,把sram0xd0020000位置對映到了0x0這個位址,因為我們把**燒寫在了這個記憶體區域,但是大部分的指令都是位置相關**,這就決定了執行位址和鏈結位址必須相同,否則一定出錯。
在s5pv210中,三星推薦的啟動方式,bootloader必須小於96k,並且必須大於16k,如果bootloader是80k,則啟動過程為:
在uboot啟動過程中,uboot的大小是沒有限制的,假設uboot是200k,則啟動過程為:
鏈結位址和執行位址有時候不能相同,而且不能全部使用位置無關指令,則需要重定位來解決該問題,
執行位址是在程式執行時決定的,在編譯鏈結階段沒有權利也沒有辦法決定程式的執行位址。
鏈結位址是程式在編譯鏈結階段由-ttext或者lds鏈結檔案決定,程式在編譯連線階段,程式設計師期望程式執行在乙個合適的位址,就把這個位址作為程式的鏈結位址。
程式段是程式在編譯之後由原始碼得到的程式的組成部分,每個二進位制程式被分成了若干段,並對每個段命名,段名分為兩種:
- 內建段名:由編譯聯結器內部的命名規則對段的命名
- 自定義段名:由程式設計師自己命名的段名
內建段名一般有以下幾種:
sections
.data :
bss_start = .;
.bss :
bss_end = .;
}
連線指令碼由乙個或者多個sections{}組成:
目標:將**從0xd0020010重定位到0xd0024000,
**本來是從0xd0020010位址開始執行的,因為bl1從這裡開始執行,但是因為特殊原因,我們又需要**從0xd0024000開始執行,這時候就需要重定位,在某些情況下,重定位是必須的,例如uboot.
長跳轉也就是乙個跳轉,在arm中常用分支指令(b,bl)來完成短跳轉,跳轉通常是指令通過給pc暫存器賦值,從而完成**跳轉執行,長跳轉和普通跳轉的區別在於長跳轉的目標位址和當前位址的距離較大,範圍較寬
**重定位之後,我們需要去**被拷貝的目的地的**區域執行,這個 目標位址通常會距離我們當前位址較遠,所以才需要使用重定位,長跳轉示例:ldr pc,=led_blink
,這樣我們就會跳轉到目的地的led_blink函式來執行了,這裡不能使用短跳轉(b,bl),短跳轉會跳轉到執行位址附近的led_blink函式,只有使用長跳轉,才能跳轉到鏈結位址上的led_blink
重定位其實就是,在執行位址位置執行一段位置無關**,這段**將整個程式拷貝到鏈結位址上,然後使用乙個長跳轉,跳轉到鏈結位址的對應函式上去,在鏈結位址上繼續執行,從而實現重定位之後的無縫連線。
重定位**如下:
// 重定位開始
adr r0,_start // adr 用於載入_start執行位址到r0中
ldr r1,=_start // ldr 載入_start的鏈結位址到r1中
// 獲取bss段起始位址,等於重定位**的結束位址
ldr r2,=bss_start // bss_start是在lds鏈結指令碼中指定的
cmp r0,r1 // 比較_start的執行位址和鏈結位址
beq clean_bss // 如果相等就直接跳轉到clean_bss函式
// 如果上一句不相等,則進入**拷貝階段
copy_loop:
ldr r3,[r0],#4 // 拷貝源
str r3,[r1],#4 // 儲存到目的,長度是bss_start - _start,**段+資料段的長度
cmp r1,r2 // 比較拷貝是否完成,也就是到沒到重定位**的結束位址
bne copy_loop // 拷貝沒有完成就繼續迴圈
// 如果拷貝完成,進入clean_bss函式,該函式用於清理bss
clean_bss:
ldr r0,bss_start // 為了滿足c語言的要求,需要清理bss的
ldr r1,bss_end // 一般是不需要手動清理的,這裡我們做了重定位,所以需要手動清理
cmp r0,r1 // 比較bss段起始位址和結束位址
beq run_on_dram // 如果相等則直接進入run_on_dram函式
movr2,#0 // 不相等的話就把r2設定為0
clear_loop: // 開始清理bss
str r2,[r0],#4 // 把r2賦值給r0然後r0+=4
cmp r0,r1
bne clear_loop // 迴圈,直到bss_end
// 所以在這一句之前,必須保證**已經拷貝完畢
run_on_dram:
// 長跳轉到led_blink開始第二階段
ldr pc,=led_blink // 長跳轉
// 重定位完畢
重定位放置在icache開關之後。
sdram是dram的一種,前面的s代表sync,表示具有可同步性,常見的ddr就是sdram的一種,sdram屬於動態記憶體,需要進行初始化之後才能使用,這點和sram是有很大的不同。
sdram屬於soc外部外設,通過位址匯流排和資料匯流排和soc通訊。
ddr初始化完畢之後,這些位址都是可用的,其他位址則不可用。
每個ddr埠都有三類匯流排組成,分別是位址匯流排addr14個,控制匯流排,資料匯流排32個。
x210開發板共使用了4片記憶體,每片128mb,資料匯流排為16bit,通過兩個記憶體之間進行併聯,從而實現了資料匯流排32bit。x210上的每片記憶體分為8個bank,cpu通過ddr埠中的ba0-ba2來選擇bank,每個bank有128mbit的範圍,通過行位址和列位址進行定址,定址範圍是2的24次方(16mb = 128mbit)。
在進行重定位之前,先去執行sdram初始化,可以直接跳轉到sdram的初始化函式:
// 初始化sdram
bl sdram_asm_init // bl短跳轉到sdram_asm_init函式進行sdram初始化
sdram的初始化需要先初始化dram0,然後初始化dram1,步驟在手冊的1.2.1.3
dram初始化之後可以使用dram進行重定位,步驟和之前完全一致。
重定位和鏈結
指令分為兩種 在程式設計編譯鏈結過程會給程式乙個執行位址,而且必須給編譯聯結器指定這個位址,最後得到的二進位制程式是和指定的鏈結位址相關的,這個位址叫做 鏈結位址 所以我們在程式編譯時其實就已經知道程式將來執行時的位址,這個位址叫做 執行位址 執行位址和鏈結位址相關,但是不一定是同乙個,程式執行時必...
重定位和鏈結指令碼
重定位實際就是在執行位址處執行一段位置無關碼pic,讓這段pic 也就是重定位 從執行位址處把整個程式映象拷貝乙份到鏈結位址處,完了之後使用一句長跳轉指令從執行位址處直接跳轉到鏈結位址處去執行同乙個函式,這樣就實現了重定位之後的無縫連線。鏈結位址 鏈結時指定的位址 指定方式為 makefile中用 ...
重定位和鏈結指令碼
但是也有一種特別的指令他可以跟指定的鏈結位址沒有關係,這些 不管放在 都可以正常執行。分析 在linux中的應用程式 gcc hello.c o hello bootloader必須大於16kb小於96kb,假定為80kb。啟動過程如下 開機上電後bl0執行,bl0載入外部啟動裝置中的bootloa...