- 硬件設(shè)備初始化。
- 為加載 Boot Loader 的 stage2 準備 RAM 空間。
- 拷貝 Boot Loader 的 stage2 到 RAM 空間中。
- 設(shè)置好堆棧。
- 跳轉(zhuǎn)到 stage2 的 C 入口點。
Boot Loader 的 stage2 通常包括以下步驟(以執(zhí)行的先后順序):
- 初始化本階段要使用到的硬件設(shè)備。
- 檢測系統(tǒng)內(nèi)存映射(memory map)。
- 將 kernel 映像和根文件系統(tǒng)映像從 flash 上讀到 RAM 空間中。
- 為內(nèi)核設(shè)置啟動參數(shù)。
- 調(diào)用內(nèi)核。
3.1 Boot Loader 的 stage1
3.1.1基本的硬件初始化
這是 Boot Loader 一開始就執(zhí)行的操作,其目的是為 stage2 的執(zhí)行以及隨后的 kernel 的執(zhí)行準備好一些基本的硬件環(huán)境。它通常包括以下步驟(以執(zhí)行的先后順序):
- 屏蔽所有的中斷。為中斷提供服務(wù)通常是 OS 設(shè)備驅(qū)動程序的責(zé)任,因此在 BootLoader 的執(zhí)行全過程中可以不必響應(yīng)任何中斷。中斷屏蔽可以通過寫CPU 的中斷屏蔽寄存器或狀態(tài)寄存器(比如ARM 的 CPSR 寄存器)來完成。
- 設(shè)置 CPU 的速度和時鐘頻率。
- RAM 初始化。包括正確地設(shè)置系統(tǒng)的內(nèi)存控制器的功能寄存器以及各內(nèi)存庫控制寄存器等。
- 初始化 LED。典型地,通過 GPIO 來驅(qū)動 LED,其目的是表明系統(tǒng)的狀態(tài)是 OK 還是 Error。如果板子上沒有 LED,那么也可以通過初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息來完成這一點。
- 關(guān)閉 CPU 內(nèi)部指令/數(shù)據(jù) cache。
3.1.2 為加載 stage2 準備 RAM 空間
為了獲得更快的執(zhí)行速度,通常把 stage2 加載到 RAM 空間中來執(zhí)行,因此必須為加載Boot Loader 的 stage2 準備好一段可用的 RAM 空間范圍。由于 stage2 通常是 C 語言執(zhí)行代碼,因此在考慮空間大小時,除了 stage2 可執(zhí)行映象的大小外,還必須把堆?臻g也考慮進來。此外,空間大小最好是 memory page 大小(通常是 4KB)的倍數(shù)。一般而言,1M的 RAM 空間已經(jīng)足夠了。具體的地址范圍可以任意安排,比如 blob 就將它的 stage2 可執(zhí)行映像安排到從系統(tǒng) RAM 起始地址 0xc0200000 開始的1M空間內(nèi)執(zhí)行。
但是,將 stage2 安排到整個 RAM 空間的最頂 1MB(也即(RamEnd-1MB) - RamEnd)是一種值得推薦的方法。
為了后面的敘述方便,這里把所安排的 RAM 空間范圍的大小記為:stage2_size(字節(jié)) ,把起始地址和終止地址分別記為:stage2_start 和 stage2_end(這兩個地址均以 4 字節(jié)邊界對齊)。因此: stage2_end=stage2_start+stage2_size
另外,還必須確保所安排的地址范圍的的確確是可讀寫的 RAM 空間,因此,必須對你所安排的地址范圍進行測試。
具體的測試方法可以采用類似于 blob 的方法,也即:以 memory page 為被測試單位,測試每個 memory page 開始的兩個字是否是可讀寫的。為了后面敘述的方便,我們記這個檢測算法為:test_mempage,其具體步驟如下:
- 先保存 memory page 一開始兩個字的內(nèi)容。
- 向這兩個字中寫入任意的數(shù)字。比如:向第一個字寫入 0x55,第 2 個字寫入 0xaa。
- 然后,立即將這兩個字的內(nèi)容讀回。顯然,我們讀到的內(nèi)容應(yīng)該分別是 0x55 和 0xaa。如果不是,則說明這個 memory page 所占據(jù)的地址范圍不是一段有效的 RAM 空間。
- 再向這兩個字中寫入任意的數(shù)字。比如:向第一個字寫入 0xaa,第 2 個字中寫入0x55。
- 然后,立即將這兩個字的內(nèi)容立即讀回。顯然,我們讀到的內(nèi)容應(yīng)該分別是 0xaa和 0x55。如果不是,則說明這個 memory page 所占據(jù)的地址范圍不是一段有效的 RAM空間。
- 恢復(fù)這兩個字的原始內(nèi)容。測試完畢。
為了得到一段干凈的 RAM 空間范圍,我們也可以將所安排的 RAM 空間范圍進行清零操作。
3.1.3 拷貝 stage2 到 RAM 中
拷貝時要確定兩點:
(1) stage2 的可執(zhí)行映象在固態(tài)存儲設(shè)備的存放起始地址和終止地址;
(2) RAM 空間的起始地址。
3.1.4 設(shè)置堆棧指針 sp
堆棧指針的設(shè)置是為了執(zhí)行 C 語言代碼作好準備。通常我們可以把 sp 的值設(shè)置為(stage2_end-4),也即在 3.1.2 節(jié)所安排的那個 1MB 的 RAM 空間的最頂端(堆棧向下生長)。
此外,在設(shè)置堆棧指針 sp 之前,也可以關(guān)閉 led 燈,以提示用戶我們準備跳轉(zhuǎn)到 stage2。
經(jīng)過上述這些執(zhí)行步驟后,系統(tǒng)的物理內(nèi)存布局應(yīng)該如下圖2所示。
3.1.5 跳轉(zhuǎn)到 stage2 的 C 入口點
在上述一切都就緒后,就可以跳轉(zhuǎn)到 Boot Loader 的 stage2 去執(zhí)行了。
比如,在 ARM 系統(tǒng)中,這可以通過修改 PC 寄存器為合適的地址來實現(xiàn)。
3.2 Boot Loader 的stage2
正如前面所說,stage2 的代碼通常用 C 語言來實現(xiàn),以便于實現(xiàn)更復(fù)雜的功能和取得更好的代碼可讀性和可移植性。
但是與普通 C 語言應(yīng)用程序不同的是,在編譯和鏈接boot loader 這樣的程序時,我們不能使用 glibc 庫中的任何支持函數(shù)。其原因是顯而易見的。這就給我們帶來一個問題,那就是從那里跳轉(zhuǎn)進 main() 函數(shù)呢?直接把 main() 函數(shù)的起始地址作為整個 stage2 執(zhí)行映像的入口點或許是最直接的想法。但是這樣做有兩個缺點:
1)無法通過main() 函數(shù)傳遞函數(shù)參數(shù);
2)無法處理 main() 函數(shù)返回的情況。
一種更為巧妙的方法是利用 trampoline(彈簧床)的概念。也即,用匯編語言寫一段trampoline 小程序,并將這段 trampoline 小程序來作為 stage2 可執(zhí)行映象的執(zhí)行入口點。然后我們可以在 trampoline 匯編小程序中用 CPU 跳轉(zhuǎn)指令跳入 main() 函數(shù)中去執(zhí)行;而當(dāng) main() 函數(shù)返回時,CPU 執(zhí)行路徑顯然再次回到我們的 trampoline 程序。簡而言之,這種方法的思想就是:用這段 trampoline 小程序來作為main() 函數(shù)的外部包裹(external wrapper)。
下面給出一個簡單的 trampoline 程序示例(來自blob):
.text
.globl _trampoline
_trampoline:
bl main
/* if main ever returns we just call it again */
b _trampoline
可以看出,當(dāng) main() 函數(shù)返回后,我們又用一條跳轉(zhuǎn)指令重新執(zhí)行 trampoline 程序,當(dāng)然也就重新執(zhí)行 main() 函數(shù),這也就是 trampoline(彈簧床)一詞的意思所在。
3.2.1初始化本階段要使用到的硬件設(shè)備
這通常包括:
(1)初始化至少一個串口,以便和終端用戶進行 I/O 輸出信息;
(2)初始化計時器等。
在初始化這些設(shè)備之前,也可以重新把 LED 燈點亮,以表明我們已經(jīng)進入 main() 函數(shù)執(zhí)行。
設(shè)備初始化完成后,可以輸出一些打印信息,程序名字字符串、版本號等。
3.2.2 檢測系統(tǒng)的內(nèi)存映射(memory map)
所謂內(nèi)存映射就是指在整個 4GB 物理地址空間中有哪些地址范圍被分配用來尋址系統(tǒng)的RAM 單元。
比如,在 SA-1100 CPU 中,從 0xC000,0000 開始的512M地址空間被用作系統(tǒng)的 RAM 地址空間,而在 Samsung S3C44B0X CPU 中,從 0x0c00,0000 到 0x1000,0000 之間的64M地址空間被用作系統(tǒng)的 RAM 地址空間。雖然 CPU 通常預(yù)留出一大段足夠的地址空間給系統(tǒng) RAM,但是在搭建具體的嵌入式系統(tǒng)時卻不一定會實現(xiàn) CPU 預(yù)留的全部 RAM 地址空間。也就是說,具體的嵌入式系統(tǒng)往往只把 CPU 預(yù)留的全部 RAM 地址空間中的一部分映射到 RAM 單元上,而讓剩下的那部分預(yù)留 RAM 地址空間處于未使用狀態(tài)。
由于上述這個事實,因此 Boot Loader 的 stage2 必須在它想干點什么 (比如,將存儲在 flash 上的內(nèi)核映像讀到 RAM 空間中) 之前檢測整個系統(tǒng)的內(nèi)存映射情況,也即它必須知道CPU 預(yù)留的全部 RAM 地址空間中的哪些被真正映射到 RAM 地址單元,哪些是處于 "unused" 狀態(tài)的。
(1) 內(nèi)存映射的描述
可以用如下數(shù)據(jù)結(jié)構(gòu)來描述 RAM 地址空間中的一段連續(xù)(continuous)的地址范圍:
typedef struct memory_area_struct {
u32 start; /* the base address of the memory region */
u32 size; /* the byte number of the memory region */
int used;
} memory_area_t;
這段 RAM 地址空間中的連續(xù)地址范圍可以處于兩種狀態(tài)之一:
(1)used=1,則說明這段連續(xù)的地址范圍已被實現(xiàn),也即真正地被映射到 RAM 單元上。
(2)used=0,則說明這段連續(xù)的地址范圍并未被系統(tǒng)所實現(xiàn),而是處于未使用狀態(tài)。
基于上述 memory_area_t 數(shù)據(jù)結(jié)構(gòu),整個 CPU 預(yù)留的 RAM 地址空間可以用一個 memory_area_t 類型的數(shù)組來表示,如下所示:
memory_area_t memory_map[NUM_MEM_AREAS] = {
[0 ... (NUM_MEM_AREAS - 1)] = {
.start = 0,
.size = 0,
.used = 0
},
};
(2) 內(nèi)存映射的檢測
下面我們給出一個可用來檢測整個 RAM 地址空間內(nèi)存映射情況的簡單而有效的算法:
/* 數(shù)組初始化 */
for(i = 0; i < NUM_MEM_AREAS; i++)
memory_map[i].used = 0;
/* first write a 0 to all memory locations */
for(addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE)
* (u32 *)addr = 0;
for(i = 0, addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE) {
/*
* 檢測從基地址 MEM_START+i*PAGE_SIZE 開始,大小為
* PAGE_SIZE 的地址空間是否是有效的RAM地址空間。
*/
調(diào)用3.1.2節(jié)中的算法test_mempage();
if ( current memory page isnot a valid ram page) {
/* no RAM here */
if(memory_map[i].used )
i++;
continue;
}
/*
* 當(dāng)前頁已經(jīng)是一個被映射到 RAM 的有效地址范圍
* 但是還要看看當(dāng)前頁是否只是 4GB 地址空間中某個地址頁的別名?
*/
if(* (u32 *)addr != 0) { /* alias? */
/* 這個內(nèi)存頁是 4GB 地址空間中某個地址頁的別名 */
if ( memory_map[i].used )
i++;
continue;
}
/*
* 當(dāng)前頁已經(jīng)是一個被映射到 RAM 的有效地址范圍
* 而且它也不是4GB 地址空間中某個地址頁的別名。
*/
if (memory_map[i].used == 0) {
memory_map[i].start = addr;
memory_map[i].size = PAGE_SIZE;
memory_map[i].used = 1;
} else {
memory_map[i].size += PAGE_SIZE;
}
} /* end of for (…) */
在用上述算法檢測完系統(tǒng)的內(nèi)存映射情況后,Boot Loader 也可以將內(nèi)存映射的詳細信息打印到串口。
3.2.3 加載內(nèi)核映像和根文件系統(tǒng)映像
(1)規(guī)劃內(nèi)存占用的布局
這里包括兩個方面:





