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