原文在此:http://www.cnblogs.com/mddblog/p/4920063.htmlhtml
在嵌入式系統中,啓動文件是整個系統很是關鍵的部分,它會進行一些底層的初始化,構建程序運行必要的環境,好比堆棧初始化,變量初始化等。若是啓動文件出現錯誤,則整個系統就跑不起來,所以研究啓動文件很是必要。函數
在keil中,啓動文件由彙編代碼編寫,通常命名爲startup_xxx.s,xxx爲支持的某種芯片,好比能夠是lpc15xx(NXP的LPC15xx系列)、MK60D10(飛思卡爾)、stm32f10x(意法半導體stm32f10x系列)等Cortext-M0/M3/M4內核芯片。它們的代碼格式很是相近,根據啓動文件代碼由上到下的編寫順序.加密
能夠將其分爲如下5個典型部分:spa
1.堆棧空間定義;指針
2.存放中斷向量表;htm
3.復位中斷函數(Reset_Handler);blog
4.其它中斷異常服務函數,以及弱[WEAK]聲明;get
5.將堆棧地址傳遞給庫函數,利用庫函數初始化堆棧,和庫函數自身初始化。編譯器
5個部分具體說明以下:it
以下圖所示,定義了棧大小Stack_Size = 0X200,即512字節;堆大小Heap_Size = 0X100,256字節。還定義了三個標號:__initial_sp(棧頂)、__heap_base(堆起始地址)和__heap_limit(堆終止地址),它們的空間由SPACE關鍵字來申請,並記做Stack_Mem和Heap_Mem。
經過這些咱們能夠很容易的知道堆棧的大小,可是它們的絕對地址或者說基地址僅僅從這裏是得不到的。編譯器編譯完工程後,根據生成.bss段(好比未初始化的全局變量)和.data段(好比初始化的全局變量)的大小以及RAM的起始地址,來計算堆棧的基地址。
舉個例子:
一個芯片的RAM起始地址爲0x0200_0000,RAM大小爲0x500字節,程序編譯後.bss段爲0x100個字節,.data段爲0x100個字節。堆棧大小定義如上圖。則:
A:堆起始地址 __heap_base==Heap_Mem==0x0200_0200;
B:堆終止地址即棧底 __heap_limit==Stack_Mem==0x0200_0300;
C:棧頂地址 __initial_sp==0x0200_0500(棧是向下生長,棧頂處於RAM最大地址處)。
其實,我能夠在.map文件中查看堆棧的大小和基地址,以下圖所示:
在啓動代碼中,會見到許多由DCD申請空間存放的一個個函數入口,即中斷向量表,以下圖所示,只列出了部分。
關鍵字DCD表明申請一個字的空間,後面的函數名即爲中斷服務函數入口地址。另外中斷向量表通常存放在Flash 0地址。
另外,對於NXP微控制器,均實現了芯片的加密,加密的設置在向量表的結尾處,具體地址爲0x02FC處。經過在此地址存放不一樣的值實現是否加密或者加密的等級。加密分爲三個等級,CRP1:0x12345678;CRP2:0x87654321;CRP3:0x43218765。至於每一個等級的具體說明請參考芯片用戶手冊。下面說一下加密步驟,以CRP1爲例:
首先將下圖中0xFFFFFFFF,修改成0x12345678。
其次,圖中IF :LNOT::DEF:NOCRP表示若是沒有定義宏NOCRP則執行下面的代碼,那麼必須保證彙編中沒有定義NOCRP宏。即保證下圖中Define:一欄中沒有定義NOCRP便可。
程序上電後,首先加載SP和PC,ARM規定從0地址處加載SP,從偏移爲4的地址(0x00000004)處加載PC。而後將程序控制權交給程序。咱們知道0地址處存放__initial_sp,0x00000004地址處存放Reset_Handler,加載PC後,程序跳轉到Reset_Handler開始運行。Reset_Handler函數體以下圖所示:
首先調用SystemInit函數來初始化系統的各類時鐘,而後調用__main函數(由KEIL微庫或者C庫實現),在__main函數中:.data段數據的初始化->.bss段變量清零->設置堆棧指針->庫函數初始化(好比經常使用的malloc函數)->若是必要會設置main函數的argc和argv兩個參數->調用用戶main函數->退出。
如上圖所示,這裏的中斷服務函數是弱聲明的(由[WEAK]關鍵字標註)。所謂弱聲明,即:若是用戶定義了相同的函數則此處函數失效而使用用戶定義的中斷服務函數。這樣是爲了防止用戶使能了中斷而沒有中斷服務函數,從而形成程序崩潰。假設使能了中斷,而用戶又沒有定義中斷服務函數則會進入默認中斷,以下圖所示,默認中斷爲死循環(死循環與程序崩潰不是一個概念)。
第三步驟中,調用__main函數,而後__main調用庫函數初始化堆棧,但庫函數並不知道堆棧的大小,所以咱們須要告訴它,具體作法就是傳遞參數或聲明標號。
下圖爲具體作法,能夠看到第一行爲:
IF :DEF:__MICROLIB
是條件編譯選項,若是定義__MICROLIB,則編譯圖中紅線上面部分,不然編譯紅線下面部分。那麼就分2種狀況。
2種狀況的選擇能夠以下實現:
若是勾選【Options for Target】->【Target】->【Use MicroLIB】,以下圖所示。即便用微庫,則__MICROLIB會被定義,編譯器編譯紅線以上代碼。用EXPORT聲明 __initial_sp、__heap_base和__heap_limit。
若是不勾選【Use MicroLIB】,則缺省使用KEIL C庫,上圖紅線如下會參與編譯,KEIL C庫函數會調用__user_initial_stackheap,經過R0~R3將堆棧以參數形式傳遞給KEIL C庫。