@
目錄- Bootloader啟動流程分析
- Bootloader第一階段的功能
- 硬體設備初始化
- 為加載 Bootloader的第二階段代碼準備RAM空間(初始化記憶體空間)
- 復制 Bootloader的第二階段代碼到SDRAM空間中(重定位)
- 設定好堆疊
- 跳轉到第二階段代碼的C入口點
- Bootloader第二階段的功能
- 初始化本階段要使用到的硬體設備
- 檢測系統記憶體映射( memory map)
- 將內核映象和根檔案系統映象從 Flash上讀到SDRAM空間中
- 為內核設定啟動引數
- 呼叫內核
- uboot啟動內核詳解
- uboot與Linux內核之間的引數傳遞
- 為什么要給內核傳遞引數呢?
- 如何給內核傳遞引數?
- uboot跳轉到Linux內核
- uboot中bootm命令實作
- 內核鏡像格式vmlinuz和zImage和uImage
- uboot與Linux內核之間的引數傳遞
- Bootloader第一階段的功能
Bootloader啟動流程分析
??Bootloader的啟動程序可以分為單階段、多階段兩種,通常多階段的 Bootloader能提供更為復雜的功能以及更好的可移植性,從固態存盤設備上啟動的 Bootloader大多都是兩階段的啟動程序,第一階段使用匯編來實作,它完成一些依賴于CPU體系結構的初始化,并呼叫第二階段的代碼;第二階段則通常使用C語言來實作,這樣可以實作更復雜的功能,而且代碼會有更好的可讀性和可移植性,
??一般而言,這兩個階段完成的功能可以如下分類:
Bootloader第一階段的功能
硬體設備初始化
??首先需要設定時鐘,設定MPLL(具體參見下面的FCLK HCLK PCLK 部分),接著設定CLKDIVN地址為0x4C000014,寫入0x05,表示設定分頻系數為FCLK:HCLK:PCLK=1:4:8,接著,關閉看門狗,關中斷,啟動ICACHE,關閉DCACHE和TLB,關閉MMU(ICACHE為指令快取,可以不關閉,指令直接操作的硬體,實際的物理地址,但是DCACHE就必須要關閉,此時MMU沒有使能,虛擬地址映射不成功,sdram無法訪問,DCACHE無資料),start.s具體代碼如下:
/* 設定時鐘 */
ldr r0, =0x4c000014
// mov r1, #0x03;
mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8
str r1, [r0]
/* 如果HDIVN非0,CPU的總線模式應該從“fast bus mode”變為“asynchronous bus mode” */
mrc p15, 0, r1, c1, c0, 0 /* 讀出控制暫存器 */
orr r1, r1, #0xc0000000 /* 設定為“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
??這里具體講下是如何設定FCLK HCLK PCLK,
??FCLK又稱為內核時鐘,是提供給ARM920T 的時鐘,
??HCLK又稱為總線時鐘,是提供給用于存盤器控制器,中斷控制器,LCD 控制器,DMA 和 USB 主機模塊的 AHB總線(advanced high-performance bus)的時鐘,
??PCLK又稱為I/O介面時鐘,是提供給用于外設如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(鎖相環)來提高主頻,S3C2440上的PLL有兩種,一種是MPLL,它是用來產生FCLK、HCLK、PCLK的高頻作業時鐘;還有一種是UPLL,用來為USB提供作業頻率,S3C2440時鐘體系如下:


??從時序圖中,我們可以看到,上電之后,如果什么都不設定,FCLK和晶振的頻率相等,當設定PLL后,CPU并不是馬上就使用設定好的高頻時鐘,而是有一段鎖定時間,在這段時間里,CPU停止運行,等12MHz變成高頻時鐘穩定以后,整個系統再重新運行,
??開啟MPLL的程序:
??1、設定LOCKTIME變頻鎖定時間
??2、設定FCLK與晶振輸入頻率(Fin)的倍數
??3、設定FCLK,HCLK,PCLK三者之間的比例
??從手冊上可以看到,LOCKTIME的默認時間是0xFFFFFFFF,控制方法如圖:
??(剛設定好PLL時,系統認為這是PLL還沒穩定,所有這時不用PLL的時鐘,而用外部晶振做時鐘,將PLL鎖住,過了LOCKTIME后認為PLL已經穩定了,才使用PLL給系統提供時鐘)

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

??當設定完MPLL之后,就會自動進入LockTime變頻鎖定期間,LOCKTIME之后,MPLL輸出穩定時鐘頻率,
?? FCLK、HCLK、PCLK的設定比例如圖:


??如果HDIV設定為非0,CPU的總線模式要進行改變,默認情況下FCLK = HCLK,CPU作業在fast bus mode快速總線模式下,HDIV設定為非0后, FCLK與HCLK不再相等,要將CPU改為asynchronous bus mod異步總線模式,可以通過下面的嵌入匯編代碼實作:
__asm__(
"mrc p15, 0, r1, c1, c0, 0\n" /* 讀出控制暫存器 */
"orr r1, r1, #0xc0000000\n" /* 設定為“asynchronous bus mode” */
"mcr p15, 0, r1, c1, c0, 0\n" /* 寫入控制暫存器 */
);
為加載 Bootloader的第二階段代碼準備RAM空間(初始化記憶體空間)
??lowlevel_init中設定相應BANK地址,主要用來設定SDRAM,記憶體是被映射在了0x30000000-0x40000000的位置,即bank6與bank7,那么在記憶體時序設定的時候,主要關心的,就是bank6與bank7,當然,bank0也是需要關注的,因為它是啟動時,啟動程式存放的位置,但是bank0是由OM[1:0],即板子上的那幾個小開關中的兩個來控制的,所以這里程式上是不用管它的,
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
??接下來設定堆疊地址指向NAND,準備初始化NANDFLASH,
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)//等于0x30000f80
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
bl nand_init_ll
??初始化NANDFLASH,其中包括設定時序NFCONF,(參考芯片手冊和2440手冊設定nandflsh的啟動時序),TACLS表示的建立所用的時間,TWRPH0表示nWE寫控制信號的持續時間,TWRPH1表示資料生效所用的時間,什么時候可以讀資料, 最后就是使能NFCONT NAND Flash控制器,初始化ECC, 禁止片選,到這里,NANDFLASH的初始化就完成了,下面就可以進行重定位了,
void nand_init_ll(void)
{
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
/* 設定時序 */
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
/* 使能NAND Flash控制器, 初始化ECC, 禁止片選 */
NFCONT = (1<<4)|(1<<1)|(1<<0);
}
復制 Bootloader的第二階段代碼到SDRAM空間中(重定位)
??首先判斷是NOR啟動還是NAND啟動,如果是NAND啟動就直接拷貝資料,拷貝代碼之前,要傳遞給拷貝函式三個引數,源,目的,長度,讀取NAND的話要參考芯片手冊的NAND讀取資料的時序,選中NAND,發出讀命令,發出地址,發出讀命令,判斷狀態,讀取資料,取消選中等,
bl copy_code_to_sdram
bl clear_bss //清除bss段(參考自制uboot章節)
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段不占用空間,都是未初始化的全域變數或者已經初始化為零的變數,本來就是零,直接清零就好,不清零的話未初始化的變數可能會存在未知的數值,
設定好堆疊
??設定堆疊跳轉到SDRAM執行,
ldr pc,=call_board_init_f //絕對跳轉,跳到SDRAM上執行
跳轉到第二階段代碼的C入口點
??跳轉到SDRAM執行剩下的程式,
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
/*重新設定堆疊到之前的位置 指向原來addr_sp += 128;*/
ldr sp,base_sp
/*呼叫第二階段代碼*/
bl board_init_r
Bootloader第二階段的功能
初始化本階段要使用到的硬體設備
??為了方便開發,至少要初始化一個串口以便程式員與 Bootloader進行互動,
檢測系統記憶體映射( memory map)
??所謂檢測記憶體映射,就是確定板上使用了多少記憶體、它們的地址空間是什么,由于嵌入式開發中 Bootloader多是針對某類板子進行撰寫,所以可以根據板子的情況直接設定,不需要考慮可以適用于各類情況的復雜演算法,
將內核映象和根檔案系統映象從 Flash上讀到SDRAM空間中
??Flash上的內核映象有可能是經過壓縮的,在讀到SDRAM之后,還需要進行解壓,當然,對于有自解壓功能的內核,不需要 Bootloader來解壓,將根檔案系統映象復制到SDRAM中,這不是必需的,這取決于是什么型別的根檔案系統以及內核訪問它的方法,
??將內核存放在適當的位置后,直接跳到它的入口點即可呼叫內核,呼叫內核之前,下列條件要滿足:
??(1)CPU暫存器的設定
??R0=0(規定),
??R1=機器型別ID;對于ARM結構的CPU,其機器型別ID可以參見 linux/arch/arm tools/ mach-types
??R2=啟動引數標記串列在RAM中起始基地址(下面會詳細介紹如何傳遞引數),
??(2)CPU作業模式
??必須禁止中斷(IRQ和FIQ,uboot啟動是一個完整的程序,沒有必要也不能被打斷)
??CPU必須為SVC模式(為什么呢?主要是像例外模式、用戶模式都不合適,具體深入的原因自己可以查下資料),
??(3) Cache和MMU的設定
??MMU必須關閉,
??指令 Cache可以打開也可以關閉,
??資料 Cache必須關閉,
為內核設定啟動引數
??Bootloader與內核的互動是單向的, Bootloader將各類引數傳給內核,由于它們不能同時行,傳遞辦法只有一個:Bootloader將引數放在某個約定的地方之后,再啟動內核,內核啟動后從這個地方獲得引數,
??除了約定好引數存放的地址外,還要規定引數的結構,Linu2.4x以后的內核都期望以標記串列( tagged_list)的形式來傳遞啟動引數,標記,就是一種資料結構;標記串列,就是挨著存放的多個標記,標記串列以標記 ATAG CORE開始,以標記 ATAG NONE結束,
??標記的資料結構為tag,它由一個 tag_header結構和一個聯合(union)組成, tag_ header結構表小標記的型別及長度,比如是表示記憶體還是表示命令列引數等,對于不同型別的標記使用不同的聯合(union),比如表示記憶體時使用 tag_mem32,表示命令列時使用 tag_cmdline,
??bootloader與內核約定的引數地址,設定記憶體的起始地址和大小,指定根檔案系統在那個磁區,系統啟動后執行的第一個程式linuxrc,控制臺ttySAC0等,
呼叫內核
??呼叫內核就是uboot啟動的最后一步了,到這里就uboot就完成了他的使命,
uboot啟動內核詳解
??下面我們來展開說下uboot具體是如何呼叫內核的,引導內核啟動的,
uboot與Linux內核之間的引數傳遞
??我們知道,uboot啟動后已經完成了基本的硬體初始化(如:記憶體、串口等),接下來它的主要任務就是加載Linux內核到開發板的記憶體,然后跳轉到Linux內核所在的地址運行,
??具體是如何跳轉呢?做法很簡單,直接修改PC暫存器的值為Linux內核所在的地址,這樣CPU就會從Linux內核所在的地址去取指令,從而執行內核代碼,
??在前面我們已經知道,在跳轉到內核以前,uboot需要做好以下三件事情:
??(1) CPU暫存器的設定
??R0=0,
??R1=機器型別ID;對于ARM結構的CPU,其機器型別ID可以參見 linux/arch/arm tools/ mach-types
??R2=啟動引數標記串列在RAM中起始基地址,
??(2) CPU作業模式
??必須禁止中斷(IRQs和FIQs)
??CPU必須為SVC模式
??(3) Cache和MMU的設定
??MMU必須關閉
??指令 Cache可以打開也可以關閉
??資料 Cache必須關閉
??其中上面第一步CPU暫存器的設定中,就是通過R0,R1,R2三個引數給內核傳遞引數的,(ATPCS規則可以參考)
為什么要給內核傳遞引數呢?
??在此之前,uboot已經完成了硬體的初始化,可以說已經”適應了“這塊開發板,然而,內核并不是對于所有的開發板都能完美適配的(如果適配了,可想而知這個內核有多龐大,又或者有新技術發明了,可以完美的適配各種開發板),此時對于開發板的環境一無所知,所以,要想啟動Linux內核,uboot必須要給內核傳遞一些必要的資訊來告訴內核當前所處的環境,
如何給內核傳遞引數?
??因此,uboot就把機器ID通過R1傳遞給內核,Linux內核運行的時候首先就從R1中讀取機器ID來判斷是否支持當前機器,這個機器ID實際上就是開發板CPU的ID,每個廠家生產出一款CPU的時候都會給它指定一個唯一的ID,大家可以到uboot原始碼的arch\arm\include\asm\mach-type.h檔案中去查看,

??R2存放的是塊記憶體的基地址,這塊記憶體中存放的是uboot給Linux內核的其他引數,這些引數有記憶體的起始地址、記憶體大小、Linux內核啟動后掛載檔案系統的方式等資訊,很明顯,引數有多個,不同的引數有不同的內容,為了讓Linux內核能精確的決議出這些引數,雙方在傳遞引數的時候要求引數在存放的時猴需要按照雙方規定的格式存放,
??除了約定好引數存放的地址外,還要規定引數的結構,Linux2.4.x以后的內核都期望以標記串列(tagged_list)的形式來傳遞啟動引數,標記,就是一種資料結構;標記串列,就是挨著存放的多個標記,標記串列以標記ATAG_CORE開始,以標記ATAG_NONE結束,
??標記的資料結構為tag,它由一個tag_header結構和一個聯合(union)組成,tag_header結構表示標記的型別及長度,比如是表示記憶體還是表示命令列引數等,對于不同型別的標記使用不同的聯合(union),比如表示記憶體時使用tag_ mem32,表示命令列時使用 tag_cmdline,具體代碼見arch\arm\include\asm\setup.h,

??從上面可以看出,struct_tag結構體由structtag_header+聯合體union構成,結構體struct tag_header用來描述每個tag的頭部資訊,如tag的型別,tag大小,聯合體union用來描述每個傳遞給Linux內核的引數資訊,
??下面以傳遞記憶體標記、傳遞命令列引數為例來說明引數的傳遞,
??(1)設定開始標記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);
??涉及到的結構體定義如下
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定義如下,指向當前標記的結尾
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
??(2)設定記憶體標記
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);
??相關結構體定義如下
#define ATAG_MEM 0x54410002
struct tag_mem32 {
__u32 size;
__u32 start; /* physical start address */
};
??(3)設定命令列引數標記
??命令列引數是一個字串,一般用它來告訴內核掛載根檔案系統的方式,由uboot的bootargs環境變數提供,它的內容有如下兩種格式
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內核掛載根檔案系統的方式,nfs表示以NFS服務的方式掛載根檔案系統,/dev/mtdblock2表示根檔案系統在MTD設定的第二個磁區上, |
| nfsroot | 告訴Linux內核,以NFS方式掛載根檔案系統時,根檔案系統所在主機的P地址和路徑 |
| ip | 告訴Linux內核,啟動后它的p地址 |
| init | 告訴Linux內核,啟動的第一個應用程式是根目錄下的linuxrc程式 |
| console | 告訴Linux區內核,控制臺為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);
??相關結構體定義如下
/* command line: \0 terminated string */
#define ATAG_CMDLINE 0x54410003
struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
??(4)設定結束標記
tag->hdr.tag = ATAG_NONE;
tag->hdr.size = 0;
??我們明白了運行Linux區內核的時候,uboot需要給內核的傳遞的引數,接下來我們就來看看如何從uboot中跳到Linux內核,
uboot跳轉到Linux內核
??在uboot中可以使用go和bootm來跳轉到內核,這兩個命令的區別如下:
??(1) go命令僅僅修改pc的值到指定地址
??格式:go addr
??(2) bootm命令是uboot專門用來啟動uImage格式的Linux內核,它在修改pc的值到指定地址之前,會設定傳遞給Linux內核的引數,用法如下:
??格式:bootm addr
uboot中bootm命令實作
??bootm命令在uboot原始碼common/cmd_bootm.c中實作,它的功能如下:
??(1)讀取uImage頭部,把內核拷貝到合適的地方,
??(2)把引數給內核準備好,
??(3)引導內核,
??當我們使用我們在uboot使用bootm命令后,bootm命令會從uImage頭中讀取資訊后,發現是Linux內核,就會呼叫do_bootm_linux()函式,函式的具體實作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 函式最侄訓 跳轉執行 boot_prep_linux 和 boot_jump_linux 函式,首先分析 boot_prep_linux 函式(位于 bootm.c 檔案中):
static void boot_prep_linux(bootm_headers_t *images)
{
char *commandline = getenv("bootargs"); //從環境變數中獲取 bootargs 的值
,,,,,,,
setup_board_tags(¶ms);
setup_end_tag(gd->bd); //將 tag 引數保存在指定位置
} else {
printf("FDT and ATAGS support not compiled in - hanging\n");
hang();
}
do_nonsec_virt_switch();
}
?? 從代碼可以看出來,boot_prep_linux,主要功能是將 tag 引數保存到指定位置,比如 bootargs 環境變數 tag,串口 tag,接下來分析 boot_jump_linux 函式(位于 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 中設定,為 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"); //從環境變數里獲取機器id (本例中還未在環境變數里設定過機器 id)
if (s) { //判斷環境變數里是否設定機器id
strict_strtoul(s, 16, &machid); //如果設定則用環境變數里的機器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引數地址,gd->bd->bi_boot_params在 setup_start_tag 函式里設定
if (!fake) kernel_entry(0, machid, r2); } //進入內核
??通過分析可以看出,最終進入內核的函式為 :
kernel_entry(0, machid, r2)
??到這里bootm就成功給內核傳遞了引數,并跳轉到了內核,關于go命令的實作可以自己參考內核,在cmd_boot.c檔案中,所不同的是,go命令實作的時候沒有設定引數,只是簡單的跳轉執行,如果想要使用go來跳轉到Linux內核,我們需要做簡單的修改,有興趣的可以自己研究下,這里就不展開講了,
??至此,uboot就啟動了內核,啟動內核后就是掛載根檔案系統了,下篇將具體介紹是如何掛載根檔案系統的,
??構建根檔案系統
內核鏡像格式vmlinuz和zImage和uImage
??最后插講下內核的不同映像格式的區別:
??(1)uboot經過編譯直接生成的elf格式的可執行程式是u-boot,這個程式類似于windows下的exe格式,在作業系統下是可以直接執行的,但是這種格式不能用來燒錄下載,我們用來燒錄下載的是u-boot.bin,這個東西是由u-boot使用arm-linux-objcopy工具進行加工(主要目的是去掉一些無用的)得到的,這個u-boot.bin就叫鏡像(image),鏡像就是用來燒錄到iNand中執行的,
??(2)linux內核經過編譯后也會生成一個elf格式的可執行程式,叫vmlinux或vmlinuz,這個就是原始的未經任何處理加工的原版內核elf檔案;嵌入式系統部署時燒錄的一般不是這個vmlinuz/vmlinux,而是要用objcopy工具去制作成燒錄鏡像格式(就是u-boot.bin這種,但是內核沒有.bin后綴),經過制作加工成燒錄鏡像的檔案就叫Image(制作把78M大的精簡成了7.5M,因此這個制作燒錄鏡像主要目的就是縮減大小,節省磁盤),
??(3)原則上Image就可以直接被燒錄到Flash上進行啟動執行(類似于u-boot.bin),但是實際上并不是這么簡單,實際上linux的作者們覺得Image還是太大了所以對Image進行了壓縮,并且在image壓縮后的檔案的前端附加了一部分解壓縮代碼,構成了一個壓縮格式的鏡像就叫zImage,(因為當年Image大小剛好比一張軟盤(軟盤有2種,1.2M的和1.44MB兩種)大,為了節省1張軟盤的錢于是乎設計了這種壓縮Image成zImage的技術),
??(4)uboot為了啟動linux內核,還發明了一種內核格式叫uImage,uImage是由zImage加工得到的,uboot中有一個工具,可以將zImage加工生成uImage,注意:uImage不關linux內核的事,linux內核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage來給uboot啟動,這個加工程序其實就是在zImage前面加上64位元組的uImage的頭資訊即可,
??(5)原則上uboot啟動時應該給他uImage格式的內核鏡像,但是實際上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工具到系統目錄下,再去make uImage即可,
??通過上面的介紹我們了解了內核鏡像的各種格式,如果通過uboot啟動內核,Linux必須為uImage格式,
??大家的鼓勵是我繼續創作的動力,如果覺得寫的不錯,歡迎關注,點贊,收藏,轉發,謝謝!

本文參考:<嵌入式Linux應用開發完全手冊>
歡迎歡迎關注我的公眾號:嵌入式與Linux那些事,領取秋招筆試面試大禮包(華為小米等大廠面經,嵌入式知識點總結,筆試題目,簡歷模版等)和2000G學習資料,公眾號主要分享Linux驅動開發,資料結構與演算法,計算機基礎,C/C++等相關知識,有任何問題均可以加我微信,歡迎騷擾!
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/236314.html
標籤:其他
上一篇:詳解匯編語言B和LDR指令與相對跳轉和絕對跳轉的關系
下一篇:使用 tcpdump 工具抓包
