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 (&params); #endif #ifdef CONFIG_REVISION_TAG setup_revision_tag (&params); #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函數來對內核傳參並且啟動內核;

 

 

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM