一、Bootloader 的引入
1.1 Bootloader 的引入
Linux 內(nèi)核的啟動是需要一定的必要條件的,但在 CPU 剛上電啟動時,一般連內(nèi)存控制 器都沒有配置過,根本無法在內(nèi)存中運行程序,更不可能處在 Linux 內(nèi)核的啟動環(huán)境中。為 了初始化 CPU 及其他外設(shè),使得 Linux 內(nèi)核可以在系統(tǒng)主存中跑起來,并讓系統(tǒng)符合 Linux 內(nèi)核啟動的必備條件,必須要有一個先于內(nèi)核運行的程序,他就是所謂的引導(dǎo)加載程序: Bootloader。
Bootloader并不是只有Linux才需要,是幾乎所有的運行操作系統(tǒng)的設(shè)備都必須具備的。 PC電腦的BIOS就是bootloader的一部分,對于Linux PC來說:Bootloader = BIOS + GRUB/LILO。
1.2 嵌入式 Linux 系統(tǒng)軟件結(jié)構(gòu)與分布
一般情況下嵌入式 Linux 系統(tǒng)中軟件主要由以下幾個部分組成:
1.引導(dǎo)加載程序:其中包括內(nèi)部ROM中的固化啟動代碼和bootloader兩部分。固化ROM 是廠家在芯片生產(chǎn)時固化,用于引導(dǎo) bootloader。
2.Linux Kernel 和Drivers。
3.文件系統(tǒng):包括根文件系統(tǒng)和建立于Flash 內(nèi)存設(shè)備之上的文件系統(tǒng)(ext4、UBI、 CRAMFS 等)。它是提供管理系統(tǒng)的各種配置文件以及系統(tǒng)執(zhí)行用于應(yīng)用程序的良好運行環(huán) 境的載體。
4.應(yīng)用程序:用于自定義的應(yīng)用程序,存放于文件系統(tǒng)之中。 在Flash 存儲器中,上面四個部分的分布如圖 1 所示:
圖1:嵌入式Linux的軟件分布
但是以上只是大部分情況下的分布,也有一些根文件系統(tǒng)可能是 initramfs,被一起壓縮到了內(nèi)核映像里,或者沒有 bootloader 參數(shù)區(qū)等。
二、Bootloader 介紹
2.1 Bootloader 的功能
Bootloader是在操作系統(tǒng)內(nèi)核運行之前運行的一段小程序,通過它我們可以初始化硬件設(shè)備,從而將系統(tǒng)的軟硬件環(huán)境帶到一個合適的狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好 正確的環(huán)境,最后從別處(Flash、以太網(wǎng)、UART 等)載入內(nèi)核映像并跳到入口地址運行。
簡單的說,Bootloader 就是這么一小段程序,它在系統(tǒng)上電時開始執(zhí)行,初始化硬件設(shè)備、準(zhǔn)備好軟件環(huán)境,最后調(diào)用操作系統(tǒng)內(nèi)核。
可以增強(qiáng) Bootloader 的功能,比如增加網(wǎng)絡(luò)功能、從 PC 上通過串口或網(wǎng)絡(luò)下載文件、 燒寫文件、將 Flash 上壓縮的文件解壓后再運行等,這就是一個功能更為強(qiáng)大的 Bootloader, 也稱為 Monitor。實際上,在最終產(chǎn)品中用戶并不需要這些功能,他們只是為了方便開發(fā)。
2.2 Bootloader 的特點
由于 Bootloader 直接操作硬件,因此它嚴(yán)重依賴于硬件,且依據(jù)所引導(dǎo)的操作系統(tǒng)的不同而不同。在嵌入式世界中建立一個通用的 Bootloader 幾乎是不可能的,而有可能的是 讓一個 Bootloader 代碼支持多種不同的架構(gòu)和操作系統(tǒng),并讓他方便移植。Bootloader的啟動過程通常是多階段的,這樣既能夠提供復(fù)雜的功能,又具有更好的可移植性。
大多數(shù) Bootloader 都包含兩種不同的操作模式:本地加載模式和遠(yuǎn)程下載模式。遠(yuǎn)程下載模式只對開發(fā)人員才有意義。
Bootloader 都映射在 CPU復(fù)位后運行的第一條指令的地址處,以保證系統(tǒng)上電或復(fù)位 后首先執(zhí)行 Bootloader。
2.3 Bootloader 的分類
首先區(qū)分一下“Bootloader”和“Monitor”的概念:嚴(yán)格的講,“Bootloader”只是引 導(dǎo)設(shè)備并執(zhí)行主程序的固件;而“Monitor”還提供更多的命令行接口,可進(jìn)行調(diào)試、讀寫內(nèi)存、燒寫 Flash、配置環(huán)境變量等。“Monitor”在嵌入式系統(tǒng)開發(fā)過程中可以提供更好的調(diào)試功能,開發(fā)完成后,就完全設(shè)置成一個“Bootloader”了。所以習(xí)慣上將他們統(tǒng)稱為 Bootloader。表 2.1 為常見的開放源碼的 Linux 引導(dǎo)程序。
表2.1 常見的開放源碼 Linux 引導(dǎo)程序
其中 U-Boot 支持大多 CPU,可以燒寫 EXT2、JFFS2 文件系統(tǒng)映像,支持串口下載、網(wǎng)絡(luò) 下載,并提供了大量的命令。
2.4 Bootloader 的啟動模式
Bootloader 的主要功能是引導(dǎo)操作系統(tǒng),但在開發(fā)時,通常需要使用各種命令操作 Bootloader,一般通過串口來連接 PC 和開發(fā)板,可以在串口上輸入各種命令、觀察運行結(jié)果等。這也只是對開發(fā)人員才有意義,用戶使用產(chǎn)品時是不用接串口來控制 Bootloader 的。 從這個角度可以將 Bootloader 分為啟動加載(Boot loading)模式和下載(Down loading)模 式。
2.4.1 啟動加載(Bootloading)模式
這種模式也稱為"自主"(Autonomous)模式。也即 Boot Loader 從目標(biāo)機(jī)上的某個固態(tài)存儲設(shè)備上將操作系統(tǒng)加載到 RAM 中運行,整個過程并沒有用戶的介入。這種模式是 Boot Loader 的正常工作模式,因此在嵌入式產(chǎn)品發(fā)布的時侯,Bootloader顯然必須工作在這種模 式下。
2.4.2 下載(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)存儲設(shè)備中。Boot Loader 的這種模式通常在第一次安裝內(nèi)核與根文件系統(tǒng)時被使用;此 外,以后的系統(tǒng)更新也會使用Boot Loader的這種工作模式。工作于這種模式下的Boot Loader 通常都會向它的終端用戶提供一個簡單的命令行接口。
像 Blob 或 U-Boot 等這樣功能強(qiáng)大的 Boot Loader 通常同時支持這兩種工作模式,而且 允許用戶在這兩種工作模式之間進(jìn)行切換。比如,Blob 在啟動時處于正常的啟動加載模式, 但是它會延時 10 秒等待終端用戶按下任意鍵而將 blob 切換到下載模式。如果在 10 秒內(nèi)沒 有用戶按鍵,則 blob 繼續(xù)啟動 Linux 內(nèi)核。
2.4.3 下載模式之網(wǎng)絡(luò)啟動方式
這種方式開發(fā)板不需要配置較大的存儲介質(zhì)(跟無盤工作站有點類似),但是使用這種 啟動方式之前,需要把 Bootloader 安裝到板上的 EPPROM 或者 Flash 中。Bootloader 通過以 太網(wǎng)接口遠(yuǎn)程下載 Linux 內(nèi)核映像或者文件系統(tǒng)到 RAM 中運行。這種方式對于嵌入式系統(tǒng)開發(fā)來說非常重要。
使用這種方式的前提條件,就是目標(biāo)板有串口、以太網(wǎng)接口、USB 接口或者其他鏈接方式:串口一般作為控制臺,同時可以用來下載內(nèi)核映像和 RAMDISK 文件系統(tǒng);用網(wǎng)絡(luò)接口 來掛載 NFS 文件系統(tǒng);也可以使用 USB 接口虛擬成以太網(wǎng)口來通訊。
使用網(wǎng)絡(luò)啟動方式,還需要在服務(wù)器上配置啟動相關(guān)網(wǎng)絡(luò)服務(wù):使用 TFTP 服務(wù)為 Bootloader 客戶端提供文件下載功能;DHCP 服務(wù)動態(tài)為 Bootloader 設(shè)置 IP 地址,配置網(wǎng)絡(luò) 參數(shù)。網(wǎng)絡(luò)啟動方式如圖 2.1 所示。
圖2.1 網(wǎng)絡(luò)方式啟動系統(tǒng)
2.5 Bootloader 的啟動流程
Bootloader 的啟動過程可以分為單階段(Single Stage)、多階段(Multi-Stage)兩種。通 常多階段的 Bootloader 能提供更為復(fù)雜的功能以及更好的可移植性。從固態(tài)存儲設(shè)備上啟動 的Bootloader 大多都是兩個階段的啟動過程。第一階段使用匯編來實現(xiàn),它完成一些依賴于 CPU 體系結(jié)構(gòu)的初始化,并調(diào)用第二階段的代碼;第二階段則通常使用 C 語言來實現(xiàn),這樣 可以實現(xiàn)更復(fù)雜的功能,而且代碼會有更好的可讀性和可移植性。
一般而言,這兩個階段完成的功能可以如下分類:
2.5.1 Bootloader 第一階段的功能
Bootloader 在第一階段主要完成以下功能:
硬件設(shè)備初始化;
為加載 Bootloader 的第二階段代碼準(zhǔn)備 RAM 空間;
復(fù)制 Bootloader 的第二階段代碼到 RAM 空間中;
設(shè)置好棧;
跳轉(zhuǎn)到第二階段代碼的 C 入口點;
在第一階段進(jìn)行的硬件初始化一般包括:關(guān)閉 WATCHDOG、關(guān)中斷、設(shè)置 CPU 的速度 和時鐘頻率、RAM 初始化等。這些并不都是必須的,比如 S3C2410/S3C2440 的開發(fā)板所使 用的 U-Boot 中,就將 CPU 的速度和時鐘頻率放在第二階段進(jìn)行設(shè)置。
甚至,將第二階段的代碼復(fù)制到 RAM 空間也不是必須的,對于 Nor Flash 等支持 XIP 的 存儲設(shè)備,完全可以在上面直接執(zhí)行代碼,只不過相比在 RAM 中執(zhí)行效率大為降低。
2.5.2 Bootloader 第二階段的功能
Bootloader 在第二階段主要完成以下功能:
初始化本階段要使用到的硬件設(shè)備;
檢測系統(tǒng)內(nèi)存映射(Memory map);
將內(nèi)核映像和根文件系統(tǒng)映像從 Flash 上讀到 RAM 空間中;
為內(nèi)核設(shè)置啟動參數(shù);
調(diào)用內(nèi)核;
為了方便開發(fā),只要要初始化一個串口以便程序員與 Bootloader 進(jìn)行交互。
所謂檢測內(nèi)存映射,就是確定板上使用了多少內(nèi)存、他們的地址空間是什么。由于嵌入 式開發(fā)中的 Bootloader 多是針對某類板子進(jìn)行編寫,所以可以根據(jù)板子的情況直接設(shè)置,不 需要考慮可以適用于各類情況的復(fù)雜算法。
Flash 上的內(nèi)核映像有可能是經(jīng)過壓縮的,在讀到 RAM 之后,還需要進(jìn)行解壓。當(dāng)然, 對于有自解壓功能的內(nèi)核,不需要Bootloader 來解壓。
將根文件系統(tǒng)映像復(fù)制到 RAM 中并不是必須的,這取決于是什么類型的根文件系統(tǒng),以及內(nèi)核訪問它的方法。
將內(nèi)核放在適當(dāng)?shù)奈恢煤?,在跳入?zhí)行內(nèi)核之前,需要根據(jù)內(nèi)核啟動的需求,配置相應(yīng) 的啟動參數(shù)。如 Linux 內(nèi)核的啟動要求如表 2.2 所示。
表2.2 Linux 內(nèi)核啟動條件
在 C 語言中,可以像下列示例代碼一樣來調(diào)用內(nèi)核:
void (*theKernel)(int zero, int arch, u32params_addr) = (void (*)(int, int, u32))KERNEL_RAM_BASE;
theKernel(0,ARCH_NUMBER, (u32)kernel_params_start);
2.6 Bootloader 與內(nèi)核的交互
U-Boot 與內(nèi)核之間的交互是單向的,U-Boot 將各類參數(shù)傳遞給內(nèi)核。由于他們(U-Boot 與內(nèi)核)不能同時運行,傳遞的辦法只有一個:U-Boot 將參數(shù)放在放在某個約定的地方之 后,再啟動內(nèi)核,內(nèi)核啟動后從這個地方獲得參數(shù)。
除了約定好參數(shù)存放的地址外,還要規(guī)定參數(shù)的結(jié)構(gòu)。Linux 2.4.x 以后的內(nèi)核都期望以標(biāo)記列表(tagged list)的形式來傳遞啟動參數(shù)。標(biāo)記,就是一種數(shù)據(jù)結(jié)構(gòu);標(biāo)記列表,就 是挨著存放的多個標(biāo)記。標(biāo)記列表以標(biāo)記 ATAG_CORE 開始,以標(biāo)記 ATAG_NONE 結(jié)束。
標(biāo)記的數(shù)據(jù)結(jié)構(gòu)為tag,它由一個tag_header結(jié)構(gòu)和一個聯(lián)合體(union)組成。tag_header 結(jié)構(gòu)標(biāo)示標(biāo)記的類型及長度,比如是表示內(nèi)存還是表示命令行參數(shù)等。對于不同類型的標(biāo)記使用不同的聯(lián)合體(union),比如表示內(nèi)存時使用tag_mem32,表示命令時使用tag_cmdline。
數(shù)據(jù)結(jié)構(gòu) tag 和tag_header 定義在 Linux 內(nèi)核源碼的include/asm/setup.h 頭文件中(在 U-Boot 的 include/asm-arm/目錄下的 setup.h 中也有定義),如下所示:
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;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
下面以設(shè)置內(nèi)存標(biāo)記、命令標(biāo)記為例說明參數(shù)的傳遞。
2.6.1 設(shè)置標(biāo)記ATAG_CORE
標(biāo)記列表以標(biāo)記 ATAG_CORE 開始,其結(jié)構(gòu)體定義如下:
/* The list must start with an ATAG_COREnode */
#define ATAG_CORE 0x54410001
struct tag_core {
u32 flags; /* bit 0 =read-only */
u32 pagesize;
u32 rootdev;
};
假設(shè) Bootloader與內(nèi)核約定的參數(shù)存放地址為 0x30000100,則可以以如下代碼設(shè)置標(biāo) 記 ATAG_CORE:
#define tag_next(t) ((struct tag *)((u32*)(t) + (t)->hdr.size))
static struct tag *params; //定義全局變量 params,為 struct tag 結(jié)構(gòu)
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params; //bi_boot_params =0x30000100
params->hdr.tag = ATAG_CORE;// ATAG_CORE 在前面定義,指定標(biāo)記類型
params->hdr.size = tag_size (tag_core); //計算標(biāo)記大小
// 設(shè)置 ATAG_CORE 標(biāo)記的內(nèi)容
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
其中,tag_next定義為指向當(dāng)前標(biāo)記的末尾。
2.6.2 設(shè)置內(nèi)存標(biāo)記
內(nèi)存標(biāo)記 tag_mem32 的定義如下:
struct tag_mem32 {
u32 size; // 內(nèi)存的大小
u32 start; /* physical start address */
};
假設(shè)開發(fā)板使用的內(nèi)存起始地址為 0x30000000,大小為 0x4000000,則內(nèi)存標(biāo)記可以如 下設(shè)置:
static void setup_memory_tags (bd_t *bd)
{
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = 0x30000000;
params->u.mem.size = 0x4000000;
params = tag_next (params);
}
2.6.3 設(shè)置命令行標(biāo)記
命令行就是一個字符串,它被用來控制內(nèi)核的一些行為。比如“root=/dev/mtdlock2 init=/linuxrc console=ttySAC0”表示根文件系統(tǒng)在 MTD2 分區(qū)上,系統(tǒng)啟動后執(zhí)行的第一個程序為/linuxrc,控制臺為 ttySAC0。
命令行可以在 Bootloader 中通過命令設(shè)置好,然后按如下構(gòu)造標(biāo)記傳給內(nèi)核。
char *p = ”root=/dev/mtdlock 2init=/linuxrc console=ttySAC0”;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof (structtag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline,p);
params = tag_next (params);
2.6.4 設(shè)置標(biāo)記ATAG_NONE
標(biāo)記列表以標(biāo)記 ATAG_NONE 結(jié)束,如下設(shè)置:
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
-
嵌入式
+關(guān)注
關(guān)注
5059文章
18973瀏覽量
301997 -
Linux
+關(guān)注
關(guān)注
87文章
11207瀏覽量
208712 -
bootloader
+關(guān)注
關(guān)注
2文章
234瀏覽量
45503
原文標(biāo)題:詳談嵌入式之Bootloader
文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論