歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

程序運行地址和加載地址-為什麼使用位置無關指令

在嵌入式編程中,我們經常講程序保存在 nand flash中。但是我們知道,nand flash的接口設計和 RAM 的接口設計是不一樣的。
他的 數據線通常都是復用的,所以通常存取都是以塊為單位(nor flash帶有RAM接口,有足夠的地址線來尋址,
所以可以訪問內存中每一個字節)  這導致了,nand flash不可以片內執行程序(nor flash可以,因為他能存取內存每一個字節)

對於 s3c2440 來說,當使用 nand flash 啟動時,為了解決 nand flash 不能片內執行程序的問題(片內不能執行,那麼程序燒進去不是不能運行嘛)
 於是 s3c2440 在內部有一個 叫 Stepping Stone(墊腳石?)的東西,其實他就是一塊 4KB 的RAM,當我們以 nand flash啟動想運行燒寫在上面
 的程序是,s3c2440會自動將 nand flash 前面 4KB 的內容拷貝到 這個叫Stepping Stone的片內內存中。
 然後 pc指針為0 從這個片內內存0地址開始運行。

但是這個 片內內存只有 4Kb 大小,如果我們燒到 nand falsh中的程序大於 4KB 那麼只有一部分被考到了片內內存中去執行。剩下的就不能執行了。
 
但是我們不是可以接外設嗎, s3c2440的BANK6 和 BANK7都可以接最大 128M的SDRAM。SDRAM是一個RAM(內存)
 
那麼當程序大於 4k 的時候,當我們以 nand flash啟動後,前面的4Kb 被拷貝到 片內RAM中去執行(自動完成)。
我們在這前4K的程序中初始化SDRAM(SDRAM 使用前需要初始化) ,然後將剩下的程序拷貝到 SDRAM中(不是只有4kb 被拷貝到片內RAM中執行了嘛)然後跳轉到 SDRAM中去執行剩下的程序。
 
那麼也就是說 通常當程序大於 4kb的 時候,我們就需要把程序拷貝到SDRAM中去運行。(程序小於4KB 那麼也就可以不用拷貝了,以nand flash方式 啟動後,程序全被拷貝到 片內4kb的 RAM中去運行。)。
 
那麼,既然程序大於4kb的時候需要從nand flash中拷貝到 SDRAM中去運行。自然可以想到 燒到nand flash中的程序前面一部分代碼應該是初始化SDRAM(程序最終需要拷貝到SDRAM中去運行)和 將NAND flash中的剩余的程序拷貝到SDRAM中去(全考過去也行,方便點),然後跳轉到SDRAM中執行。
 
下面我們就要詳細說明,前面這一段跳轉到 SDRAM去執行前在片內內存中運行的初始化代碼的要點和細節。也就是 關於程序運行地址和加載地址以及位置無關指令的 一些注意點和細節

先來看下程序運行地址和加載地址

看個 隨便寫的簡單的示例,這是一個連接腳本中的一段 first 0x30000000 : AT(0){main.o}

我們只注意 0x30000000 和 AT(4096) 這兩處。

如果程序是燒到nand flash中。這句話裡面的意思就是  a.o燒到nand flash 中從0地址(當然也可以是其他數)開始的地方,但他的運行地址是在從0x30000000地址開始的地方。(為什麼是0x30000000,應為 s3c2440的 bank6 和bank7 可以接sdram,bank6從地址0x30000000開始,我們的程序最終是要在SDRAM中運行的)燒到 nand flash中從地址0 開始的地方應該比較好理解,就是說我程序是存儲在nand flash中最開始的地方。那麼運行地址呢怎麼理解呢
 
 看一下斷匯編
  1 .text
  2 .global _start
  3 _start:
  4        mov    r0, #2
  5 loop:
  6        ldr    pc,=loop

 上面這段匯編,我們將 他的運行地址分別設為 0x0 和0x30000000來看看反匯編後的情況

先將運行地址設為0x0
  all: test.c head.s
  2        arm-linux-gcc -c -Wall -o head.o head.s
  3        arm-linux-ld -Ttext 0x0 -o main_elf  head.o
  4        arm-linux-objdump -D -m arm main_elf > main.dis
  5
  6 clean:
  7        rm -rf *.o main.dis main_elf

反匯編 main.dis如下:
 6 00000000 <_start>:
  7    0:  e3a00002        mov    r0, #2  ; 0x2
  8
  9 00000004 :
 10    4:  e51ff004        ldr    pc, [pc, #-4]  ; 8 <.text+0x8>
 11    8:  00000004        andeq  r0, r0, r4

 將    arm-linux-ld -Ttext 0x0 -o main_elf  head.o
 改為    arm-linux-ld -Ttext 0x30000000 -o main_elf  head.o
 也就是將運行地址設定為0x30000000 再看看它的反匯編
  6 30000000 <_start>:
  7 30000000:      e3a00002        mov    r0, #2  ; 0x2
  8
  9 30000004 :
 10 30000004:      e51ff004        ldr    pc, [pc, #-4]  ; 30000008 <.text+0x8>
 11 30000008:      30000004        andcc  r0, r0, r4

注意最左邊的數字,這就代表程序的運行時地址。也就是說程序的代碼的地址是以運行地址為基址來標示的。什麼意思呢, 看 head.s中的  ldr    pc,=loop  這段,這是想讓 程序調回到 loop處(死循環)
 當運行地址設定為 0x0 時 從匯編代碼我們看到 loop標號代表的地址為0000004
 也就是說  ldr    pc,=loop 反匯編為 ldr    pc, [pc, #-4] 即pc值為pc-4地址裡面放的值(4)。就是跳轉到 00000004 地址去
 
 那麼把運行地址設定為0x30000000時, 從反匯編代碼中我們看到 這時 loop表號代表的地址是0x30000000
 那麼 ldr    pc,=loop 就是跳轉到 地址 0x30000000
 
簡單的理解就是 程序運行地址 就是 計算機認為程序運行時應該處於的地址。
所以 運行地址設置為0 時,程序中的所有代碼的中的標號都是以0x0為基址的。計算機認為他運行的時候的地址是從 0x0開始的。
 運行地址設置為0x30000000 時,程序中的所有代碼的中的標號都是以0x30000000為基址的。計算機認為他運行的時候的地址是從0x30000000開始的
 
 
明白了 運行地址 的概念後,我們來看看程序的啟動過程

假設現在程序的 加載地址(燒到nand flash中的位置)為0 運行地址為0x30000000
前面說過程序是燒寫在 nand flash中從 0地址開始的地方,那麼以nand flash方式啟動後,該程序被拷貝到 片內ram中。這時候pc為0。並開始運行程序,也就是說,計算機現在從 片內內存地址0開始運行程序進行一些初始化操作並將sdram初始化後再跳轉到sdram中去運行。
但是我們上面不是說,程序運行地址被設置成了 0x30000000(SDRAM的起始地址)嗎。

但是程序在跳轉到sdram之前卻是在0x0地址開始運行。這就造成了前面這段還未跳轉到sdram(從0x30000000開始)的程序的實際程序運行地址和設定的運行地址不符合。


如果程序中沒有用到 地址有關代碼,和全局變量靜態變量之類地址有關變量,那麼這段 在初始化SDRAM之前 的程序 其實還是可以運行下去的
並在初始化SDRAM後,跳轉到SDRAM中去運行,一切正常
但是如果程序中用了這些代碼。就不會運行成功了。

原因很簡單,應為現在我們設定的 運行地址為 0x30000000,那麼當我們執行地址有關代碼 如 ldr pc,=A (A是個標號或函數名)
就是使用了 絕對地址,那麼 pc = 0x3000000+x (x為標號A相對於起始也就是前面設定的運行地址的偏移) 。

那麼這條指令執行之後,pc 指針將跳轉到 0x30000000後的某處(SDRAM中)。但是 sdram 現在 還未初始化!
這就 造成了 錯誤。
同樣 如果有全局變量 或靜態變量也是。

所以我們需要使用 b bl類的 位置無關指令。  如  b  A
b bl跳轉是基於 pc的 跳轉。即相對跳轉 ,比如執行 b A 這條指令時,假設 現在pc =5. A標號相對當前的位置為2(在之後)
那麼 b A 後 pc =pc +2    即計算機不管當前pc指針是多少,他執行的是相對於當前位置的跳轉。也就避免了 向上面的那樣跳轉到了 0x30000000之後的未初始化的地址中去

Copyright © Linux教程網 All Rights Reserved