ARM Linux啟動(dòng)流程大致為:bootloader ---->kernel---->root filesystem。bootloader 是一上電就拿到cpu 的控制權(quán)的,而bootloader實(shí)現(xiàn)了硬件的初始化,為kernel的運(yùn)行創(chuàng)造好條件。
那么bootloader一般都會(huì)做些什么
-
硬件初始化
- 屏蔽所有的中斷/關(guān)閉處理器內(nèi)部指令/數(shù)據(jù)cache等.
- 初始化一個(gè)串口為調(diào)試串口。用于輸出信息。
-
初始化RAM
- kernel一般會(huì)在RAM中運(yùn)行,所以需要設(shè)置和初始化RAM.
- 設(shè)置CPU的控制寄存器參數(shù),以便正常使用RAM,以及檢測(cè)RAM大小。
-
檢測(cè)處理器類型
- Bootloader在調(diào)用Linux內(nèi)核前必須檢測(cè)系統(tǒng)的處理器類型,并將其保存到某個(gè)常量中提供給Linux內(nèi)核。
- Linux內(nèi)核在啟動(dòng)過(guò)程中會(huì)根據(jù)該處理器類型調(diào)用相應(yīng)的初始化程序。
-
設(shè)置linux的啟動(dòng)參數(shù)
Bootloader在執(zhí)行過(guò)程中必須設(shè)置和初始化Linux的內(nèi)核啟動(dòng)參數(shù)。bootloader需要傳很多參數(shù)給kernel,對(duì)我們來(lái)說(shuō),我們?cè)陂_(kāi)發(fā)的時(shí)候,經(jīng)常更換硬件芯片,我們需要在軟件上實(shí)現(xiàn)兼容,就需要把芯片的信息從bootloader傳到kernel。
調(diào)用linux內(nèi)核映像
如果Linux內(nèi)核存放在Flash中,并且可直接在上面運(yùn)行(這里的Flash指NorFlash),那么可直接跳轉(zhuǎn)到內(nèi)核中去執(zhí)行。但由于在Flash中執(zhí)行代碼會(huì)有種種限制,而且速度也遠(yuǎn)不及RAM快,所以一般的嵌入式系統(tǒng)都是將Linux內(nèi)核拷貝到RAM中,然后跳轉(zhuǎn)到RAM中去執(zhí)行。
/======================================================/
【實(shí)現(xiàn)細(xì)節(jié)】
工作在啟動(dòng)加載模式時(shí),uboot會(huì)自動(dòng)執(zhí)行bootcmd命令,
比如:
bootcmd=“nand read 0x100000 0x80000000 0x300000; bootm 0x80000000”
uboot首先把內(nèi)核鏡像拷貝到內(nèi)存地址為0x80000000的地方,然后執(zhí)行bootm 0x80000000命令。
bootm命令實(shí)際上調(diào)用的是do_bootm_linux函數(shù):
內(nèi)核調(diào)用函數(shù):theKernel (0,bd->bi_arch_number, bd->bi_boot_params);
the kernel其實(shí)不是個(gè)函數(shù),而是指向內(nèi)核入口地址的指針,把它強(qiáng)行轉(zhuǎn)化為帶三個(gè)參數(shù)的函數(shù)指針,會(huì)把三個(gè)參數(shù)保存到通用寄存器中,實(shí)現(xiàn)了向kernel傳遞信息的功能,在這個(gè)例子里,會(huì)把R0賦值為0,R1賦值為機(jī)器號(hào) R2賦值為啟動(dòng)參數(shù)數(shù)據(jù)結(jié)構(gòu)的首地址。
r0,r1,r2三個(gè)寄存器的設(shè)置
bootloader啟動(dòng)內(nèi)核時(shí),會(huì)設(shè)置r0,r1,r2三個(gè)寄存器,
r0一般設(shè)置為0;
r1一般設(shè)置為machine id (在使用設(shè)備樹(shù)時(shí)該參數(shù)沒(méi)有被使用);
r2一般設(shè)置ATAGS或DTB的開(kāi)始地址;
這里的machine id,是讓內(nèi)核知道是哪個(gè)CPU,從而調(diào)用對(duì)應(yīng)的初始化函數(shù)。
以前沒(méi)有使用設(shè)備樹(shù)時(shí),需要bootloader傳一個(gè)machine id給內(nèi)核,現(xiàn)在使用設(shè)備樹(shù)的話,這個(gè)參數(shù)就不需要設(shè)置了。
r2要么是以前的ATAGS開(kāi)始地址,要么是現(xiàn)在使用設(shè)備樹(shù)后的DTB文件開(kāi)始地址。
【繼續(xù)深入】
uboot會(huì)把machine_id傳給內(nèi)核,內(nèi)核啟動(dòng)的時(shí)候會(huì)根據(jù)這個(gè)machine_id來(lái)比較內(nèi)核machine_desc(機(jī)器描述結(jié)構(gòu)體)中的.nr,如果相等,就選中了對(duì)應(yīng)的machine_desc(機(jī)器描述結(jié)構(gòu)體)),然后調(diào)用machine_desc(機(jī)器描述結(jié)構(gòu)體)中的.init(初始化函數(shù))。
在設(shè)備樹(shù)中它有一項(xiàng),在根節(jié)點(diǎn)中有model、compatible,設(shè)備樹(shù)就會(huì)根據(jù)compatible屬性(是一系列的字符串),內(nèi)核就是根據(jù)這一系列的字符串找到匹配的machine_desc,
model = "SMDK24440";
compatible = "samsung,smdk2440", "samsung,smdk2410","samsung,smdk24xx;
1、需要在設(shè)備樹(shù)文件中聲明,單板需要什么樣的machine_desc,(可以是一系列的字符串,kernel會(huì)從左到右匹配這些字符串,一直找到匹配的為止);
2、kernel中需要表明每個(gè)machine_desc需要表明它能支持哪些單板,用字符串表明支持哪些單板。
static const char *const smdk2440_dt_compat[] __initconst = { //可以寫(xiě)入一個(gè)或者多個(gè)單板的名字
"samsung,smdk2440", //表示一種單板
NULL
};
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben-linux@fluff.org> */
.atag_offset = 0x100,
.dt_compat = smdk2440_dt_compat, //支持哪些單板
.init_irq = s3c2440_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.init_time = smdk2440_init_time,
MACHINE_END
MACHINE_START和 MACHINE_END實(shí)際上被展開(kāi)成一個(gè)結(jié)構(gòu)體
#defineMACHINE_START(_type,_name) \
staticconst struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init")))= { \
.nr =MACH_TYPE_##_type, \
.name =_name,
#defineMACHINE_END \
};
3、kernel有多個(gè)machine_desc跟設(shè)備樹(shù)文件dts中的compatible 吻合,選擇哪個(gè)?
設(shè)備樹(shù)文件dts中compatible(屬性值)從左到右的屬性值與kernel中的machine_desc結(jié)構(gòu)體中的dt_compat成員進(jìn)行比較,匹配成功之后就不會(huì)再進(jìn)行匹配(設(shè)備書(shū)的屬性值從左右匹配優(yōu)先級(jí)依次降低)。
從內(nèi)核的第一個(gè)執(zhí)行文件head.S開(kāi)始分析
a. __lookup_processor_type : 使用匯編指令讀取CPU ID, 根據(jù)該ID找到對(duì)應(yīng)的proc_info_list結(jié)構(gòu)體(里面含有這類CPU的初始化函數(shù)、信息)
b. __vet_atags : 判斷是否存在可用的ATAGS或DTB
c. __create_page_tables : 創(chuàng)建頁(yè)表, 即創(chuàng)建虛擬地址和物理地址的映射關(guān)系
d. __enable_mmu : 使能MMU, 以后就要使用虛擬地址了
e. __mmap_switched : 上述函數(shù)里將會(huì)調(diào)用__mmap_switched
f. 把bootloader傳入的r2參數(shù), 保存到變量__atags_pointer中
g. 調(diào)用C函數(shù)start_kernel
start_kernel的調(diào)用過(guò)程如下:
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c
early_init_dt_verify(phys_to_virt(dt_phys) // 判斷是否有效的dtb, drivers/of/ftd.c
initial_boot_params = params;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); // 找到最匹配的machine_desc, drivers/of/ftd.c
while ((data = get_next_compat(&compat))) {
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
machine_desc = mdesc;
注意:
C語(yǔ)言中的變量在匯編語(yǔ)言中出現(xiàn),變量名表示的是變量的地址
unsigned int processor_id; ---->setup.c
long processor_id ---->表示的是變量的地址