UBOOT——啟動核心
https://www.cnblogs.com/biaohc/p/6403863.html
UBOOT——啟動核心
1:什麼是UBOOT,為什麼要有UBOOT?
UBOOT的主要作用是用來啟動linux核心,因為CPU不能直接從塊裝置中執行程式碼,需要把塊裝置中的程式複製到記憶體中,而複製之前還需要進行很多初始化工作,如時鐘、串列埠、dram等;
如要想讓CPU啟動linux核心,只能通過另外的程式,進行必要的初始化工作,在把linux核心中程式碼複製到記憶體中,並執行這塊記憶體中的程式碼,即可啟動linux核心;一般情況下,我們把linux
映象儲存在塊裝置中如SD卡、iNand、Nandflash等塊裝置中,首先執行UBOOT帶碼,在UBOOT中把塊裝置中的核心程式碼複製到記憶體地址0x30008000地址處,然後在執行bootm 0x30008000
命令來執行核心程式碼;
整個過程大致如上述所講,下面我們詳細分析一下UBOOT啟動核心的程式碼:
2:在啟動UBOOT時候會出現看機倒計時,如果沒有按鍵按下,會自動啟動核心,我們來看一下這個是如何實現的:
下面這段程式碼是在main_loop函式中:作用是執行完倒數計時函式以後啟動linux核心,啟動方式是 s = getenv ("bootcmd");我們假定不使用HUAH_PARSER的情況下 run_command (s, 0);
實際上就是讀取環境變數bootcmd,然後執行這個命令;
s = getenv ("bootcmd"); debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>"); if (bootdelay >= 0 && s && !abortboot (bootdelay)) { #ifndef CFG_HUSH_PARSER run_command (s, 0); #else parse_string_outer(s, FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP);
看一下bootcmd命令:bootcmd=movi read kernel 30008000; movi read rootfs 30B00000 300000; bootm 30008000 30B00000
movi read kernel 30008000 以及 bootm 30008000
這兩個命令來完成linux核心啟動的:
movi read kernel 30008000是把sd卡中kernel分割槽複製到30008000記憶體地址處,bootm 30008000即到記憶體地址處執行程式碼;
下面詳細分一下bootm這個命令對應的函式
程式碼一步步分析:
下面這段程式碼的作用是判斷核心映象是zImage、uImage、裝置樹
在這裡要解釋一下zImage、uImage的區別:
linux核心程式碼經過編譯連結以後生成一個elf檔名叫vmlinuz檔案,這個檔案在經過arm-linux-objcopy編譯以後會生成一個Image映象檔案,vmlinuz elf檔案大小為70M以上
而Image映象檔案為7M左右,然後Image檔案在進一步經過壓縮生成zImage檔案,當zImage檔案作為啟動映象來啟動時,首先要解壓這個檔案,這個解壓過程可以由uboot解壓
或者zImage檔案本身可以自解壓,zImage中除了linux核心的映象以外,還有一些標頭檔案以及這部分解壓程式碼,所以核心實際上在addr地址中在加一個偏移量的位置;
uImage是uboot自己專用的啟動核心映象,相對於zImage他們之間標頭檔案有一定區別可以詳細看程式碼是如何判斷的;uImage現在基本上要屬於過時的技術了,新一點的技術為
裝置樹的啟動方式;
我們時這麼使用bootm命令的:bootm 0x30008000
走的是addr = simple_strtoul(argv[1], NULL, 16);
addr中的值為0x30008000
接下來判斷0x30008000右偏移36位元組以後,這個地址中的值如果為 0x016f2818這個魔數的話,說明啟動映象為zImage則 輸出boot with zImage,
hdr->ih_os = IH_OS_LINUX; zImage header中 IH_os 賦值為 IH_OS_LINUX;
hdr->ih_ep = ntohl(addr); ih_ep 中存放的是point address 這個值實際上就是真正核心程式碼的地址;
在看下面這句程式碼
memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
把hdr中的值複製一份到 image.legacy_hdr_os_copy中,即把記憶體地址0x30008000處設定好的zImage頭複製一份到uboot的data段,
因為static bootm_headers_t images; images為uboot內定義的一個bootm_header_t格式的全域性變數;
看一下bootm_header_t型別為一個結構體,包含一個image_header_t型別的指標,這個指標最後指向了0x30008000處的zImage header
還包含一個image_header_t型別的結構體,就是用上面那句程式碼把0x30008000處的zImage header在酯類複製了一份;
還包含一個標誌位 legacy_hdr_valid如果上面兩個賦值以後,把legacy_hdr_valid賦值為1;
typedef struct bootm_headers { image_header_t *legacy_hdr_os; /* image header pointer */ image_header_t legacy_hdr_os_copy; /* header copy */ ulong legacy_hdr_valid; }
uint8_t ih_os; /* Operating System */
typedef struct image_header { uint32_t ih_magic; /* Image Header Magic Number */ uint32_t ih_hcrc; /* Image Header CRC Checksum */ uint32_t ih_time; /* Image Creation Timestamp */ uint32_t ih_size; /* Image Data Size */ uint32_t ih_load; /* Data Load Address */ uint32_t ih_ep; /* Entry Point Address */ uint32_t ih_dcrc; /* Image Data CRC Checksum */ uint8_t ih_os; /* Operating System */ uint8_t ih_arch; /* CPU architecture */ uint8_t ih_type; /* Image Type */ uint8_t ih_comp; /* Compression Type */ uint8_t ih_name[IH_NMLEN]; /* Image Name */ } image_header_t;
#ifdef CONFIG_ZIMAGE_BOOT #define LINUX_ZIMAGE_MAGIC 0x016f2818 /* find out kernel image address */ if (argc < 2) { addr = load_addr; debug ("* kernel: default image load address = 0x%08lx\n", load_addr); } else { addr = simple_strtoul(argv[1], NULL, 16); debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr); } if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) { printf("Boot with zImage\n"); addr = virt_to_phys(addr); hdr = (image_header_t *)addr; hdr->ih_os = IH_OS_LINUX; hdr->ih_ep = ntohl(addr); memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t)); /* save pointer to image header */ images.legacy_hdr_os = hdr; images.legacy_hdr_valid = 1; goto after_header_check; } #endif
直接跳轉到after_header_check處,os為IH_OS_LINUX
下面判斷作業系統,然後呼叫do_bootm_linux函式;
do_bootm_linux (cmdtp, flag, argc, argv, &images);
1 #if defined(CONFIG_ZIMAGE_BOOT) 2 after_header_check: 3 os = hdr->ih_os; 4 #endif 5 6 switch (os) { 7 default: /* handled by (original) Linux case */ 8 case IH_OS_LINUX: 9 #ifdef CONFIG_SILENT_CONSOLE 10 fixup_silent_linux(); 11 #endif 12 do_bootm_linux (cmdtp, flag, argc, argv, &images); 13 break; 14 15 case IH_OS_NETBSD: 16 do_bootm_netbsd (cmdtp, flag, argc, argv, &images); 17 break; 18 19 #ifdef CONFIG_LYNXKDI 20 case IH_OS_LYNXOS: 21 do_bootm_lynxkdi (cmdtp, flag, argc, argv, &images); 22 break; 23 #endif 24 25 case IH_OS_RTEMS: 26 do_bootm_rtems (cmdtp, flag, argc, argv, &images); 27 break; 28 29 #if defined(CONFIG_CMD_ELF) 30 case IH_OS_VXWORKS: 31 do_bootm_vxworks (cmdtp, flag, argc, argv, &images); 32 break; 33 34 case IH_OS_QNX: 35 do_bootm_qnxelf (cmdtp, flag, argc, argv, &images); 36 break; 37 #endif 38 39 #ifdef CONFIG_ARTOS 40 case IH_OS_ARTOS: 41 do_bootm_artos (cmdtp, flag, argc, argv, &images); 42 break; 43 #endif 44 } 45 46 show_boot_progress (-9); 47 #ifdef DEBUG 48 puts ("\n## Control returned to monitor - resetting...\n"); 49 do_reset (cmdtp, flag, argc, argv); 50 #endif 51 if (iflag) 52 enable_interrupts(); 53 54 return 1; 55 }
下面看一下do_bootm_linux都做了哪些事情
#ifdef CONFIG_CMDLINE_TAG char *commandline = getenv ("bootargs"); #endif
首先獲取環境變數bootargs:
if (images->legacy_hdr_valid) { ep = image_get_ep (&images->legacy_hdr_os_copy)
else {
puts ("Could not find kernel entry point!\n");
goto error;
}
在判斷全域性變數images中的legacy_hdr_valid是否為1,如果為1 獲取ep 值;如果為1讀出ep的值,如果不為1則erro
theKernel = (void (*)(int, int, uint))ep; s = getenv ("machid"); if (s) { machid = simple_strtoul (s, NULL, 16); printf ("Using machid 0x%x from environment\n", machid); }
把ep強制型別換換為函式指標型別複製給thekernel;
從環境變數中讀取machid的值,賦值給s,如果s不空 則machid = 環境變數中machid的值,並列印machid;
在看一下uboot如何給核心傳參:
傳參主要是uboot把與硬體有關的資訊傳給linux核心,如memory資訊幾bank size 起始地址、命令列資訊、lcd 串列埠、initrd、MTD等資訊
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \ defined (CONFIG_CMDLINE_TAG) || \ defined (CONFIG_INITRD_TAG) || \ defined (CONFIG_SERIAL_TAG) || \ defined (CONFIG_REVISION_TAG) || \ defined (CONFIG_LCD) || \ defined (CONFIG_VFD) || \ defined (CONFIG_MTDPARTITION) setup_start_tag (bd); #ifdef CONFIG_SERIAL_TAG setup_serial_tag (¶ms); #endif #ifdef CONFIG_REVISION_TAG setup_revision_tag (¶ms); #endif #ifdef CONFIG_SETUP_MEMORY_TAGS setup_memory_tags (bd); #endif #ifdef CONFIG_CMDLINE_TAG setup_commandline_tag (bd, commandline); #endif #ifdef CONFIG_INITRD_TAG if (initrd_start && initrd_end) setup_initrd_tag (bd, initrd_start, initrd_end); #endif #if defined (CONFIG_VFD) || defined (CONFIG_LCD) setup_videolfb_tag ((gd_t *) gd); #endif #ifdef CONFIG_MTDPARTITION setup_mtdpartition_tag(); #endif setup_end_tag (bd); #endif /* we assume that the kernel is in place */ printf ("\nStarting kernel ...\n\n"); #ifdef CONFIG_USB_DEVICE { extern void udc_disconnect (void); udc_disconnect (); } #endif cleanup_before_linux (); theKernel (0, machid, bd->bi_boot_params); /* does not return */
首先:如要定義了任意一個CONFIG_XXXXX的話
struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; /* * Acorn specific */ struct tag_acorn acorn; /* * DC21285 specific */ struct tag_memclk memclk; struct tag_mtdpart mtdpart_info; } u; };
static void setup_start_tag (bd_t *bd) { params = (struct tag *) bd->bi_boot_params; params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size (tag_core); params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; params = tag_next (params); }
struct tag_header { u32 size; u32 tag; };
首先要setup_start_tag(bd); 這個函式的作用
params = (struct tag *) bd->bi_boot_params; 給params賦值,gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
這句程式碼的作用就是把uboot全域性變數中設定好的bi_boot_params記憶體地址處強制轉換為stuct tag* 型別賦值給params
分析一下struct tag結構體:它是由一個stuct tag_header型別的結構體加上一個由一系列結構體組成的union聯合體組成;
這一系列結構體中存放的就是與board有關的引數;
把PHYS_SDRAM_1+0x100這個地址設定為傳參的起始地址;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
hdr.tag 與hdr.size賦值;
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
然後對聯合體中的結構體引數賦值;
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
params = tag_next (params);
在把params向右移動sizeof(tag_core)大小
繼續賦值:
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
這段程式碼是傳遞記憶體引數:
把記憶體每個bank的資訊放到這裡:第一個扇區的起始地址和大小,第二個扇區的起始地址和大小
1 #ifdef CONFIG_SETUP_MEMORY_TAGS 2 static void setup_memory_tags (bd_t *bd) 3 { 4 int i; 5 6 for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { 7 params->hdr.tag = ATAG_MEM; 8 params->hdr.size = tag_size (tag_mem32); 9 10 params->u.mem.start = bd->bi_dram[i].start; 11 params->u.mem.size = bd->bi_dram[i].size; 12 13 params = tag_next (params); 14 } 15 } 16 #endif /* CONFIG_SETUP_MEMORY_TAGS */
接著是傳遞命令列引數
1 static void setup_commandline_tag (bd_t *bd, char *commandline) 2 { 3 char *p; 4 5 if (!commandline) 6 return; 7 8 /* eat leading white space */ 9 for (p = commandline; *p == ' '; p++); 10 11 /* skip non-existent command lines so the kernel will still 12 * use its default command line. 13 */ 14 if (*p == '\0') 15 return; 16 17 params->hdr.tag = ATAG_CMDLINE; 18 params->hdr.size = 19 (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2; 20 21 strcpy (params->u.cmdline.cmdline, p); 22 23 params = tag_next (params); }
前面我們分析了commandline是一個char *型別,指向環境變數中的bootargs的值;
#define CONFIG_BOOTARGS "root=/dev/mtdblock4 rootfstype=yaffs2 init=/init console=ttySAC0,115200"
最後setup_end_tag (bd);結束傳參
再看最後uboot中最後一句程式碼
theKernel (0, machid, bd->bi_boot_params); /* does not return */ return;
通過執行thekernel函式直接啟動linux核心,傳遞三個引數0、machid、傳參的首地址;
這三個引數是通過r0、r1、r2三個暫存器來傳遞了,r0傳遞0、r1傳遞machid、r2傳遞傳參的首地址;
這樣就啟動起來linux核心了;
-----------------------------------------------------------------------------------------------------------------------------------
下面我們再來總結一下uboot啟動linux核心的整個流程:
開機時會出現倒計時,當沒有按鍵按下的時候,uboot會讀取出bootcmd這個環境變數,並使用rum_command函式來執行這個命令;
實質是執行了movi read kernel 30008000;以後在執行bootm 30008000
moviread kernel的作用是把sd卡中的kernel分割槽賦值到30008000記憶體處
bootm 30008000就是真正的傳參以及跳轉到linux核心中執行;
bootm 首先要做的事情是判斷這個核心映象為zImage、uImage、裝置樹
通過對映象檔案的標頭檔案的驗證,確定是哪種核心映象,然後再把必須的資訊儲存起來如是linux作業系統,ep的值等;
確定好以後呼叫do_bootm_linux函式來對核心傳參並且啟動核心;