一.簡介
回憶一下 PC 的體系結構我們可以知道,PC 機中的引導加載程序由 BIOS(其本質就是一段固件程序)和位于硬盤 MBR 中的OS Boot Loader(比如,LILO 和GRUB 等)一起組成。BIOS 在完成硬件檢測和資源分配后,將硬盤 MBR 中的 Boot Loader 讀到系統的 RAM 中,然后將控制權交給 OS Boot Loader。Boot Loader 的主要運行任務就是將內核映象從硬盤上讀到 RAM 中,然后跳轉到內核的入口點去運行,也即開始啟動操作系統。而在嵌入式系統中,通常并沒有像 BIOS 那樣的固件程序(注,有的嵌入式 CPU 也會內嵌一段短小的啟動程序),因此整個系統的加載啟動任務就完全由 Boot Loader 來完成。比如在一個基于 ARM7TDMI core 的嵌入式系統中,系統在上電或復位時通常都從地址0x00000000 處開始執行,而在這個地址處安排的通常就是系統的 Boot Loader 程序。
引導加載程序 。包括固化在固件 (firmware) 中的 boot 代碼 ( 可選 ) ,和 Boot Loader 兩大部分。
Linux 內核 。特定于嵌入式板子的定制內核以及內核的啟動參數。
引導加載程序是系統加電后運行的第一段軟件代碼。
本文將從 BootLoader 的概念、BootLoader 的主要任務、BootLoader 的框架結構以及 BootLoader 的安裝等四個方面來討論嵌入式系統的 BootLoader。
二.Boot Loader 的概念
簡單地說,Boot Loader 就是在操作系統內核運行之前運行的一段小程序。通過這段小程序,我們可以初始化硬件設備、建立內存空間的映射圖,從而將系統的軟硬件環境帶到一個合適的狀態,以便為最終調用操作系統內核準備好正確的環境。
1. Boot Loader 所支持的 CPU 和嵌入式板
每種不同的 CPU 體系結構都有不同的 Boot Loader。有些 Boot Loader 也支持多種體系結構的 CPU,比如 U-Boot 就同時支持 ARM 體系結構和MIPS 體系結構。除了依賴于CPU 的體系結構外,BootLoader 實際上也依賴于具體的嵌入式板級設備的配置。這也就是說,對于兩塊不同的嵌入式板而言,即使它們是基于同一種 CPU 而構建的,要想讓運行在一塊板子上的 Boot Loader 程序也能運行在另一塊板子上,通常也都需要修改Boot Loader 的源程序。
2. Boot Loader 的安裝媒介(Installation Medium)
系統加電或復位后,所有的 CPU 通常都從某個由 CPU 制造商預先安排的地址上取指令。比如,基于 ARM7TDMI core 的 CPU 在復位時通常都從地址 0x00000000 取它的第一條指令。而基于 CPU 構建的嵌入式系統通常都有某種類型的固態存儲設備(比如:ROM、EEPROM 或 FLASH 等)被映射到這個預先安排的地址上。因此在系統加電后,CPU 將首先執行 Boot Loader 程序。
3. 用來控制 Boot Loader 的設備或機制
主機和目標機之間一般通過串口建立連接,Boot Loader 軟件在執行時通常會通過串口來進行 I/O,比如:輸出打印信息到串口,從串口讀取用戶控制字符等。
4. Boot Loader 的啟動過程
Boot Loader 的啟動過程是單階段(Single Stage)還是多階段(Multi-Stage)通常多階段的 Boot Loader 能提供更為復雜的功能,以及更好的可移植性。從固態存儲設備上啟動的 Boot Loader 大多都是 2 階段的啟動過程,也即啟動過程可以分為 stage 1 和stage 2 兩部分。而至于在 stage 1 和 stage 2 具體完成哪些任務將在下面討論。
5. BootLoader 與主機之間進行文件傳輸所用的通信設備及協議
最常見的情況就是,目標機上的 Boot Loader 通過串口與主機之間進行文件傳輸,傳輸協議通常是 xmodem/ymodem/zmodem 協議中的一種。但是,串口傳輸的速度是有限的,因此通過以太網連接并借助 TFTP 協議來下載文件是個更好的選擇。在討論了 BootLoader 的上述概念后,下面我們來具體看看 BootLoader 的應該完成哪些任務。
三. Boot Loader 的主要任務與典型結構框架
在繼續本節的討論之前,首先我們做一個假定,那就是:假定內核映像與根文件系統映像都被加載到 RAM 中運行。之所以提出這樣一個假設前提是因為,在嵌入式系統中內核映像與根文件系統映像也可以直接在 ROM 或 Flash 這樣的固態存儲設備中直接運行。但這種做法無疑是以運行速度的犧牲為代價的。
從操作系統的角度看,Boot Loader 的總目標就是正確地調用內核來執行。另外,由于 Boot Loader 的實現依賴于 CPU 的體系結構,因此大多數 Boot Loader 都分為 stage1 和 stage2 兩大部分。依賴于 CPU 體系結構的代碼,比如設備初始化代碼等,通常都放在 stage1 中,而且通常都用匯編語言來實現,以達到短小精悍的目的。
而 stage2 則通常用C語言來實現,這樣可以實現給復雜的功能,而且代碼會具有更好的可讀性和可移植性。
Boot Loader 的 stage1 通常包括以下步驟(以執行的先后順序):
硬件設備初始化。
為加載 Boot Loader 的 stage2 準備 RAM 空間。
拷貝 Boot Loader 的 stage2 到 RAM 空間中。
設置好堆棧。
跳轉到 stage2 的 C 入口點。
Boot Loader 的 stage2 通常包括以下步驟(以執行的先后順序):
初始化本階段要使用到的硬件設備。
檢測系統內存映射(memory map)。
將 kernel 映像和根文件系統映像從 flash 上讀到 RAM 空間中。
為內核設置啟動參數。
調用內核。
3.1 Boot Loader 的 stage1
3.1.1基本的硬件初始化
這是 Boot Loader 一開始就執行的操作,其目的是為 stage2 的執行以及隨后的 kernel 的執行準備好一些基本的硬件環境。它通常包括以下步驟(以執行的先后順序):
屏蔽所有的中斷。為中斷提供服務通常是 OS 設備驅動程序的責任,因此在 BootLoader 的執行全過程中可以不必響應任何中斷。中斷屏蔽可以通過寫CPU 的中斷屏蔽寄存器或狀態寄存器(比如ARM 的 CPSR 寄存器)來完成。
設置 CPU 的速度和時鐘頻率。
RAM 初始化。包括正確地設置系統的內存控制器的功能寄存器以及各內存庫控制寄存器等。
初始化 LED。典型地,通過 GPIO 來驅動 LED,其目的是表明系統的狀態是 OK 還是 Error。如果板子上沒有 LED,那么也可以通過初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息來完成這一點。
關閉 CPU 內部指令/數據 cache。
3.1.2 為加載 stage2 準備 RAM 空間
為了獲得更快的執行速度,通常把 stage2 加載到 RAM 空間中來執行,因此必須為加載Boot Loader 的 stage2 準備好一段可用的 RAM 空間范圍。由于 stage2 通常是 C 語言執行代碼,因此在考慮空間大小時,除了 stage2 可執行映象的大小外,還必須把堆棧空間也考慮進來。此外,空間大小最好是 memory page 大小(通常是 4KB)的倍數。一般而言,1M的 RAM 空間已經足夠了。具體的地址范圍可以任意安排,比如 blob 就將它的 stage2 可執行映像安排到從系統 RAM 起始地址 0xc0200000 開始的1M空間內執行。
但是,將 stage2 安排到整個 RAM 空間的最頂 1MB(也即(RamEnd-1MB) - RamEnd)是一種值得推薦的方法。
為了后面的敘述方便,這里把所安排的 RAM 空間范圍的大小記為:stage2_size(字節) ,把起始地址和終止地址分別記為:stage2_start 和 stage2_end(這兩個地址均以 4 字節邊界對齊)。因此: stage2_end=stage2_start+stage2_size
另外,還必須確保所安排的地址范圍的的確確是可讀寫的 RAM 空間,因此,必須對你所安排的地址范圍進行測試。
具體的測試方法可以采用類似于 blob 的方法,也即:以 memory page 為被測試單位,測試每個 memory page 開始的兩個字是否是可讀寫的。為了后面敘述的方便,我們記這個檢測算法為:test_mempage,其具體步驟如下:
先保存 memory page 一開始兩個字的內容。
向這兩個字中寫入任意的數字。比如:向第一個字寫入 0x55,第 2 個字寫入 0xaa。
然后,立即將這兩個字的內容讀回。顯然,我們讀到的內容應該分別是 0x55 和 0xaa。如果不是,則說明這個 memory page 所占據的地址范圍不是一段有效的 RAM 空間。
再向這兩個字中寫入任意的數字。比如:向第一個字寫入 0xaa,第 2 個字中寫入0x55。
然后,立即將這兩個字的內容立即讀回。顯然,我們讀到的內容應該分別是 0xaa和 0x55。如果不是,則說明這個 memory page 所占據的地址范圍不是一段有效的 RAM空間。
恢復這兩個字的原始內容。測試完畢。
為了得到一段干凈的 RAM 空間范圍,我們也可以將所安排的 RAM 空間范圍進行清零操作。
評論
查看更多