1. 程式人生 > >UBOOT——啟動核心

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 (&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函式來對核心傳參並且啟動核心;