超詳細分析Bootloader到內(nèi)核的啟動流程(萬字長文)

[TOC]

Bootloader啟動流程分析

??Bootloader的啟動過程可以分為單階段蜂大、多階段兩種。通常多階段的 Bootloader能提供更為復雜的功能以及更好的可移植性。從固態(tài)存儲設(shè)備上啟動的 Bootloader大多都是兩階段的啟動過程智蝠。第一階段使用匯編來實現(xiàn)票摇,它完成一些依賴于CPU體系結(jié)構(gòu)的初始化,并調(diào)用第二階段的代碼妙同;第二階段則通常使用C語言來實現(xiàn)射富,這樣可以實現(xiàn)更復雜的功能,而且代碼會有更好的可讀性和可移植性粥帚。
??一般而言辉浦,這兩個階段完成的功能可以如下分類:

Bootloader第一階段的功能

硬件設(shè)備初始化

??首先需要設(shè)置時鐘,設(shè)置MPLL(具體參見下面的FCLK HCLK PCLK 部分)茎辐。接著設(shè)置CLKDIVN地址為0x4C000014宪郊,寫入0x05,表示設(shè)置分頻系數(shù)為FCLK:HCLK:PCLK=1:4:8轨帜。接著,關(guān)閉看門狗锡搜,關(guān)中斷响鹃,啟動ICACHE,關(guān)閉DCACHE和TLB乎串,關(guān)閉MMU(ICACHE為指令緩存店枣,可以不關(guān)閉,指令直接操作的硬件叹誉,實際的物理地址鸯两。但是DCACHE就必須要關(guān)閉,此時MMU沒有使能长豁,虛擬地址映射不成功钧唐,sdram無法訪問,DCACHE無數(shù)據(jù))匠襟。start.s具體代碼如下:

    /* 設(shè)置時鐘 */
        ldr r0, =0x4c000014
        //  mov r1, #0x03;          
        mov r1, #0x05;            // FCLK:HCLK:PCLK=1:4:8
        str r1, [r0]
    
        /* 如果HDIVN非0钝侠,CPU的總線模式應該從“fast bus mode”變?yōu)椤癮synchronous bus mode” */
        mrc p15, 0, r1, c1, c0, 0       /* 讀出控制寄存器 */ 
        orr r1, r1, #0xc0000000         /* 設(shè)置為“asynchronous bus mode” */
        mcr p15, 0, r1, c1, c0, 0       /* 寫入控制寄存器 */
    
        /* MPLLCON = S3C2440_MPLL_200MHZ */
        ldr r0, =0x4c000004
        ldr r1, =S3C2440_MPLL_400MHZ
        str r1, [r0]
        /* 啟動ICACHE */
        mrc p15, 0, r0, c1, c0, 0   @ read control reg
        orr r0, r0, #(1<<12)
        mcr p15, 0, r0, c1, c0, 0   @ write it back

??這里具體講下是如何設(shè)置FCLK HCLK PCLK。
??FCLK又稱為內(nèi)核時鐘酸舍,是提供給ARM920T 的時鐘帅韧。
??HCLK又稱為總線時鐘,是提供給用于存儲器控制器啃勉,中斷控制器忽舟,LCD 控制器,DMA 和 USB 主機模塊的 AHB總線(advanced high-performance bus)的時鐘淮阐。
??PCLK又稱為I/O接口時鐘叮阅,是提供給用于外設(shè)如WDT,IIS枝嘶,I2C帘饶,PWM 定時器,MMC/SD 接口群扶,ADC及刻,UART,GPIO竞阐,RTC 和SPI的 APB (advanced peripherals bus)總線的時鐘缴饭。

??S3C2440 FLCK值為400MHz,HCLK值為100MHz骆莹、PCLK值為50MHz颗搂。那么這些值通過什么方法計算出來呢?S3C2440上的時鐘源是12MHz幕垦,如果想讓CPU工作在更高頻率上丢氢,就需要通過PLL(鎖相環(huán))來提高主頻傅联。S3C2440上的PLL有兩種,一種是MPLL疚察,它是用來產(chǎn)生FCLK蒸走、HCLK、PCLK的高頻工作時鐘貌嫡;還有一種是UPLL比驻,用來為USB提供工作頻率。S3C2440時鐘體系如下:


在這里插入圖片描述

在這里插入圖片描述

??從時序圖中岛抄,我們可以看到别惦,上電之后,如果什么都不設(shè)置夫椭,F(xiàn)CLK和晶振的頻率相等掸掸。當設(shè)置PLL后,CPU并不是馬上就使用設(shè)置好的高頻時鐘益楼,而是有一段鎖定時間猾漫,在這段時間里点晴,CPU停止運行感凤,等12MHz變成高頻時鐘穩(wěn)定以后,整個系統(tǒng)再重新運行粒督。

??開啟MPLL的過程:
??1陪竿、設(shè)置LOCKTIME變頻鎖定時間
??2、設(shè)置FCLK與晶振輸入頻率(Fin)的倍數(shù)
??3屠橄、設(shè)置FCLK族跛,HCLK,PCLK三者之間的比例
??從手冊上可以看到锐墙,LOCKTIME的默認時間是0xFFFFFFFF礁哄,控制方法如圖:
??(剛設(shè)置好PLL時,系統(tǒng)認為這是PLL還沒穩(wěn)定溪北,所有這時不用PLL的時鐘桐绒,而用外部晶振做時鐘,將PLL鎖住之拨,過了LOCKTIME后認為PLL已經(jīng)穩(wěn)定了茉继,才使用PLL給系統(tǒng)提供時鐘)


在這里插入圖片描述

??FCLK與Fin的倍數(shù)通過MPLLCON寄存器設(shè)置,三者之間有以下關(guān)系:
??MPLL(FCLK) = (2 * m * Fin)/(p*2^s)
??其中:m = MDIV + 8, p = PDIV + 2, s = SDIV
??PLL配置寄存器如圖:


在這里插入圖片描述

??當設(shè)置完MPLL之后蚀乔,就會自動進入LockTime變頻鎖定期間烁竭,LOCKTIME之后,MPLL輸出穩(wěn)定時鐘頻率吉挣。
?? FCLK派撕、HCLK婉弹、PCLK的設(shè)置比例如圖:
在這里插入圖片描述

在這里插入圖片描述

??如果HDIV設(shè)置為非0,CPU的總線模式要進行改變终吼,默認情況下FCLK = HCLK马胧,CPU工作在fast bus mode快速總線模式下,HDIV設(shè)置為非0后衔峰, FCLK與HCLK不再相等佩脊,要將CPU改為asynchronous bus mod異步總線模式,可以通過下面的嵌入?yún)R編代碼實現(xiàn):
__asm__(
    "mrc    p15, 0, r1, c1, c0, 0\n"        /* 讀出控制寄存器 */ 
    "orr    r1, r1, #0xc0000000\n"          /* 設(shè)置為“asynchronous bus mode” */
    "mcr    p15, 0, r1, c1, c0, 0\n"        /* 寫入控制寄存器 */
    );

為加載 Bootloader的第二階段代碼準備RAM空間(初始化內(nèi)存空間)

??lowlevel_init中設(shè)置相應BANK地址垫卤,主要用來設(shè)置SDRAM威彰。內(nèi)存是被映射在了0x30000000-0x40000000的位置,即bank6與bank7穴肘。那么在內(nèi)存時序設(shè)置的時候歇盼,主要關(guān)心的,就是bank6與bank7评抚。當然豹缀,bank0也是需要關(guān)注的,因為它是啟動時慨代,啟動程序存放的位置邢笙。但是bank0是由OM[1:0],即板子上的那幾個小開關(guān)中的兩個來控制的侍匙,所以這里程序上是不用管它的氮惯。

SMRDATA:
        .long 0x22011110     //BWSCON
        .long 0x00000700     //BANKCON0
        .long 0x00000700     //BANKCON1
        .long 0x00000700     //BANKCON2
        .long 0x00000700     //BANKCON3  
        .long 0x00000740     //BANKCON4
        .long 0x00000700     //BANKCON5
        .long 0x00018005     //BANKCON6
        .long 0x00018005     //BANKCON7
        .long 0x008C04F4     // REFRESH
        .long 0x000000B1     //BANKSIZE
        .long 0x00000030     //MRSRB6
        .long 0x00000030     //MRSRB7

??接下來設(shè)置棧地址指向NAND,準備初始化NANDFLASH想暗。

    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)//等于0x30000f80
    bic sp, sp, #7 /* 8-byte alignment for ABI compliance */

    bl nand_init_ll

版權(quán)聲明:本文為博主原創(chuàng)文章妇汗,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明说莫。

本文鏈接:https://blog.csdn.net/qq_16933601/article/details/106244510

??初始化NANDFLASH杨箭,其中包括設(shè)置時序NFCONF,(參考芯片手冊和2440手冊設(shè)置nandflsh的啟動時序)储狭。TACLS表示的建立所用的時間互婿,TWRPH0表示nWE寫控制信號的持續(xù)時間,TWRPH1表示數(shù)據(jù)生效所用的時間晶密,什么時候可以讀數(shù)據(jù)擒悬。 最后就是使能NFCONT NAND Flash控制器,初始化ECC, 禁止片選稻艰。到這里懂牧,NANDFLASH的初始化就完成了。下面就可以進行重定位了。

void nand_init_ll(void)
{
#define TACLS   0
#define TWRPH0  1
#define TWRPH1  0
    /* 設(shè)置時序 */
    NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
    /* 使能NAND Flash控制器, 初始化ECC, 禁止片選 */
    NFCONT = (1<<4)|(1<<1)|(1<<0);  
}

復制 Bootloader的第二階段代碼到SDRAM空間中(重定位)

??首先判斷是NOR啟動還是NAND啟動僧凤,如果是NAND啟動就直接拷貝數(shù)據(jù)畜侦。拷貝代碼之前躯保,要傳遞給拷貝函數(shù)三個參數(shù)旋膳,源,目的途事,長度验懊。讀取NAND的話要參考芯片手冊的NAND讀取數(shù)據(jù)的時序,選中NAND尸变,發(fā)出讀命令义图,發(fā)出地址,發(fā)出讀命令召烂,判斷狀態(tài)碱工,讀取數(shù)據(jù),取消選中等奏夫。

    bl copy_code_to_sdram
    bl clear_bss                         //清除bss段(參考自制uboot章節(jié))
void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
{   
    int i = 0;
    
    /* 如果是NOR啟動 */
    if (isBootFromNorFlash())
    {
        while (i < len)
        {
            dest[i] = src[i];
            i++;
        }
    }
    else
    {
        //nand_init();
        nand_read_ll((unsigned int)src, dest, len);
    }
}

void clear_bss(void)
{
    extern int __bss_start, __bss_end__;
    int *p = &__bss_start;
    
    for (; p < &__bss_end__; p++)
        *p = 0;
}

??最后要清除bss怕篷。bss段不占用空間,都是未初始化的全局變量或者已經(jīng)初始化為零的變量酗昼,本來就是零廊谓,直接清零就好。不清零的話未初始化的變量可能會存在未知的數(shù)值仔雷。

設(shè)置好棧

??設(shè)置棧跳轉(zhuǎn)到SDRAM執(zhí)行蹂析。

ldr pc,=call_board_init_f            //絕對跳轉(zhuǎn),跳到SDRAM上執(zhí)行

跳轉(zhuǎn)到第二階段代碼的C入口點

??跳轉(zhuǎn)到SDRAM執(zhí)行剩下的程序舔示。

call_board_init_f:
.globl base_sp
base_sp:
    .long 0

    ldr r0,=0x00000000
    bl  board_init_f
    
    /*unsigned int id 的值存在r0中碟婆,正好給board_init_r使用*/
    ldr r1, =_TEXT_BASE
    /*重新設(shè)置棧到之前的位置 指向原來addr_sp += 128;*/
    ldr sp,base_sp  
    /*調(diào)用第二階段代碼*/
    bl  board_init_r

Bootloader第二階段的功能

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

??為了方便開發(fā),至少要初始化一個串口以便程序員與 Bootloader進行交互惕稻。

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

??所謂檢測內(nèi)存映射竖共,就是確定板上使用了多少內(nèi)存、它們的地址空間是什么俺祠。由于嵌入式開發(fā)中 Bootloader多是針對某類板子進行編寫公给,所以可以根據(jù)板子的情況直接設(shè)置,不需要考慮可以適用于各類情況的復雜算法蜘渣。

將內(nèi)核映象和根文件系統(tǒng)映象從 Flash上讀到SDRAM空間中

??Flash上的內(nèi)核映象有可能是經(jīng)過壓縮的淌铐,在讀到SDRAM之后,還需要進行解壓蔫缸。當然腿准,對于有自解壓功能的內(nèi)核,不需要 Bootloader來解壓。將根文件系統(tǒng)映象復制到SDRAM中吐葱,這不是必需的街望。這取決于是什么類型的根文件系統(tǒng)以及內(nèi)核訪問它的方法。

??將內(nèi)核存放在適當?shù)奈恢煤蟮芘埽苯犹剿娜肟邳c即可調(diào)用內(nèi)核灾前。調(diào)用內(nèi)核之前,下列條件要滿足:
??(1)CPU寄存器的設(shè)置
??R0=0(規(guī)定)孟辑。
??R1=機器類型ID哎甲;對于ARM結(jié)構(gòu)的CPU,其機器類型ID可以參見 linux/arch/arm tools/ mach-types
??R2=啟動參數(shù)標記列表在RAM中起始基地址(下面會詳細介紹如何傳遞參數(shù))饲嗽。
??(2)CPU工作模式
??必須禁止中斷(IRQ和FIQ烧给,uboot啟動是一個完整的過程,沒有必要也不能被打斷)
??CPU必須為SVC模式(為什么呢喝噪?主要是像異常模式础嫡、用戶模式都不合適。具體深入的原因自己可以查下資料)酝惧。
??(3) Cache和MMU的設(shè)置
??MMU必須關(guān)閉榴鼎。
??指令 Cache可以打開也可以關(guān)閉。
??數(shù)據(jù) Cache必須關(guān)閉晚唇。

為內(nèi)核設(shè)置啟動參數(shù)

??Bootloader與內(nèi)核的交互是單向的巫财, Bootloader將各類參數(shù)傳給內(nèi)核。由于它們不能同時行哩陕,傳遞辦法只有一個:Bootloader將參數(shù)放在某個約定的地方之后平项,再啟動內(nèi)核,內(nèi)核啟動后從這個地方獲得參數(shù)悍及。

??除了約定好參數(shù)存放的地址外闽瓢,還要規(guī)定參數(shù)的結(jié)構(gòu)。Linu2.4x以后的內(nèi)核都期望以標記列表( tagged_list)的形式來傳遞啟動參數(shù)心赶。標記扣讼,就是一種數(shù)據(jù)結(jié)構(gòu);標記列表缨叫,就是挨著存放的多個標記椭符。標記列表以標記 ATAG CORE開始,以標記 ATAG NONE結(jié)束耻姥。

??標記的數(shù)據(jù)結(jié)構(gòu)為tag销钝,它由一個 tag_header結(jié)構(gòu)和一個聯(lián)合(union)組成。 tag_ header結(jié)構(gòu)表小標記的類型及長度琐簇,比如是表示內(nèi)存還是表示命令行參數(shù)等蒸健。對于不同類型的標記使用不同的聯(lián)合(union),比如表示內(nèi)存時使用 tag_mem32,表示命令行時使用 tag_cmdline纵装。

??bootloader與內(nèi)核約定的參數(shù)地址征讲,設(shè)置內(nèi)存的起始地址和大小,指定根文件系統(tǒng)在那個分區(qū)橡娄,系統(tǒng)啟動后執(zhí)行的第一個程序linuxrc诗箍,控制臺ttySAC0等。

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

??調(diào)用內(nèi)核就是uboot啟動的最后一步了挽唉。到這里就uboot就完成了他的使命滤祖。

uboot啟動內(nèi)核詳解

??下面我們來展開說下uboot具體是如何調(diào)用內(nèi)核的,引導內(nèi)核啟動的瓶籽。

uboot與Linux內(nèi)核之間的參數(shù)傳遞

??我們知道匠童,uboot啟動后已經(jīng)完成了基本的硬件初始化(如:內(nèi)存、串口等)塑顺,接下來它的主要任務就是加載Linux內(nèi)核到開發(fā)板的內(nèi)存汤求,然后跳轉(zhuǎn)到Linux內(nèi)核所在的地址運行。

??具體是如何跳轉(zhuǎn)呢严拒?做法很簡單扬绪,直接修改PC寄存器的值為Linux內(nèi)核所在的地址,這樣CPU就會從Linux內(nèi)核所在的地址去取指令裤唠,從而執(zhí)行內(nèi)核代碼挤牛。

??在前面我們已經(jīng)知道,在跳轉(zhuǎn)到內(nèi)核以前种蘸,uboot需要做好以下三件事情:

??(1) CPU寄存器的設(shè)置
??R0=0墓赴。
??R1=機器類型ID;對于ARM結(jié)構(gòu)的CPU航瞭,其機器類型ID可以參見 linux/arch/arm tools/ mach-types
??R2=啟動參數(shù)標記列表在RAM中起始基地址诫硕。
??(2) CPU工作模式
??必須禁止中斷(IRQs和FIQs)
??CPU必須為SVC模式
??(3) Cache和MMU的設(shè)置
??MMU必須關(guān)閉
??指令 Cache可以打開也可以關(guān)閉
??數(shù)據(jù) Cache必須關(guān)閉

??其中上面第一步CPU寄存器的設(shè)置中,就是通過R0,R1,R2三個參數(shù)給內(nèi)核傳遞參數(shù)的沧奴。(ATPCS規(guī)則可以參考

為什么要給內(nèi)核傳遞參數(shù)呢痘括?

??在此之前,uboot已經(jīng)完成了硬件的初始化滔吠,可以說已經(jīng)”適應了“這塊開發(fā)板。然而挠日,內(nèi)核并不是對于所有的開發(fā)板都能完美適配的(如果適配了疮绷,可想而知這個內(nèi)核有多龐大,又或者有新技術(shù)發(fā)明了嚣潜,可以完美的適配各種開發(fā)板)冬骚,此時對于開發(fā)板的環(huán)境一無所知。所以,要想啟動Linux內(nèi)核只冻,uboot必須要給內(nèi)核傳遞一些必要的信息來告訴內(nèi)核當前所處的環(huán)境庇麦。

如何給內(nèi)核傳遞參數(shù)?

??因此喜德,uboot就把機器ID通過R1傳遞給內(nèi)核山橄,Linux內(nèi)核運行的時候首先就從R1中讀取機器ID來判斷是否支持當前機器。這個機器ID實際上就是開發(fā)板CPU的ID舍悯,每個廠家生產(chǎn)出一款CPU的時候都會給它指定一個唯一的ID航棱,大家可以到uboot源碼的arch\arm\include\asm\mach-type.h文件中去查看。

在這里插入圖片描述

??R2存放的是塊內(nèi)存的基地址萌衬,這塊內(nèi)存中存放的是uboot給Linux內(nèi)核的其他參數(shù)饮醇。這些參數(shù)有內(nèi)存的起始地址、內(nèi)存大小秕豫、Linux內(nèi)核啟動后掛載文件系統(tǒng)的方式等信息朴艰。很明顯,參數(shù)有多個混移,不同的參數(shù)有不同的內(nèi)容呵晚,為了讓Linux內(nèi)核能精確的解析出這些參數(shù),雙方在傳遞參數(shù)的時候要求參數(shù)在存放的時猴需要按照雙方規(guī)定的格式存放沫屡。

??除了約定好參數(shù)存放的地址外饵隙,還要規(guī)定參數(shù)的結(jié)構(gòu)。Linux2.4.x以后的內(nèi)核都期望以標記列表(tagged_list)的形式來傳遞啟動參數(shù)沮脖。標記金矛,就是一種數(shù)據(jù)結(jié)構(gòu);標記列表勺届,就是挨著存放的多個標記驶俊。標記列表以標記ATAG_CORE開始,以標記ATAG_NONE結(jié)束免姿。

??標記的數(shù)據(jù)結(jié)構(gòu)為tag饼酿,它由一個tag_header結(jié)構(gòu)和一個聯(lián)合(union)組成。tag_header結(jié)構(gòu)表示標記的類型及長度胚膊,比如是表示內(nèi)存還是表示命令行參數(shù)等故俐。對于不同類型的標記使用不同的聯(lián)合(union),比如表示內(nèi)存時使用tag_ mem32紊婉,表示命令行時使用 tag_cmdline药版。具體代碼見arch\arm\include\asm\setup.h。

在這里插入圖片描述

??從上面可以看出喻犁,struct_tag結(jié)構(gòu)體由structtag_header+聯(lián)合體union構(gòu)成槽片,結(jié)構(gòu)體struct tag_header用來描述每個tag的頭部信息何缓,如tag的類型,tag大小还栓。聯(lián)合體union用來描述每個傳遞給Linux內(nèi)核的參數(shù)信息碌廓。
??下面以傳遞內(nèi)存標記、傳遞命令行參數(shù)為例來說明參數(shù)的傳遞剩盒。
??(1)設(shè)置開始標記ATAG_CORE

    tag->hdr.tag  = ATAG_CORE;
    tag->hdr.size = tag_size(tag_core);
    tag->u.core.flags = params->u1.s.flags & FLAG_READONLY;
    tag->u.core.pagesize = params->u1.s.page_size;
    tag->u.core.rootdev = params->u1.s.rootdev;

    tag = tag_next(tag);

??涉及到的結(jié)構(gòu)體定義如下

struct tag_header {
    __u32 size;
    __u32 tag;
};

/* The list must start with an ATAG_CORE node */
#define ATAG_CORE   0x54410001

struct tag_core {
    __u32 flags;        /* bit 0 = read-only */
    __u32 pagesize;
    __u32 rootdev;
};

??其中tag_next谷婆,tag_size定義如下,指向當前標記的結(jié)尾

版權(quán)聲明:本文為博主原創(chuàng)文章勃刨,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議波材,轉(zhuǎn)載請附上原文出處鏈接和本聲明。

本文鏈接:https://blog.csdn.net/qq_16933601/article/details/106244510

#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type)  ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)

??(2)設(shè)置內(nèi)存標記

    t->hdr.tag = ATAG_MEM;
    t->hdr.size = tag_size(tag_mem32);
    t->u.mem.start = CFG_GLOBAL_RAM_BASE;
    t->u.mem.size = CFG_GLOBAL_RAM_SIZE;

    t = tag_next(t);

??相關(guān)結(jié)構(gòu)體定義如下

#define ATAG_MEM    0x54410002

struct tag_mem32 {
    __u32   size;
    __u32   start;  /* physical start address */
};

??(3)設(shè)置命令行參數(shù)標記

??命令行參數(shù)是一個字符串身隐,一般用它來告訴內(nèi)核掛載根文件系統(tǒng)的方式廷区。由uboot的bootargs環(huán)境變量提供,它的內(nèi)容有如下兩種格式

root=nfs nfsroot=202.193.61.237:/work/nfs_root/first_fs ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200
root=/dev/mtdblock2 ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200
名稱 含義
root 告訴Linux內(nèi)核掛載根文件系統(tǒng)的方式贾铝,nfs表示以NFS服務的方式掛載根文件系統(tǒng)隙轻,/dev/mtdblock2表示根文件系統(tǒng)在MTD設(shè)置的第二個分區(qū)上。
nfsroot 告訴Linux內(nèi)核垢揩,以NFS方式掛載根文件系統(tǒng)時玖绿,根文件系統(tǒng)所在主機的P地址和路徑
ip 告訴Linux內(nèi)核,啟動后它的p地址
init 告訴Linux內(nèi)核叁巨,啟動的第一個應用程序是根目錄下的linuxrc程序
console 告訴Linux區(qū)內(nèi)核斑匪,控制臺為ttySAC0,波特率為115200
tag = tag_next(tag);
tag->hdr.tag = ATAG_CMDLINE;
tag->hdr.size = (strlen(params->commandline) + 3 +
         sizeof(struct tag_header)) >> 2;
strcpy(tag->u.cmdline.cmdline, params->commandline);

tag = tag_next(tag);

??相關(guān)結(jié)構(gòu)體定義如下

/* command line: \0 terminated string */
#define ATAG_CMDLINE    0x54410003

struct tag_cmdline {
    char    cmdline[1]; /* this is the minimum size */
};

??(4)設(shè)置結(jié)束標記

    tag->hdr.tag = ATAG_NONE;
    tag->hdr.size = 0;

??我們明白了運行Linux區(qū)內(nèi)核的時候,uboot需要給內(nèi)核的傳遞的參數(shù)锋勺,接下來我們就來看看如何從uboot中跳到Linux內(nèi)核蚀瘸。

uboot跳轉(zhuǎn)到Linux內(nèi)核

??在uboot中可以使用go和bootm來跳轉(zhuǎn)到內(nèi)核,這兩個命令的區(qū)別如下:

??(1) go命令僅僅修改pc的值到指定地址

??格式:go addr

??(2) bootm命令是uboot專門用來啟動uImage格式的Linux內(nèi)核庶橱,它在修改pc的值到指定地址之前贮勃,會設(shè)置傳遞給Linux內(nèi)核的參數(shù),用法如下:

??格式:bootm addr

版權(quán)聲明:本文為博主原創(chuàng)文章苏章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議寂嘉,轉(zhuǎn)載請附上原文出處鏈接和本聲明。

本文鏈接:https://blog.csdn.net/qq_16933601/article/details/106244510

uboot中bootm命令實現(xiàn)

??bootm命令在uboot源碼common/cmd_bootm.c中實現(xiàn)枫绅,它的功能如下:

??(1)讀取uImage頭部泉孩,把內(nèi)核拷貝到合適的地方。

??(2)把參數(shù)給內(nèi)核準備好撑瞧。

??(3)引導內(nèi)核棵譬。

??當我們使用我們在uboot使用bootm命令后,bootm命令會從uImage頭中讀取信息后预伺,發(fā)現(xiàn)是Linux內(nèi)核订咸,就會調(diào)用do_bootm_linux()函數(shù),函數(shù)的具體實現(xiàn)bootm.c中

int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
    /* No need for those on ARM */
    if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
        return -1;

    if (flag & BOOTM_STATE_OS_PREP) {
        boot_prep_linux(images);
        return 0;
    }

    if (flag & BOOTM_STATE_OS_GO) {
        boot_jump_linux(images);
        return 0;
    }

    boot_prep_linux(images);
    boot_jump_linux(images);
    return 0;
}

??do_bootm_linux 函數(shù)最終會 跳轉(zhuǎn)執(zhí)行 boot_prep_linux 和 boot_jump_linux 函數(shù)酬诀,首先分析 boot_prep_linux 函數(shù)(位于 bootm.c 文件中):

static void boot_prep_linux(bootm_headers_t *images)
{
    char *commandline = getenv("bootargs");      //從環(huán)境變量中獲取 bootargs 的值

  脏嚷。。瞒御。父叙。。肴裙。趾唱。
        setup_board_tags(&params);      
        setup_end_tag(gd->bd);    //將 tag 參數(shù)保存在指定位置
    } else {
        printf("FDT and ATAGS support not compiled in - hanging\n");
        hang();
    }
    do_nonsec_virt_switch();
}

?? 從代碼可以看出來,boot_prep_linux蜻懦,主要功能是將 tag 參數(shù)保存到指定位置甜癞,比如 bootargs 環(huán)境變量 tag,串口 tag宛乃,接下來分析 boot_jump_linux 函數(shù)(位于 bootm.c 文件中):

static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    unsigned long machid = gd->bd->bi_arch_number;      //獲取機器id (在 board/samsung/jz2440/jz2440.c 中設(shè)置悠咱,為 MACH_TYPE_SMDK2410(193))
    char *s;
    void (*kernel_entry)(int zero, int arch, uint params);
    unsigned long r2;
    int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

    kernel_entry = (void (*)(int, int, uint))images->ep;    //獲取 kernel的入口地址,此處應為 30000000

    s = getenv("machid");        //從環(huán)境變量里獲取機器id (本例中還未在環(huán)境變量里設(shè)置過機器 id)
    if (s) {            //判斷環(huán)境變量里是否設(shè)置機器id
        strict_strtoul(s, 16, &machid);    //如果設(shè)置則用環(huán)境變量里的機器id
        printf("Using machid 0x%lx from environment\n", machid);
    }

    debug("## Transferring control to Linux (at address %08lx)" \
        "...\n", (ulong) kernel_entry);
    bootstage_mark(BOOTSTAGE_ID_RUN_OS);
    announce_and_cleanup(fake);

    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
        r2 = (unsigned long)images->ft_addr;
    else
        r2 = gd->bd->bi_boot_params;    //獲取 tag參數(shù)地址征炼,gd->bd->bi_boot_params在 setup_start_tag 函數(shù)里設(shè)置
if (!fake) kernel_entry(0, machid, r2); }  //進入內(nèi)核

??通過分析可以看出析既,最終進入內(nèi)核的函數(shù)為 :

kernel_entry(0, machid, r2)

??到這里bootm就成功給內(nèi)核傳遞了參數(shù),并跳轉(zhuǎn)到了內(nèi)核谆奥。關(guān)于go命令的實現(xiàn)可以自己參考內(nèi)核眼坏,在cmd_boot.c文件中,所不同的是酸些,go命令實現(xiàn)的時候沒有設(shè)置參數(shù)宰译,只是簡單的跳轉(zhuǎn)執(zhí)行。如果想要使用go來跳轉(zhuǎn)到Linux內(nèi)核擂仍,我們需要做簡單的修改囤屹,有興趣的可以自己研究下,這里就不展開講了逢渔。

??至此肋坚,uboot就啟動了內(nèi)核。啟動內(nèi)核后就是掛載根文件系統(tǒng)了肃廓,下篇將具體介紹是如何掛載根文件系統(tǒng)的智厌。
??構(gòu)建根文件系統(tǒng)

內(nèi)核鏡像格式vmlinuz和zImage和uImage

??最后插講下內(nèi)核的不同映像格式的區(qū)別:

??(1)uboot經(jīng)過編譯直接生成的elf格式的可執(zhí)行程序是u-boot,這個程序類似于windows下的exe格式盲赊,在操作系統(tǒng)下是可以直接執(zhí)行的铣鹏。但是這種格式不能用來燒錄下載。我們用來燒錄下載的是u-boot.bin哀蘑,這個東西是由u-boot使用arm-linux-objcopy工具進行加工(主要目的是去掉一些無用的)得到的诚卸。這個u-boot.bin就叫鏡像(image)葵第,鏡像就是用來燒錄到iNand中執(zhí)行的。

??(2)linux內(nèi)核經(jīng)過編譯后也會生成一個elf格式的可執(zhí)行程序合溺,叫vmlinux或vmlinuz卒密,這個就是原始的未經(jīng)任何處理加工的原版內(nèi)核elf文件;嵌入式系統(tǒng)部署時燒錄的一般不是這個vmlinuz/vmlinux棠赛,而是要用objcopy工具去制作成燒錄鏡像格式(就是u-boot.bin這種哮奇,但是內(nèi)核沒有.bin后綴),經(jīng)過制作加工成燒錄鏡像的文件就叫Image(制作把78M大的精簡成了7.5M睛约,因此這個制作燒錄鏡像主要目的就是縮減大小鼎俘,節(jié)省磁盤)。

??(3)原則上Image就可以直接被燒錄到Flash上進行啟動執(zhí)行(類似于u-boot.bin)辩涝,但是實際上并不是這么簡單贸伐。實際上linux的作者們覺得Image還是太大了所以對Image進行了壓縮,并且在image壓縮后的文件的前端附加了一部分解壓縮代碼膀值。構(gòu)成了一個壓縮格式的鏡像就叫zImage棍丐。(因為當年Image大小剛好比一張軟盤(軟盤有2種,1.2M的和1.44MB兩種)大沧踏,為了節(jié)省1張軟盤的錢于是乎設(shè)計了這種壓縮Image成zImage的技術(shù))歌逢。

??(4)uboot為了啟動linux內(nèi)核,還發(fā)明了一種內(nèi)核格式叫uImage翘狱。uImage是由zImage加工得到的秘案,uboot中有一個工具,可以將zImage加工生成uImage潦匈。注意:uImage不關(guān)linux內(nèi)核的事阱高,linux內(nèi)核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage來給uboot啟動茬缩。這個加工過程其實就是在zImage前面加上64字節(jié)的uImage的頭信息即可赤惊。

??(5)原則上uboot啟動時應該給他uImage格式的內(nèi)核鏡像,但是實際上uboot中也可以支持zImage凰锡,是否支持就看x210_sd.h中是否定義了LINUX_ZIMAGE_MAGIC這個宏未舟。所以大家可以看出:有些uboot是支持zImage啟動的,有些則不支持掂为。但是所有的uboot肯定都支持uImage啟動裕膀。

??(6)如果直接在kernel底下去make uImage會提供mkimage command not found。解決方案是去uboot/tools下cp mkimage /usr/local/bin/勇哗,復制mkimage工具到系統(tǒng)目錄下昼扛。再去make uImage即可。

??通過上面的介紹我們了解了內(nèi)核鏡像的各種格式欲诺,如果通過uboot啟動內(nèi)核抄谐,Linux必須為uImage格式渺鹦。

??大家的鼓勵是我繼續(xù)創(chuàng)作的動力,如果覺得寫的不錯斯稳,歡迎關(guān)注海铆,點贊迹恐,收藏挣惰,轉(zhuǎn)發(fā),謝謝殴边!

在這里插入圖片描述

版權(quán)聲明:本文為博主原創(chuàng)文章憎茂,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明锤岸。

本文鏈接:https://blog.csdn.net/qq_16933601/article/details/106244510

本文參考:<嵌入式Linux應用開發(fā)完全手冊>

如遇到排版錯亂的問題竖幔,可以通過以下鏈接訪問我的CSDN。

CSDN:CSDN搜索“嵌入式與Linux那些事

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末是偷,一起剝皮案震驚了整個濱河市拳氢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛋铆,老刑警劉巖馋评,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異刺啦,居然都是意外死亡留特,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門玛瘸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜕青,“玉大人,你說我怎么就攤上這事糊渊∮液耍” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵渺绒,是天一觀的道長贺喝。 經(jīng)常有香客問我,道長芒篷,這世上最難降的妖魔是什么搜变? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮针炉,結(jié)果婚禮上挠他,老公的妹妹穿的比我還像新娘。我一直安慰自己篡帕,他們只是感情好殖侵,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布贸呢。 她就那樣靜靜地躺著,像睡著了一般拢军。 火紅的嫁衣襯著肌膚如雪楞陷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天茉唉,我揣著相機與錄音固蛾,去河邊找鬼。 笑死度陆,一個胖子當著我的面吹牛艾凯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播懂傀,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼趾诗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹬蚁?” 一聲冷哼從身側(cè)響起恃泪,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎犀斋,沒想到半個月后贝乎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡闪水,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年糕非,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片球榆。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡朽肥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出持钉,到底是詐尸還是另有隱情衡招,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布每强,位于F島的核電站始腾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏空执。R本人自食惡果不足惜浪箭,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辨绊。 院中可真熱鬧奶栖,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冻晤,卻和暖如春苇羡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鼻弧。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工设江, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人温数。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓绣硝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親撑刺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容