嵌入式系統(tǒng) Boot Loader 技術(shù)內(nèi)幕
本文詳細(xì)地介紹了基于嵌入式系統(tǒng)中的 os 啟動(dòng)加載程序 ―― boot loader 的概念、軟件設(shè)計(jì)的主要任務(wù)以及結(jié)構(gòu)框架等內(nèi)容。 在專用的嵌入式板子運(yùn)行 gnu/linux 系統(tǒng)已經(jīng)變得越來越流行。一個(gè)嵌入式 linux 系統(tǒng)從軟件的角度看通??梢苑譃樗膫€(gè)層次: 1. 引導(dǎo)加載程序。包括固化在固件(firmware)中的 boot 代碼(可選),和 boot loader 兩大部分。 2. linux 內(nèi)核。特定于嵌入式板子的定制內(nèi)核以及內(nèi)核的啟動(dòng)參數(shù)。 3. 文件系統(tǒng)。包括根文件系統(tǒng)和建立于 flash 內(nèi)存設(shè)備之上文件系統(tǒng)。通常用 ram disk 來作為 root fs。 4. 用戶應(yīng)用程序。特定于用戶的應(yīng)用程序。有時(shí)在用戶應(yīng)用程序和內(nèi)核層之間可能還會(huì)包括一個(gè)嵌入式圖形用戶界面。常用的嵌入式 gui 有:microwindows 和 minigui 懂。 引導(dǎo)加載程序是系統(tǒng)加電后運(yùn)行的第一段軟件代碼。回憶一下 pc 的體系結(jié)構(gòu)我們可以知道,pc 機(jī)中的引導(dǎo)加載程序由 bios(其本質(zhì)就是一段固件程序)和位于硬盤 mbr 中的 os boot loader(比如,lilo 和 grub 等)一起組成。bios 在完成硬件檢測(cè)和資源分配后,將硬盤 mbr 中的 boot loader 讀到系統(tǒng)的 ram 中,然后將控制權(quán)交給 os boot loader。boot loader 的主要運(yùn)行任務(wù)就是將內(nèi)核映象從硬盤上讀到 ram 中,然后跳轉(zhuǎn)到內(nèi)核的入口點(diǎn)去運(yùn)行,也即開始啟動(dòng)操作系統(tǒng)。 而在嵌入式系統(tǒng)中,通常并沒有像 bios 那樣的固件程序(注,有的嵌入式 cpu 也會(huì)內(nèi)嵌一段短小的啟動(dòng)程序),因此整個(gè)系統(tǒng)的加載啟動(dòng)任務(wù)就完全由 boot loader 來完成。比如在一個(gè)基于 arm7tdmi core 的嵌入式系統(tǒng)中,系統(tǒng)在上電或復(fù)位時(shí)通常都從地址 0x00000000 處開始執(zhí)行,而在這個(gè)地址處安排的通常就是系統(tǒng)的 boot loader 程序。 本文將從 boot loader 的概念、boot loader 的主要任務(wù)、boot loader 的框架結(jié)構(gòu)以及 boot loader 的安裝等四個(gè)方面來討論嵌入式系統(tǒng)的 boot loader。 簡(jiǎn)單地說,boot loader 就是在操作系統(tǒng)內(nèi)核運(yùn)行之前運(yùn)行的一段小程序。通過這段小程序,我們可以初始化硬件設(shè)備、建立內(nèi)存空間的映射圖,從而將系統(tǒng)的軟硬件環(huán)境帶到一個(gè)合適的狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好正確的環(huán)境。 通常,boot loader 是嚴(yán)重地依賴于硬件而實(shí)現(xiàn)的,特別是在嵌入式世界。因此,在嵌入式世界里建立一個(gè)通用的 boot loader 幾乎是不可能的。盡管如此,我們?nèi)匀豢梢詫?duì) boot loader 歸納出一些通用的概念來,以指導(dǎo)用戶特定的 boot loader 設(shè)計(jì)與實(shí)現(xiàn)。 每種不同的 cpu 體系結(jié)構(gòu)都有不同的 boot loader。有些 boot loader 也支持多種體系結(jié)構(gòu)的 cpu,比如 u-boot 就同時(shí)支持 arm 體系結(jié)構(gòu)和mips 體系結(jié)構(gòu)。除了依賴于 cpu 的體系結(jié)構(gòu)外,boot loader 實(shí)際上也依賴于具體的嵌入式板級(jí)設(shè)備的配置。這也就是說,對(duì)于兩塊不同的嵌入式板而言,即使它們是基于同一種 cpu 而構(gòu)建的,要想讓運(yùn)行在一塊板子上的 boot loader 程序也能運(yùn)行在另一塊板子上,通常也都需要修改 boot loader 的源程序。 2. boot loader 的安裝媒介(installation medium) 系統(tǒng)加電或復(fù)位后,所有的 cpu 通常都從某個(gè)由 cpu 制造商預(yù)先安排的地址上取指令。比如,基于 arm7tdmi core 的 cpu 在復(fù)位時(shí)通常都從地址 0x00000000 取它的第一條指令。而基于 cpu 構(gòu)建的嵌入式系統(tǒng)通常都有某種類型的固態(tài)存儲(chǔ)設(shè)備(比如:rom、eeprom 或 flash 等)被映射到這個(gè)預(yù)先安排的地址上。因此在系統(tǒng)加電后,cpu 將首先執(zhí)行 boot loader 程序。 下圖1就是一個(gè)同時(shí)裝有 boot loader、內(nèi)核的啟動(dòng)參數(shù)、內(nèi)核映像和根文件系統(tǒng)映像的固態(tài)存儲(chǔ)設(shè)備的典型空間分配結(jié)構(gòu)圖。
3. 用來控制 boot loader 的設(shè)備或機(jī)制 主機(jī)和目標(biāo)機(jī)之間一般通過串口建立連接,boot loader 軟件在執(zhí)行時(shí)通常會(huì)通過串口來進(jìn)行 i/o,比如:輸出打印信息到串口,從串口讀取用戶控制字符等。 4. boot loader 的啟動(dòng)過程是單階段(single stage)還是多階段(multi-stage) 通常多階段的 boot loader 能提供更為復(fù)雜的功能,以及更好的可移植性。從固態(tài)存儲(chǔ)設(shè)備上啟動(dòng)的 boot loader 大多都是 2 階段的啟動(dòng)過程,也即啟動(dòng)過程可以分為 stage 1 和 stage 2 兩部分。而至于在 stage 1 和 stage 2 具體完成哪些任務(wù)將在下面討論。 5. boot loader 的操作模式 (operation mode) 大多數(shù) boot loader 都包含兩種不同的操作模式:"啟動(dòng)加載"模式和"下載"模式,這種區(qū)別僅對(duì)于開發(fā)人員才有意義。但從最終用戶的角度看,boot loader 的作用就是用來加載操作系統(tǒng),而并不存在所謂的啟動(dòng)加載模式與下載工作模式的區(qū)別。 啟動(dòng)加載(boot loading)模式:這種模式也稱為"自主"(autonomous)模式。也即 boot loader 從目標(biāo)機(jī)上的某個(gè)固態(tài)存儲(chǔ)設(shè)備上將操作系統(tǒng)加載到 ram 中運(yùn)行,整個(gè)過程并沒有用戶的介入。這種模式是 boot loader 的正常工作模式,因此在嵌入式產(chǎn)品發(fā)布的時(shí)侯,boot loader 顯然必須工作在這種模式下。 下載(downloading)模式:在這種模式下,目標(biāo)機(jī)上的 boot loader 將通過串口連接或網(wǎng)絡(luò)連接等通信手段從主機(jī)(host)下載文件,比如:下載內(nèi)核映像和根文件系統(tǒng)映像等。從主機(jī)下載的文件通常首先被 boot loader 保存到目標(biāo)機(jī)的 ram 中,然后再被 boot loader 寫到目標(biāo)機(jī)上的flash 類固態(tài)存儲(chǔ)設(shè)備中。boot loader 的這種模式通常在第一次安裝內(nèi)核與根文件系統(tǒng)時(shí)被使用;此外,以后的系統(tǒng)更新也會(huì)使用 boot loader 的這種工作模式。工作于這種模式下的 boot loader 通常都會(huì)向它的終端用戶提供一個(gè)簡(jiǎn)單的命令行接口。 像 blob 或 u-boot 等這樣功能強(qiáng)大的 boot loader 通常同時(shí)支持這兩種工作模式,而且允許用戶在這兩種工作模式之間進(jìn)行切換。比如,blob 在啟動(dòng)時(shí)處于正常的啟動(dòng)加載模式,但是它會(huì)延時(shí) 10 秒等待終端用戶按下任意鍵而將 blob 切換到下載模式。如果在 10 秒內(nèi)沒有用戶按鍵,則 blob 繼續(xù)啟動(dòng) linux 內(nèi)核。 6. bootloader 與主機(jī)之間進(jìn)行文件傳輸所用的通信設(shè)備及協(xié)議 最常見的情況就是,目標(biāo)機(jī)上的 boot loader 通過串口與主機(jī)之間進(jìn)行文件傳輸,傳輸協(xié)議通常是 xmodem/ymodem/zmodem 協(xié)議中的一種。但是,串口傳輸?shù)乃俣仁怯邢薜?,因此通過以太網(wǎng)連接并借助 tftp 協(xié)議來下載文件是個(gè)更好的選擇。 此外,在論及這個(gè)話題時(shí),主機(jī)方所用的軟件也要考慮。比如,在通過以太網(wǎng)連接和 tftp 協(xié)議來下載文件時(shí),主機(jī)方必須有一個(gè)軟件用來的提供 tftp 服務(wù)。 在討論了 bootloader 的上述概念后,下面我們來具體看看 bootloader 的應(yīng)該完成哪些任務(wù)。 3. boot loader 的主要任務(wù)與典型結(jié)構(gòu)框架 在繼續(xù)本節(jié)的討論之前,首先我們做一個(gè)假定,那就是:假定內(nèi)核映像與根文件系統(tǒng)映像都被加載到 ram 中運(yùn)行。之所以提出這樣一個(gè)假設(shè)前提是因?yàn)?,在嵌入式系統(tǒng)中內(nèi)核映像與根文件系統(tǒng)映像也可以直接在 rom 或 flash 這樣的固態(tài)存儲(chǔ)設(shè)備中直接運(yùn)行。但這種做法無疑是以運(yùn)行速度的犧牲為代價(jià)的。 從操作系統(tǒng)的角度看,boot loader 的總目標(biāo)就是正確地調(diào)用內(nèi)核來執(zhí)行。 另外,由于 boot loader 的實(shí)現(xiàn)依賴于 cpu 的體系結(jié)構(gòu),因此大多數(shù) boot loader 都分為 stage1 和 stage2 兩大部分。依賴于 cpu 體系結(jié)構(gòu)的代碼,比如設(shè)備初始化代碼等,通常都放在 stage1 中,而且通常都用匯編語言來實(shí)現(xiàn),以達(dá)到短小精悍的目的。而 stage2 則通常用c語言來實(shí)現(xiàn),這樣可以實(shí)現(xiàn)給復(fù)雜的功能,而且代碼會(huì)具有更好的可讀性和可移植性。 boot loader 的 stage1 通常包括以下步驟(以執(zhí)行的先后順序):
boot loader 的 stage2 通常包括以下步驟(以執(zhí)行的先后順序):
3.1.1 基本的硬件初始化 這是 boot loader 一開始就執(zhí)行的操作,其目的是為 stage2 的執(zhí)行以及隨后的 kernel 的執(zhí)行準(zhǔn)備好一些基本的硬件環(huán)境。它通常包括以下步驟(以執(zhí)行的先后順序): 1. 屏蔽所有的中斷。為中斷提供服務(wù)通常是 os 設(shè)備驅(qū)動(dòng)程序的責(zé)任,因此在 boot loader 的執(zhí)行全過程中可以不必響應(yīng)任何中斷。中斷屏蔽可以通過寫 cpu 的中斷屏蔽寄存器或狀態(tài)寄存器(比如 arm 的 cpsr 寄存器)來完成。 2. 設(shè)置 cpu 的速度和時(shí)鐘頻率。 3. ram 初始化。包括正確地設(shè)置系統(tǒng)的內(nèi)存控制器的功能寄存器以及各內(nèi)存庫控制寄存器等。 4. 初始化 led。典型地,通過 gpio 來驅(qū)動(dòng) led,其目的是表明系統(tǒng)的狀態(tài)是 ok 還是 error。如果板子上沒有 led,那么也可以通過初始化 uart 向串口打印 boot loader 的 logo 字符信息來完成這一點(diǎn)。 5. 關(guān)閉 cpu 內(nèi)部指令/數(shù)據(jù) cache。 3.1.2 為加載 stage2 準(zhǔn)備 ram 空間 為了獲得更快的執(zhí)行速度,通常把 stage2 加載到 ram 空間中來執(zhí)行,因此必須為加載 boot loader 的 stage2 準(zhǔn)備好一段可用的 ram 空間范圍。 由于 stage2 通常是 c 語言執(zhí)行代碼,因此在考慮空間大小時(shí),除了 stage2 可執(zhí)行映象的大小外,還必須把堆??臻g也考慮進(jìn)來。此外,空間大小最好是 memory page 大小(通常是 4kb)的倍數(shù)。一般而言,1m 的 ram 空間已經(jīng)足夠了。具體的地址范圍可以任意安排,比如 blob 就將它的 stage2 可執(zhí)行映像安排到從系統(tǒng) ram 起始地址 0xc0200000 開始的 1m 空間內(nèi)執(zhí)行。但是,將 stage2 安排到整個(gè) ram 空間的最頂 1mb(也即(ramend-1mb) - ramend)是一種值得推薦的方法。 為了后面的敘述方便,這里把所安排的 ram 空間范圍的大小記為:stage2_size(字節(jié)),把起始地址和終止地址分別記為:stage2_start 和 stage2_end(這兩個(gè)地址均以 4 字節(jié)邊界對(duì)齊)。因此:
另外,還必須確保所安排的地址范圍的的確確是可讀寫的 ram 空間,因此,必須對(duì)你所安排的地址范圍進(jìn)行測(cè)試。具體的測(cè)試方法可以采用類似于 |