@
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++等相關知識,有任何問題均可以加我微信,歡迎騷擾!