bootloader

ARM Linux啟動(dòng)流程大致為:bootloader ---->kernel---->root filesystem。bootloader 是一上電就拿到cpu 的控制權(quán)的,而bootloader實(shí)現(xiàn)了硬件的初始化,為kernel的運(yùn)行創(chuàng)造好條件。

那么bootloader一般都會(huì)做些什么

  1. 硬件初始化

    • 屏蔽所有的中斷/關(guān)閉處理器內(nèi)部指令/數(shù)據(jù)cache等.
    • 初始化一個(gè)串口為調(diào)試串口。用于輸出信息。
  2. 初始化RAM

    • kernel一般會(huì)在RAM中運(yùn)行,所以需要設(shè)置和初始化RAM.
    • 設(shè)置CPU的控制寄存器參數(shù),以便正常使用RAM,以及檢測(cè)RAM大小。
  3. 檢測(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)的初始化程序。
  4. 設(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。

  5. 調(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               ---->表示的是變量的地址   
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容