BootLoader指系統啟動后,在操作系統內核運行之前運行的一段小程序。通過BootLoader,我們可以初始化硬件設備、建立內存空間的映射圖,從而將系統的軟硬件環境帶到一個合適的狀態,以便為最終調用操作系統內核准備好正確的環境。通常,BootLoader是嚴重地依賴於硬件而實現的,特別是在嵌入式世界。因此,在嵌入式世界里建立一個通用的 BootLoader 幾乎是不可能的。盡管如此,我們仍然可以對BootLoader歸納出一些通用的概念來,以指導用戶特定的BootLoader設計與實現。
BootLoader的操作模式一般分為自啟動模式和交互模式。
自啟動模式:BootLoaderd從目標機上的某個固態設備上將操作系統加載到RAM中運行,整個過程沒有用戶的介入;
交互模式:目標機上的BootLoader將通過串口或網絡等通信手段從開發板上下載內核映像和根文件系統映像等到RAM中,可以寫到目標機上的固態存儲介質中,或者直接進行系統的引導。也可以通過串口接收用戶的命令。
BootLoader基本功能:
初始化相關硬件;
把BootLoader自搬移到內存中;
執行用戶的命令(訪問環境變量;通過網絡/串口通信;讀寫RAM/Flash);
加載並執行內核。
一個嵌入式Linux系統從軟件的角度看通常可以分為四個部分:BootLoader、Linux內核、跟文件系統及用戶的應用程序。BootLoader處於系統的最底層,運行於系統啟動的最初階段。
系統加電或復位后,所有CPU都會從某個地址開始執行,這是由處理器設計決定的。比如,X86的復位向量在高地址端,ARM處理器在復位時從地址0x00000000取第一條指令。嵌入式系統的開發板都要把板上ROM或Flash映射到這個地址。因此,必須把Bootloader程序存儲在相應的Flash位置。系統加電后,CPU將首先執行它。
BootLoader的啟動過程可以是單階段的,也可以是多階段的。多階段一般比單階段的提供更為復雜的功能,以及更好的可移植性。從固態存儲設備上啟動的bootloader大多數是二階段的啟動過程。
BootLoader 的實現依賴於CPU的體系結構,因此大多數 BootLoader 都分為stage1 和stage2 兩大部分。依賴於CPU體系結構的代碼,比如設備初始化代碼等,通常都放在 stage1中,而且通常都用匯編語言來實現,以達到短小精悍的目的。而stage2 則通常用C 語言來實現,這樣可以實現更復雜的功能,而且代碼會具有更好的可讀性和可移植性。
BootLoader 的 stage1 通常包括以下步驟:
·硬件設備初始化;//屏蔽所有的中斷、關閉處理器內部指令/數據Cache等
·為加載BootLoader的stage2准備RAM空間;
·拷貝BootLoader的stage2 到RAM空間中;
·設置好堆棧並將bss段清零;
·跳轉到 stage2 的 C 入口點。
Boot Loader的stage2通常包括以下步驟:
·初始化本階段要使用到的硬件設備;
·檢測系統內存映射(memory map);
·將內核映像和根文件系統映像從flash上讀到 RAM 空間中;
·為內核設置啟動參數;
·調用內核。
為什么bootloader的初始部分要用匯編?一種解釋是有些操作必須用匯編實現,如協處理器寄存器的操作。更重要的問題在於,c程序需要一個具體的運行環境,如代碼段,初始化的數據段,BSS段,棧,堆等。尤其是棧,它承擔着C函數調用參數傳遞,局部變量的存儲等工作。再者,啟動時僅有Nand Flash的前4K內容在stepping stone中運行,這如何能保證C程序的完整性呢?因此,通常的做法是將第一階段的匯編代碼在單獨的模塊實現,並鏈接到程序的開始處。然后有它將完整的bootloader程序映像文件從Nand Flash中搬運至SDRAM中,並設置好上面談到的各個段。這么以來,第二階段的代碼就會在SDRAM中運行。
第一階段匯編代碼的入口處,一般首先放置的是cpu異常的跳轉代碼,如IRQ,FIQ,SWI,Undef等。中斷源將中斷請求送至cpu的中斷控制器,通過中斷控制器仲裁,決定被響應與否或響應的順序。例如IRQ異常,cpu會跳轉到IRQ異常跳轉指令處,該指令修改pc地址使其指向IRQ異常處理例程。在處理歷程中,程序通過判斷中斷源的偏移量,確定該IRQ異常的具體類型,計算出這種IRQ異常的中斷響應函數,這個過程是通過查閱IRQ中斷向量表來實現的。IRQ中斷向量表中定義了具體IRQ中斷響應函數的地址,這些地址可以在第二階段根據需要而設置,例如Timer的中斷響應函數等等。
第二階段首先做的是設置時鍾。復位后,cpu使用外部時鍾源,而非MPLL。通過設置MPLL,從而初始化HCLK,FCLK和PCLK,后者給cpu和外設提供穩定的時鍾。接着可以對中斷控制器和串口進行初始化,這樣就可以通過串口向PC終端輸出一些交互信息了。
接着打開MMU,指令緩存和數據緩存。這里可以設置協處理器CP15的Register 13(ProcID)為0,並建立從虛地址到物理地址的直接映射關系。
以上工作完成后,將存放於Nand Flash中的kernel和啟動參數搬運到內存中的特定區域,重新設置好時鍾(與內核中的保持一致),關閉MMU,指令緩存和數據緩存,跳轉到內核的起始地址就可以運行了。