當(dāng)前位置:首頁(yè) > 嵌入式 > 嵌入式軟件
[導(dǎo)讀]一個(gè)嵌入式 Linux 系統(tǒng)從軟件的角度看通??梢苑譃樗膫€(gè)層次:

一個(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--(Basic Input Output System)(其本質(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。

2. 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)。

1. Boot Loader 所支持的 CPU 和嵌入式板

每種不同的 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 程序。

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ù)上面的討論之前,首先我們做一個(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 中,而且通常都用匯編語(yǔ)言來實(shí)現(xiàn),以達(dá)到短小精悍的目的。而 stage2 則通常用C語(yǔ)言來實(shí)現(xiàn),這樣可以實(shí)現(xiàn)給復(fù)雜的功能,而且代碼會(huì)具有更好的可讀性和可移植性。

Boot Loader 的 stage1 通常包括以下步驟(以執(zhí)行的先后順序):

· 硬件設(shè)備初始化。

· 為加載 Boot Loader 的 stage2 準(zhǔn)備 RAM 空間。

· 拷貝 Boot Loader 的 stage2 到 RAM 空間中。

· 設(shè)置好堆棧。

· 跳轉(zhuǎn)到 stage2 的 C 入口點(diǎn)。

Boot Loader 的 stage2 通常包括以下步驟(以執(zhí)行的先后順序):

· 初始化本階段要使用到的硬件設(shè)備。

· 檢測(cè)系統(tǒng)內(nèi)存映射(memory map)。

· 將 kernel 映像和根文件系統(tǒng)映像從 flash 上讀到 RAM 空間中。

· 為內(nèi)核設(shè)置啟動(dòng)參數(shù)。

· 調(diào)用內(nèi)核。

3.1 Boot Loader 的 stage1

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)存庫(kù)控制寄存器等。

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 語(yǔ)言執(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ì)齊)。因此:

stage2_end=stage2_start+stage2_size

另外,還必須確保所安排的地址范圍的的確確是可讀寫的 RAM 空間,因此,必須對(duì)你所安排的地址范圍進(jìn)行測(cè)試。具體的測(cè)試方法可以采用類似于 blob 的方法,也即:以memory page 為被測(cè)試單位,測(cè)試每個(gè) memory page 開始的兩個(gè)字是否是可讀寫的。為了后面敘述的方便,我們記這個(gè)檢測(cè)算法為:test_mempage,其具體步驟如下:

1. 先保存 memory page 一開始兩個(gè)字的內(nèi)容。

2. 向這兩個(gè)字中寫入任意的數(shù)字。比如:向第一個(gè)字寫入 0x55,第 2 個(gè)字寫入 0xaa。

3. 然后,立即將這兩個(gè)字的內(nèi)容讀回。顯然,我們讀到的內(nèi)容應(yīng)該分別是 0x55 和 0xaa。如果不是,則說明這個(gè) memory page 所占據(jù)的地址范圍不是一段有效的 RAM 空間。

4. 再向這兩個(gè)字中寫入任意的數(shù)字。比如:向第一個(gè)字寫入 0xaa,第 2 個(gè)字中寫入0x55。

5.然后,立即將這兩個(gè)字的內(nèi)容立即讀回。顯然,我們讀到的內(nèi)容應(yīng)該分別是 0xaa 和0x55。如果不是,則說明這個(gè) memory page 所占據(jù)的地址范圍不是一段有效的 RAM 空間。

6. 恢復(fù)這兩個(gè)字的原始內(nèi)容。測(cè)試完畢。

為了得到一段干凈的 RAM 空間范圍,我們也可以將所安排的 RAM 空間范圍進(jìn)行清零操作。

3.1.3 拷貝 stage2 到 RAM 中

拷貝時(shí)要確定兩點(diǎn):(1) stage2 的可執(zhí)行映象在固態(tài)存儲(chǔ)設(shè)備的存放起始地址和終止地址;(2) RAM 空間的起始地址。

3.1.4 設(shè)置堆棧指針 sp

堆棧指針的設(shè)置是為了執(zhí)行 C 語(yǔ)言代碼作好準(zhǔn)備。通常我們可以把 sp 的值設(shè)置為(stage2_end-4),也即在 3.1.2 節(jié)所安排的那個(gè) 1MB 的 RAM 空間的最頂端(堆棧向下生長(zhǎng))。

此外,在設(shè)置堆棧指針 sp 之前,也可以關(guān)閉 led 燈,以提示用戶我們準(zhǔn)備跳轉(zhuǎn)到 stage2。

經(jīng)過上述這些執(zhí)行步驟后,系統(tǒng)的物理內(nèi)存布局應(yīng)該如下圖2所示。

3.1.5 跳轉(zhuǎn)到 stage2 的 C 入口點(diǎn)

在上述一切都就緒后,就可以跳轉(zhuǎn)到 Boot Loader 的 stage2 去執(zhí)行了。比如,在 ARM 系統(tǒng)中,這可以通過修改 PC 寄存器為合適的地址來實(shí)現(xiàn)。

3.2 Boot Loader 的 stage2

正如前面所說,stage2 的代碼通常用 C 語(yǔ)言來實(shí)現(xiàn),以便于實(shí)現(xiàn)更復(fù)雜的功能和取得更好的代碼可讀性和可移植性。但是與普通 C 語(yǔ)言應(yīng)用程序不同的是,在編譯和鏈接 boot loader 這樣的程序時(shí),我們不能使用 glibc 庫(kù)中的任何支持函數(shù)。其原因是顯而易見的。這就給我們帶來一個(gè)問題,那就是從那里跳轉(zhuǎn)進(jìn) main() 函數(shù)呢?直接把 main() 函數(shù)的起始地址作為整個(gè) stage2 執(zhí)行映像的入口點(diǎn)或許是最直接的想法。但是這樣做有兩個(gè)缺點(diǎn):1)無法通過main() 函數(shù)傳遞函數(shù)參數(shù);2)無法處理 main() 函數(shù)返回的情況。一種更為巧妙的方法是利用 trampoline(彈簧床)的概念。也即,用匯編語(yǔ)言寫一段trampoline 小程序,并將這段 trampoline 小程序來作為 stage2 可執(zhí)行映象的執(zhí)行入口點(diǎn)。然后我們可以在 trampoline匯編小程序中用 CPU 跳轉(zhuǎn)指令跳入 main() 函數(shù)中去執(zhí)行;而當(dāng) main() 函數(shù)返回時(shí),CPU執(zhí)行路徑顯然再次回到我們的 trampoline 程序。簡(jiǎn)而言之,這種方法的思想就是:用這段trampoline 小程序來作為 main() 函數(shù)的外部包裹(external wrapper)。

下面給出一個(gè)簡(jiǎn)單的 trampoline 程序示例(來自blob):

.text

.globl _trampoline

_trampoline:

bl main

b _trampoline

可以看出,當(dāng) main() 函數(shù)返回后,我們又用一條跳轉(zhuǎn)指令重新執(zhí)行 trampoline 程序――當(dāng)然也就重新執(zhí)行 main() 函數(shù),這也就是 trampoline(彈簧床)一詞的意思所在。

3.2.1初始化本階段要使用到的硬件設(shè)備

這通常包括:(1)初始化至少一個(gè)串口,以便和終端用戶進(jìn)行 I/O 輸出信息;(2)初始化計(jì)時(shí)器等。

在初始化這些設(shè)備之前,也可以重新把 LED 燈點(diǎn)亮,以表明我們已經(jīng)進(jìn)入 main() 函數(shù)執(zhí)行。

設(shè)備初始化完成后,可以輸出一些打印信息,程序名字字符串、版本號(hào)等。

3.2.2 檢測(cè)系統(tǒng)的內(nèi)存映射(memory map)

所謂內(nèi)存映射就是指在整個(gè) 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)時(shí)卻不一定會(huì)實(shí)現(xiàn) CPU 預(yù)留的全部 RAM地址空間。也就是說,具體的嵌入式系統(tǒng)往往只把 CPU 預(yù)留的全部 RAM 地址空間中的一部分映射到 RAM 單元上,而讓剩下的那部分預(yù)留 RAM 地址空間處于未使用狀態(tài)。 由于上述這個(gè)事實(shí),因此 Boot Loader 的 stage2 必須在它想干點(diǎn)什么 (比如,將存儲(chǔ)在 flash上的內(nèi)核映像讀到 RAM 空間中) 之前檢測(cè)整個(gè)系統(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;

u32 size;

int used;

} memory_area_t;

這段 RAM 地址空間中的連續(xù)地址范圍可以處于兩種狀態(tài)之一:(1)used=1,則說明這段連續(xù)的地址范圍已被實(shí)現(xiàn),也即真正地被映射到 RAM 單元上。(2)used=0,則說明這段連續(xù)的地址范圍并未被系統(tǒng)所實(shí)現(xiàn),而是處于未使用狀態(tài)。

基于上述 memory_area_t 數(shù)據(jù)結(jié)構(gòu),整個(gè) CPU 預(yù)留的 RAM 地址空間可以用一個(gè)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)存映射的檢測(cè)

下面我們給出一個(gè)可用來檢測(cè)整個(gè) RAM 地址空間內(nèi)存映射情況的簡(jiǎn)單而有效的算法:

for(i = 0; i < NUM_MEM_AREAS; i++)

memory_map[i].used = 0;

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) {

調(diào)用3.1.2節(jié)中的算法test_mempage();

if ( current memory page isnot a valid ram page) {

if(memory_map[i].used )

i++;

continue;

}

if(* (u32 *)addr != 0) {

if ( memory_map[i].used )

i++;

continue;

}

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;

}

}

在用上述算法檢測(cè)完系統(tǒng)的內(nèi)存映射情況后,Boot Loader 也可以將內(nèi)存映射的詳細(xì)信息打印到串口。

3.2.3 加載內(nèi)核映像和根文件系統(tǒng)映像

(1) 規(guī)劃內(nèi)存占用的布局

這里包括兩個(gè)方面:(1)內(nèi)核映像所占用的內(nèi)存范圍;(2)根文件系統(tǒng)所占用的內(nèi)存范圍。在規(guī)劃內(nèi)存占用的布局時(shí),主要考慮基地址和映像的大小兩個(gè)方面。

對(duì)于內(nèi)核映像,一般將其拷貝到從(MEM_START+0x8000) 這個(gè)基地址開始的大約1MB大小的內(nèi)存范圍內(nèi)(嵌入式 Linux 的內(nèi)核一般都不操過 1MB)。為什么要把從 MEM_START 到MEM_START+0x8000 這段 32KB 大小的內(nèi)存空出來呢?這是因?yàn)?Linux 內(nèi)核要在這段內(nèi)存中放置一些全局?jǐn)?shù)據(jù)結(jié)構(gòu),如:?jiǎn)?dòng)參數(shù)和內(nèi)核頁(yè)表等信息。

而對(duì)于根文件系統(tǒng)映像,則一般將其拷貝到 MEM_START+0x0010,0000 開始的地方。如果用 Ramdisk 作為根文件系統(tǒng)映像,則其解壓后的大小一般是1MB。

(2)從 Flash 上拷貝

由于像 ARM 這樣的嵌入式 CPU 通常都是在統(tǒng)一的內(nèi)存地址空間中尋址 Flash 等固態(tài)存儲(chǔ)設(shè)備的,因此從 Flash 上讀取數(shù)據(jù)與從 RAM 單元中讀取數(shù)據(jù)并沒有什么不同。用一個(gè)簡(jiǎn)單的循環(huán)就可以完成從 Flash 設(shè)備上拷貝映像的工作:

while(count) {

*dest++ = *src++;

count -= 4;

};

3.2.4 設(shè)置內(nèi)核的啟動(dòng)參數(shù)

應(yīng)該說,在將內(nèi)核映像和根文件系統(tǒng)映像拷貝到 RAM 空間中后,就可以準(zhǔn)備啟動(dòng) Linux內(nèi)核了。但是在調(diào)用內(nèi)核之前,應(yīng)該作一步準(zhǔn)備工作,即:設(shè)置 Linux 內(nèi)核的啟動(dòng)參數(shù)。

Linux 2.4.x 以后的內(nèi)核都期望以標(biāo)記列表(tagged list)的形式來傳遞啟動(dòng)參數(shù)。啟動(dòng)參數(shù)標(biāo)記列表以標(biāo)記 ATAG_CORE 開始,以標(biāo)記 ATAG_NONE 結(jié)束。個(gè)標(biāo)記由標(biāo)識(shí)被傳遞參數(shù)的 tag_header 結(jié)構(gòu)以及隨后的參數(shù)值數(shù)據(jù)結(jié)構(gòu)來組成。數(shù)據(jù)結(jié)構(gòu) tag 和 tag_header 定義在 Linux 內(nèi)核源碼的include/asm/setup.h 頭文件中:

#define ATAG_NONE 0x00000000

struct tag_header {

u32 size;

u32 tag;

};

……

struct tag {

struct tag_header hdr;

union {

struct tag_core core;

struct tag_mem32 mem;

struct tag_videotext videotext;

struct tag_ramdisk ramdisk;

struct tag_initrd initrd;

struct tag_serialnr serialnr;

struct tag_revision revision;

struct tag_videolfb videolfb;

struct tag_cmdline cmdline;

struct tag_acorn acorn;

struct tag_memclk memclk;

} u;

};

在嵌入式 Linux 系統(tǒng)中,通常需要由 Boot Loader 設(shè)置的常見啟動(dòng)參數(shù)有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。

比如,設(shè)置 ATAG_CORE 的代碼如下:

params = (struct tag *)BOOT_PARAMS;

params->hdr.tag = ATAG_CORE;

params->hdr.size = tag_size(tag_core);

params->u.core.flags = 0;

params->u.core.pagesize = 0;

params->u.core.rootdev = 0;

params = tag_next(params);

其中,BOOT_PARAMS 表示內(nèi)核啟動(dòng)參數(shù)在內(nèi)存中的起始基地址,指針 params 是一個(gè)struct tag 類型的指針。宏 tag_next() 將以指向當(dāng)前標(biāo)記的指針為參數(shù),計(jì)算緊臨當(dāng)前標(biāo)記的下一個(gè)標(biāo)記的起始地址。注意,內(nèi)核的根文件系統(tǒng)所在的設(shè)備ID就是在這里設(shè)置的。

下面是設(shè)置內(nèi)存映射情況的示例代碼:

for(i = 0; i < NUM_MEM_AREAS; i++) {

if(memory_map[i].used) {

params->hdr.tag = ATAG_MEM;

params->hdr.size = tag_size(tag_mem32);

params->u.mem.start = memory_map[i].start;

params->u.mem.size = memory_map[i].size;

params = tag_next(params);

}

}

可以看出,在 memory_map[]數(shù)組中,每一個(gè)有效的內(nèi)存段都對(duì)應(yīng)一個(gè) ATAG_MEM參數(shù)標(biāo)記。

Linux 內(nèi)核在啟動(dòng)時(shí)可以以命令行參數(shù)的形式來接收信息,利用這一點(diǎn)我們可以向內(nèi)核提供那些內(nèi)核不能自己檢測(cè)的硬件參數(shù)信息,或者重載(override)內(nèi)核自己檢測(cè)到的信息。比如,我們用這樣一個(gè)命令行參數(shù)字符串"console=ttyS0,115200n8"來通知內(nèi)核以 ttyS0 作為控制臺(tái),且串口采用 "115200bps、無奇偶校驗(yàn)、8位數(shù)據(jù)位"這樣的設(shè)置。下面是一段設(shè)置調(diào)用內(nèi)核命令行參數(shù)字符串的示例代碼:

char *p;

for(p = commandline; *p == ' '; p++)

;

if(*p == '\0')

return;

params->hdr.tag = ATAG_CMDLINE;

params->hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2;

strcpy(params->u.cmdline.cmdline, p);

params = tag_next(params);

請(qǐng)注意在上述代碼中,設(shè)置 tag_header 的大小時(shí),必須包括字符串的終止符'\0',此外還要將字節(jié)數(shù)向上圓整4個(gè)字節(jié),因?yàn)?tag_header 結(jié)構(gòu)中的size 成員表示的是字?jǐn)?shù)。

下面是設(shè)置 ATAG_INITRD 的示例代碼,它告訴內(nèi)核在 RAM 中的什么地方可以找到 initrd映象(壓縮格式)以及它的大?。?/p>

params->hdr.tag = ATAG_INITRD2;

params->hdr.size = tag_size(tag_initrd);

params->u.initrd.start = RAMDISK_RAM_BASE;

params->u.initrd.size = INITRD_LEN;

params = tag_next(params);

下面是設(shè)置 ATAG_RAMDISK 的示例代碼,它告訴內(nèi)核解壓后的 Ramdisk 有多大(單位是KB):

params->hdr.tag = ATAG_RAMDISK;

params->hdr.size = tag_size(tag_ramdisk);

params->u.ramdisk.start = 0;

params->u.ramdisk.size = RAMDISK_SIZE;

params->u.ramdisk.flags = 1;

params = tag_next(params);

最后,設(shè)置 ATAG_NONE 標(biāo)記,結(jié)束整個(gè)啟動(dòng)參數(shù)列表:

sstatic void setup_end_tag(void)

{

params->hdr.tag = ATAG_NONE;

params->hdr.size = 0;

}

3.2.5 調(diào)用內(nèi)核

Boot Loader 調(diào)用 Linux 內(nèi)核的方法是直接跳轉(zhuǎn)到內(nèi)核的第一條指令處,也即直接跳轉(zhuǎn)到MEM_START+0x8000 地址處。在跳轉(zhuǎn)時(shí),下列條件要滿足:

1. CPU 寄存器的設(shè)置:

· R0=0;

· R1=機(jī)器類型 ID;關(guān)于 Machine Type Number,可以參見 linux/arch/arm/tools/mach-types。

· R2=啟動(dòng)參數(shù)標(biāo)記列表在 RAM 中起始基地址;

2. CPU 模式:

· 必須禁止中斷(IRQs和FIQs);

· CPU 必須 SVC 模式;

3. Cache 和 MMU 的設(shè)置:

· MMU 必須關(guān)閉;

· 指令 Cache 可以打開也可以關(guān)閉;

· 數(shù)據(jù) Cache 必須關(guān)閉;

如果用 C 語(yǔ)言,可以像下列示例代碼這樣來調(diào)用內(nèi)核:

void (*theKernel)(int zero, int arch, u32 params_addr) =

(void (*)(int, int, u32))KERNEL_RAM_BASE;

……

theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);

注意,theKernel()函數(shù)調(diào)用應(yīng)該永遠(yuǎn)不返回的。如果這個(gè)調(diào)用返回,則說明出錯(cuò)。

4. 關(guān)于串口終端

在 boot loader 程序的設(shè)計(jì)與實(shí)現(xiàn)中,沒有什么能夠比從串口終端正確地收到打印信息能更令人激動(dòng)了。此外,向串口終端打印信息也是一個(gè)非常重要而又有效的調(diào)試手段。但是,我們經(jīng)常會(huì)碰到串口終端顯示亂碼或根本沒有顯示的問題。造成這個(gè)問題主要有兩種原因:(1) boot loader 對(duì)串口的初始化設(shè)置不正確。(2) 運(yùn)行在 host 端的終端仿真程序?qū)Υ诘脑O(shè)置不正確,這包括:波特率、奇偶校驗(yàn)、數(shù)據(jù)位和停止位等方面的設(shè)置。

此外,有時(shí)也會(huì)碰到這樣的問題,那就是:在 boot loader 的運(yùn)行過程中我們可以正確地向串口終端輸出信息,但當(dāng) boot loader 啟動(dòng)內(nèi)核后卻無法看到內(nèi)核的啟動(dòng)輸出信息。對(duì)這一問題的原因可以從以下幾個(gè)方面來考慮:

(1) 首先請(qǐng)確認(rèn)你的內(nèi)核在編譯時(shí)配置了對(duì)串口終端的支持,并配置了正確的串口驅(qū)動(dòng)程序。

(2) 你的 boot loader 對(duì)串口的初始化設(shè)置可能會(huì)和內(nèi)核對(duì)串口的初始化設(shè)置不一致。此外,對(duì)于諸如 s3c44b0x 這樣的 CPU,CPU 時(shí)鐘頻率的設(shè)置也會(huì)影響串口,因此如果 boot loader和內(nèi)核對(duì)其 CPU 時(shí)鐘頻率的設(shè)置不一致,也會(huì)使串口終端無法正確顯示信息。

(3) 最后,還要確認(rèn) boot loader 所用的內(nèi)核基地址必須和內(nèi)核映像在編譯時(shí)所用的運(yùn)行基地址一致,尤其是對(duì)于 uClinux 而言。假設(shè)你的內(nèi)核映像在編譯時(shí)用的基地址是0xc0008000,但你的 boot loader 卻將它加載到 0xc0010000 處去執(zhí)行,那么內(nèi)核映像當(dāng)然不能正確地執(zhí)行了。

5. 結(jié)束語(yǔ)

Boot Loader 的設(shè)計(jì)與實(shí)現(xiàn)是一個(gè)非常復(fù)雜的過程。

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉