1. 程式人生 > >嵌入式Linux學習:重定位(Relocation)

嵌入式Linux學習:重定位(Relocation)

引用了http://blog.csdn.net/zhaocj/article/details/6636175博文的圖片

引用了《嵌入式Linux應用開發完全手冊》P260的圖片

這個連結裡包含了一個簡單的例項!


筆者也是嵌入式新手,以下博文只是我對uboot初淺的認識,有任何問題歡迎指正

--------------------------------------------正文開始----------------------------------------

什麼是重定位?

上面解釋了兩個概念:載入地址和執行地址,這裡在簡單總結一下,就是程式設計師將所有的程式碼編譯完以後按照一定的順序進行存放到FLASH裡,而且在存放的過程中設定了連線地址,這個連線地址就是後期我們希望這個程式碼能夠執行的地址(而不是SRAM或者在FLASH中XIP,一般就在SDRAM中),上電完成後程式一般執行在FLASH或者SRAM,然後通過適當的複製程式將原本在FLASH中的資料複製到上述指定的連線地址,然後再跳到SDRAM中繼續執行。這個是舊版本的uboot中的策略。

這樣的策略最終讓(一般連線地址就是SDRAM的0x33F80000)uboot處於SDRAM的中低端空間,再在其0x34000000後面放置kernal的程式碼;(這樣的策略直接簡單,一刀切的進行了SDRAM的空間劃分,使得SDRAM的利用率偏低);

為了最儘可能的利用SDRAM資源,在上電完成後,執行在FALSH或者SRAM中的程式將整個uboot程式複製到了SDRAM的頂端開始,依次開始對SDARM進行空間劃分。這樣的結果就是uboot放置在SDRAM中的實際位置和連線地址會存在一個偏差,這種偏差會導致程式跑飛!

圖 1 兩種策略的在uboot進入第二階段時的SDRAM分佈

從上面的圖可以看到這兩種策略所引起的不同,左圖的uboot起始位置確定為0x33F80000,而右圖的uboot的起始位置實際上是不確定的,因為uboot的.text和.bss段實際的大小是在連線時才最終確定,所以這裡的連線地址的意義已經名存實亡,因為它和最終的執行地址已經存在一個offset;因為存在這麼一個offset,所以uboot的程式需要重定位!

圖 2 策略1將uboot資料複製到了SDRAM的TEXT_BASE後,兩者執行的轉跳圖

先看看策略1,加入現在已經完成了程式的複製工作(即將FLASH中相應的資料複製到了指定的TEXT_BASE位置),如果這個時候程式若還在FLASH(或SRAM)中執行:執行ldr pc,=lable,實際上就是讀取offset處(FLASH中的offset)的資料,將這個資料賦值給pc,見上圖左虛線部分就是取讀取offset處的資料,但是這個地址實際上是指向SDRAM中的lable(並不是指向FLASH中的lable,連線地址的作用),所以這個時候就完成了從FLASH(或者SRAM)到SDRAM的轉跳;

圖 3 跳到SDRAM後的執行情況

當完成FLASH到SDRAM的轉跳後,程式已經執行在了SDRAM,那麼後續的情況會如何呢,如此時要執行ldr pc,=lable2,實際上就是讀取offset2處(SDRAM中的offset2)的資料,將這個資料賦值給pc,見上圖右虛線部分就是取讀取offset2處的資料,這個地址實際上仍指向SDRAM中的lable2(並不是指向FLASH中的lable2,連線地址的作用),所以這個時候程式正常轉跳,且還在SDRAM中執行;

通過上面的解釋,我們就明白了策略1的連線地址和後續需要複製的SDRAM的偏移地址就應該是一樣的;那麼如果此時將uboot複製到另外一個地方,情況會怎樣?

圖 4簡單的將uboot複製到SDRAM2中,而不是複製到相應的SDRAM1(TEXT_BASE所指向)

有上圖可知,CPU在FLASH中執行時,執行ldr pc,=lable,實際上就是讀取offset處(FLASH中的offset)的資料,將這個資料賦值給pc,見上圖左虛線部分就是取讀取offset處的資料,程式按照我們的預設跳到了SDRAM1(TEXT_BASE所指向)後面的某處,但是這個地址上並沒有資料(因為我們並沒有把資料複製到這個地方!),此時程式跑飛!

而如果不經過重定位,我們強行將程式執行在SDRAM2上會如何?

圖 5 若強行執行結果會怎麼樣?

同樣的,才是CPU執行在SDRAM2中,執行ldr pc,=lable2,實際上就是讀取offset2處(SDRAM2中的offset2)的資料,將這個資料賦值給pc,見上圖右虛線部分就是取讀取offset2處的資料,這個時候程式仍舊是轉跳到了SDRAM1中,程式仍然跑飛!

解決辦法就是重定位!

通過上面的例項我們知道了,實際上如果此時強行將CPU指標指向SDRAM2,那麼為了能夠正常的執行我們的程式,我們需要做的就是修改SDRAM2中offset2處所保留的資料,因為這個資料保留了接下來CPU要轉跳的地址,那麼靠的就是rel_dyn段!上面的圖片中的FLASH都有rel_dyn段,但是他們都沒有被複制到SDRAM,那麼他們的作用如何實現,接下來仍舊用圖片來解決問題;

圖 6 重定位的過程

首先在FLASH的,有一個數據段叫做rel_dyn,其中儲存了很多地址,這些地址都指向了SDRAM1中的虛擬offset(為什麼叫虛擬呢,因為我們uboot資料被複制到了SDRAM2中,SDRAM1中相應地方的資料為空),而虛擬offset中的資料就是虛擬lable的地址;我們再來重新解釋下上面沒有重定位的SDRAM2為什麼執行失敗:那是因為我們SDRAM2中的offset2中保留的資料,實際上指向了SDRAM1中的虛擬lable!

繼續講重定位;

假如,我們獲得了REL_OFFSET,就是SDRAM2餘SDRAM1的偏移;rel_dyn中有一個數據RD1,把這個資料作為地址,它指向的恰好就是SDRAM1的虛擬offset2(上圖棕色實線),那麼只要將rel_dyn加上REL_OFFSET,那麼RD1作為地址就可以指向SDRAM2中的offset2(上圖棕色虛線);

這樣我們就獲取到了SDRAM2中的offset2,它裡面所包含的資料作為地址,實際上指向了SDRAM1中的虛擬lable2(上圖黑色實線),那麼只要將SDRAM2中的offset2裡的資料也加上REL_OFFSET就可以指向正確的SDRAM2中的lable2(上圖黑色虛線),這樣即使CPU在SDRAM2中也就能夠正常運行了,而不會跑飛;

那麼關鍵就是修改SDRAM2中offset2中的資料(修改還分兩種,絕對和相對,這裡暫不展開),而這個恰恰就是重定位所需要完成的任務;而如何獲得offset2的地址,靠的就是rel_dyn中所保留的原始資料!而rel_dyn中所保留的資料是由gcc的某個編譯選項獲得