基於C程式啟動代碼的深入分析

來源:互聯網
上載者:User

一、映像檔案基本組成
映像檔案載入時域包括RO和RW段,運行時域則包括RO、RW和ZI三個段。其中RO和RW段的 內容載入時和運行時是一樣的,只是儲存空間可能不同, 而ZI段則是運行時由初始化函數建立的。
RO段:Read-Only段,包括來源程式中的 CODE段唯讀資料區段(包括變數的初始化 ——可以是任意變數,全域/局部、靜態/動態變數的初值;還包括資料常量——這個常量也可以是全域的或局部的。也就是說,編譯器既要為 變數分配儲存空間——變數是可讀寫的,並不放在RO段,又要為 變數的初值分配儲存空間,兩者是兩回事)。
RW段: 可讀寫段,主要指RW-DATA,也可能有RW-CODE。RW-DATA是指已經 初始化的全域變數。
ZI段:Zero-Initialized段,主要包括未初始化的全域變數,編譯器用0值對其進行初始化。該段中的資料由於是變數,因而也是可讀寫的,但在映像檔案載入時,並不為ZI段分配儲存空間,雖然在ADS編譯器的Memory map檔案中認為Total RW Size = (RW Data + ZI Data)。

二、代碼,資料和變數在映像檔案中的位置
上面簡單總結了映像檔案各段的組成。從程式的組成看,可以分為變數、資料和代碼,其中變數又分為全域/局部的或靜態/動態,它們的儲存空間又是如何分配的呢?
代碼:一般是唯讀,由編譯器分配儲存空間並放到映像檔案的RO段。
資料:這裡所指的資料都是常量(若可變則為變數),也包括指標常量,那麼也屬於唯讀資料,也由編譯器分配儲存空間放到映像檔案的RO段。
變數:主要根據生存期來分,因為生存期是按在記憶體中的存留時間來定義的,而範圍與儲存空間分配無關。
1.全域變數和靜態變數:包括靜態局部變數和全域/靜態指標變數在內,由編譯器分配儲存空間,已初始化的放到RW段,否則放到ZI段;
2.動態變數:主要是指局部變數,包括局部指標變數,函數參數,傳回值等在內,佔用棧空間。

三、啟動過程中的堆棧初始化釋疑
堆與棧:對於ARM,堆是向上生長的,棧是向下生長的。
局部變數佔用棧(stack)空間(但其初始化值為資料,佔用RO空間);
程式中動態申請的如malloc()和new函數申請的記憶體空間佔用堆(heap)空間。
————×以下討論不使用semihosting機制×————
因此,在轉入C應用程式前,必須要為C程式準備堆棧空間。根據具體的目標平台的儲存空間資源,要對堆棧的初始化函數__user_initial_stackheap( )進行移植,主要是正確設定堆(heap)和棧(stack)的地址。它可以使用C或ARM組合語言來編寫,並至少返回堆基址(儲存在R0中),棧基址(儲存在R1)可選。因而一個簡單的組合語言編寫的__user_initial_stackheap( )函數如下:
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDRR0, =0x20000 ;heap base
LDRR1, =0x40000 ;stack base, optional
MOV PC, R14
注意,如果在工程中沒有自訂這個函數,那麼預設情況下,編譯器/連結器會把|Image$$ZI$$Limit|作為堆(heap)的基址(即把heap和stack區放置在ZI地區的上方,這也被認為是標準的實現[7])。但是,如果使用scatter檔案實現分散載入機制,連結器並不產生符號|Image$$ZI$$Limit|,這時就必須自己重新實現__user_initial_stackheap( )函數並且設定好堆基址和棧頂,否則連結時會報錯。
堆棧區還分為單區模型和雙區模型,在雙區模型中,還必須設定堆棧限制[4,6,7]。
關於重定義__user_initial_stackheap( )函數時幾點要注意的地方:一是不要使用超過96位元組的stack,二是不要影響到R12(IP,用作進程間調用的暫存寄存器),三是按規則返回參數值(R0:heap base;R1:stack base;R2:heap limit;R3:stack limit),四是讓堆區保持8位元組對齊[6]。

在啟動代碼中,還要對各個處理器模式的棧指標進行初始化。這個問題很容易與上面談到的__user_initial_stackheap()函數的作用相混淆。可從以下幾點來加以說明:
(1)在嵌入式應用中,啟動代碼分為兩個部分:一是系統的初始化,包括中斷向量表的建立、時鐘、儲存系統初始化、關鍵I/O口初始化、各處理器模式下的棧指標初始化等;二是應用程式初始化(或說C庫函數初始化),包括RW段的搬移和ZI段的清零、C應用程式堆棧區的建立(__user_initial_stackheap()函數初始化堆棧指標)等。
從這個意義上說,兩者並沒有直接關係。
(2)但兩者並不是沒有聯絡的。以單區模型的堆棧區為例,由於棧是向下生長的,堆是向上生長的,系統模式的棧指標(與使用者模式相同,共用一個R13寄存器來描述)實際上定義了使用者模式下單區模型堆棧區的上限,而__user_initial_stackheap()函數中指定的heap基址則成為該堆棧區的下限。
因此,如果之前已經對系統模式(使用者模式)的棧指標進行了初始化,則在重定義__user_initial_stackheap()函數時,就不需要重新定義stack base了。

四、啟動代碼的內容和初始化順序探討
前面已經指出,啟動程式碼封裝括系統初始化以及應用程式運行環境的初始化兩個部分,完成初始化後,就可以呼叫使用者主程式了。參考資料[1]、[3]和[5]等都對兩個部分的內容以及過程列出了非常清晰但又簡單明了的步驟,這對於初學者來說稍微有點抽象。
如果不需要使用MMU進行地址重新對應,那麼,結合網上可以搜集的樣本boot代碼以及分析文檔,加上自己動手移植和調試,也是比較容易理解的。如果是使用處理器內建的Remap控制寄存器來進行地址重新對應,網上也有相關的代碼,例如網友twentyone的boot代碼【4510 bootloader的實現與分析(附原始碼)】就非常清楚,另外,在《ARM學習報告》系列文章中也對其有詳細的分析。
對於在啟動過程中要使用MMU進行地址重新對應的系統初始化順序,在《使用AXD調試MMU地址對應程式手記(二)》一文中給出了一個參考步驟,並做了一定的說明。通過進一步參考權威資料,這裡,對系統初始化順序作了小的改進與修正如下:
①禁止所有中斷→②初始化時鐘→③初始化儲存空間→④初始化各模式下的棧指標→⑤初始化GPIO→⑥拷貝映像檔案到SDRAM→⑦建立地址重新對應表→⑧使能MMU→⑨應用程式初始化(RW&ZI區)→⑩使能異常中斷→⑾呼叫主程式(dummyOS)。
主要對使能異常中斷和應用程式初始化的順序做了調整,即先進行應用程式的初始化,再使能異常中斷。
......

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.