1. 程式人生 > >uboot向kernel的傳參機制——bootm與tags

uboot向kernel的傳參機制——bootm與tags

版權宣告:本文為博主kerneler辛苦原創,未經允許不得轉載。 https://blog.csdn.net/skyflying2012/article/details/35787971

最近閱讀程式碼學習了uboot boot kernel的過程以及uboot如何傳參給kernel,記錄下來,與大家共享:

U-boot版本:2014.4

Kernel版本:3.4.55

一 uboot 如何啟動 kernel

1 do_bootm

uboot下使用bootm命令啟動核心映象檔案uImage,uImage是在zImage頭添加了64位元組的映象資訊供uboot解析使用,具體這64位元組頭的內容,我們在分析bootm命令的時候就會一一說到,那直接來看bootm命令。

在common/cmd_bootm.c中

    int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])     {     #ifdef CONFIG_NEEDS_MANUAL_RELOC         static int relocated = 0;               if (!relocated) {             int i;                   /* relocate boot function table */             for (i = 0; i < ARRAY_SIZE(boot_os); i++)                 if (boot_os[i] != NULL)                     boot_os[i] += gd->reloc_off;                   /* relocate names of sub-command table */             for (i = 0; i < ARRAY_SIZE(cmd_bootm_sub); i++)                 cmd_bootm_sub[i].name += gd->reloc_off;                   relocated = 1;         }     #endif         /* determine if we have a sub command */         argc--; argv++;         if (argc > 0) {             char *endp;                   simple_strtoul(argv[0], &endp, 16);             /* endp pointing to NULL means that argv[0] was just a              * valid number, pass it along to the normal bootm processing              *              * If endp is ':' or '#' assume a FIT identifier so pass              * along for normal processing.              *              * Right now we assume the first arg should never be '-'              */             if ((*endp != 0) && (*endp != ':') && (*endp != '#'))                 return do_bootm_subcommand(cmdtp, flag, argc, argv);         }               return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |             BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |             BOOTM_STATE_LOADOS |     #if defined(CONFIG_PPC) || defined(CONFIG_MIPS)             BOOTM_STATE_OS_CMDLINE |     #endif             BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |             BOOTM_STATE_OS_GO, &images, 1);     }

陣列boot_os是bootm最後階段啟動kernel時呼叫的函式陣列,CONFIG_NEEDS_MANUAL_RELOC中的程式碼含義是將boot_os函式都進行偏移(uboot啟動中會將整個code拷貝到靠近sdram頂端的位置執行),

但是boot_os函式在uboot relocate時已經都拷貝了,所以感覺沒必要在進行relocate。這個巨集因此沒有定義,直接走下面。

新版uboot對於boot kernel實現了一個類似狀態機的機制,將整個過程分成很多個階段,uboot將每個階段稱為subcommand,

核心函式是do_bootm_states,需要執行哪個階段,就在do_bootm_states最後一個引數新增那個巨集定義,如: BOOTM_STATE_START

do_bootm_subcommand是按照bootm引數來指定執行某一個階段,也就是某一個subcommand

對於正常的uImage,bootm加tftp的load地址就可以。

2 do_bootm_states

這樣會走到最後函式do_bootm_states,那就來看看核心函式do_bootm_states

    static int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc,             char * const argv[], int states, bootm_headers_t *images,             int boot_progress)     {         boot_os_fn *boot_fn;         ulong iflag = 0;         int ret = 0, need_boot_fn;               images->state |= states;               /*          * Work through the states and see how far we get. We stop on          * any error.          */         if (states & BOOTM_STATE_START)             ret = bootm_start(cmdtp, flag, argc, argv);

引數中需要注意bootm_headers_t *images,這個引數用來儲存由image頭64位元組獲取到的的基本資訊。由do_bootm傳來的該引數是images,是一個全域性的靜態變數。

首先將states儲存在images的state中,因為states中有BOOTM_STATE_START,呼叫bootm_start.

3 第一階段:bootm_start

    static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])     {         memset((void *)&images, 0, sizeof(images));         images.verify = getenv_yesno("verify");               boot_start_lmb(&images);               bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");         images.state = BOOTM_STATE_START;               return 0;     }

獲取verify,bootstage_mark_name標誌當前狀態為bootm start(bootstage_mark_name可以用於無串列埠除錯,在其中實現LED控制)。

boot_start_lmb暫時還沒弄明白,以後再搞清楚。

最後修改images.state為bootm start。

bootm_start主要工作是清空images,標誌當前狀態為bootm start。

4 第二階段:bootm_find_os

由bootm_start返回後,do_bootm傳了BOOTM_STATE_FINDOS,所以進入函式bootm_find_os

    static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,                  char * const argv[])     {         const void *os_hdr;               /* get kernel image header, start address and length */         os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,                 &images, &images.os.image_start, &images.os.image_len);         if (images.os.image_len == 0) {             puts("ERROR: can't get kernel image!\n");             return 1;         }

呼叫boot_get_kernel,函式較長,首先是獲取image的load地址,如果bootm有引數,就是img_addr,之後如下:

        bootstage_mark(BOOTSTAGE_ID_CHECK_MAGIC);               /* copy from dataflash if needed */         img_addr = genimg_get_image(img_addr);               /* check image type, for FIT images get FIT kernel node */         *os_data = *os_len = 0;         buf = map_sysmem(img_addr, 0);

首先標誌當前狀態,然後呼叫genimg_get_image,該函式會檢查當前的img_addr是否在sdram中,如果是在flash中,則拷貝到sdram中CONFIG_SYS_LOAD_ADDR處,修改img_addr為該地址。

這裡說明我們的image可以在flash中用bootm直接起

map_sysmem為空函式,buf即為img_addr。

        switch (genimg_get_format(buf)) {         case IMAGE_FORMAT_LEGACY:             printf("## Booting kernel from Legacy Image at %08lx ...\n",                     img_addr);             hdr = image_get_kernel(img_addr, images->verify);             if (!hdr)                 return NULL;             bootstage_mark(BOOTSTAGE_ID_CHECK_IMAGETYPE);                   /* get os_data and os_len */             switch (image_get_type(hdr)) {             case IH_TYPE_KERNEL:             case IH_TYPE_KERNEL_NOLOAD:                 *os_data = image_get_data(hdr);                 *os_len = image_get_data_size(hdr);                 break;             case IH_TYPE_MULTI:                 image_multi_getimg(hdr, 0, os_data, os_len);                 break;

            case IH_TYPE_STANDALONE:                 *os_data = image_get_data(hdr);                 *os_len = image_get_data_size(hdr);                 break;             default:                 printf("Wrong Image Type for %s command\n",                     cmdtp->name);                 bootstage_error(BOOTSTAGE_ID_CHECK_IMAGETYPE);                 return NULL;             }                   /*              * copy image header to allow for image overwrites during              * kernel decompression.              */             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;             bootstage_mark(BOOTSTAGE_ID_DECOMP_IMAGE);             break;

首先來說明一下image header的格式,在程式碼中由image_header_t代表,如下:

    typedef struct image_header {         __be32      ih_magic;   /* Image Header Magic Number    */         __be32      ih_hcrc;    /* Image Header CRC Checksum    */         __be32      ih_time;    /* Image Creation Timestamp */         __be32      ih_size;    /* Image Data Size      */         __be32      ih_load;    /* Data  Load  Address      */         __be32      ih_ep;      /* Entry Point Address      */         __be32      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;

genimg_get_format檢查img header的頭4個位元組,代表image的型別,有2種,legacy和FIT,這裡使用的legacy,頭4個位元組為0x27051956。

image_get_kernel則會來計算header的crc是否正確,然後獲取image的type,根據type來獲取os的len和data起始地址。

最後將hdr的資料拷貝到images的legacy_hdr_os_copy,防止kernel image在解壓是覆蓋掉hdr資料,儲存hdr指標到legacy_hdr_os中,置位legacy_hdr_valid。

從boot_get_kernel中返回到bootm_find_os,繼續往下:

        switch (genimg_get_format(os_hdr)) {         case IMAGE_FORMAT_LEGACY:             images.os.type = image_get_type(os_hdr);             images.os.comp = image_get_comp(os_hdr);             images.os.os = image_get_os(os_hdr);                   images.os.end = image_get_image_end(os_hdr);             images.os.load = image_get_load(os_hdr);

根據hdr獲取os的type,comp,os,end,load addr。

        /* find kernel entry point */         if (images.legacy_hdr_valid) {             images.ep = image_get_ep(&images.legacy_hdr_os_copy);         } else {             puts("Could not find kernel entry point!\n");             return 1;         }               if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {             images.os.load = images.os.image_start;             images.ep += images.os.load;         }               images.os.start = (ulong)os_hdr;

獲取os的start。 到這裡bootm_find_os就結束了,主要工作是根據image的hdr來做crc,獲取一些基本的os資訊到images結構體中。

回到do_bootm_states中接下來呼叫bootm_find_other,

5 第三階段:bootm_find_other 該函式大體看一下,對於legacy型別的image,獲取查詢是否有ramdisk,此處我們沒有用單獨的ramdisk,ramdisk是直接編譯到kernel image中的。

回到do_bootm_states中接下來會呼叫bootm_load_os。

6 第四階段:bootm_load_os

    static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,             int boot_progress)     {         image_info_t os = images->os;         uint8_t comp = os.comp;         ulong load = os.load;         ulong blob_start = os.start;         ulong blob_end = os.end;         ulong image_start = os.image_start;         ulong image_len = os.image_len;         __maybe_unused uint unc_len = CONFIG_SYS_BOOTM_LEN;         int no_overlap = 0;         void *load_buf, *image_buf;     #if defined(CONFIG_LZMA) || defined(CONFIG_LZO)         int ret;     #endif /* defined(CONFIG_LZMA) || defined(CONFIG_LZO) */               const char *type_name = genimg_get_type_name(os.type);               load_buf = map_sysmem(load, unc_len);         image_buf = map_sysmem(image_start, image_len);         switch (comp) {         case IH_COMP_NONE:             if (load == blob_start || load == image_start) {                 printf("   XIP %s ... ", type_name);                 no_overlap = 1;             } else {                 printf("   Loading %s ... ", type_name);                 memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);             }             *load_end = load + image_len;             break;

    #ifdef CONFIG_GZIP         case IH_COMP_GZIP:             printf("   Uncompressing %s ... ", type_name);             if (gunzip(load_buf, unc_len, image_buf, &image_len) != 0) {                 puts("GUNZIP: uncompress, out-of-mem or overwrite "                     "error - must RESET board to recover\n");                 if (boot_progress)                     bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);                 return BOOTM_ERR_RESET;             }                   *load_end = load + image_len;             break;     #endif /* CONFIG_GZIP */

load_buf是之前find_os是根據hdr獲取的load addr,image_buf是find_os獲取的image的開始地址(去掉64位元組頭)。

之後則是根據hdr的comp型別來解壓拷貝image到load addr上。

這裡就需要注意,kernel選項的壓縮格式必須在uboot下開啟相應的解壓縮支援,或者就不進行壓縮

這裡還有一點,load addr與image add是否可以重疊,看程式碼感覺是可以重疊的,還需要實際測試一下。

回到do_bootm_states,接下來根據os從boot_os陣列中獲取到了相應的os boot func,這裡是linux,則是do_bootm_linux。後面程式碼如下:

        /* Call various other states that are not generally used */         if (!ret && (states & BOOTM_STATE_OS_CMDLINE))             ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);         if (!ret && (states & BOOTM_STATE_OS_BD_T))             ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);         if (!ret && (states & BOOTM_STATE_OS_PREP))             ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);     。。。。         /* Check for unsupported subcommand. */         if (ret) {             puts("subcommand not supported\n");             return ret;         }               /* Now run the OS! We hope this doesn't return */         if (!ret && (states & BOOTM_STATE_OS_GO))             ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,                     images, boot_fn);

這時do_bootm最後的程式碼,如果正常,boot kernel之後就不應該回來了。states中定義了BOOTM_STATE_OS_PREP(對於mips處理器會使用BOOTM_STATE_OS_CMDLINE),呼叫do_bootm_linux,如下:

    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 | BOOTM_STATE_OS_FAKE_GO)) {             boot_jump_linux(images, flag);             return 0;         }                  boot_prep_linux(images);         boot_jump_linux(images, flag);         return 0;     }

do_bootm_linux實現跟do_bootm類似,也是根據flag分階段執行subcommand,這裡會調到boot_prep_linux。

7 第五階段:boot_prep_linux

該函式作用是為啟動後的kernel準備引數,這個函式我們在第三部分uboot如何傳參給kernel再仔細分析一下

boot_prep_linux完成返回到do_bootm_states後接下來就是最後一步了。執行boot_selected_os呼叫do_bootm_linux,flag為BOOTM_STATE_OS_GO,則呼叫boot_jump_linux

8 第六階段:boot_jump_linux

        unsigned long machid = gd->bd->bi_arch_number;         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;               s = getenv("machid");         if (s) {             strict_strtoul(s, 16, &machid);             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;               if (!fake)             kernel_entry(0, machid, r2);

boot_jump_linux主體函式如上

獲取gd->bd->bi_arch_number為machid,如果有env則用env的machid,kernel_entry為之前由hdr獲取的ep,也就是核心的入口地址。

fake為0,直接呼叫kernel_entry,引數1為0,引數2為machid,引數3為bi_boot_params。

這之後就進入了kernel的執行流程啟動,就不會再回到uboot

這整個boot過程中bootm_images_t一直作為對image資訊的全域性儲存結構。

三 uboot如何傳參給kernel

uboot下的傳參機制就直接來分析boot_prep_linux函式就可以了,如下:

    static void boot_prep_linux(bootm_headers_t *images)     {         char *commandline = getenv("bootargs");               if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {     #ifdef CONFIG_OF_LIBFDT             debug("using: FDT\n");             if (image_setup_linux(images)) {                 printf("FDT creation failed! hanging...");                 hang();             }     #endif         } else if (BOOTM_ENABLE_TAGS) {             debug("using: ATAGS\n");             setup_start_tag(gd->bd);             if (BOOTM_ENABLE_SERIAL_TAG)                 setup_serial_tag(¶ms);             if (BOOTM_ENABLE_CMDLINE_TAG)                 setup_commandline_tag(gd->bd, commandline);             if (BOOTM_ENABLE_REVISION_TAG)                 setup_revision_tag(¶ms);             if (BOOTM_ENABLE_MEMORY_TAGS)                 setup_memory_tags(gd->bd);             if (BOOTM_ENABLE_INITRD_TAG) {                 if (images->rd_start && images->rd_end) {                     setup_initrd_tag(gd->bd, images->rd_start,                              images->rd_end);                 }             }             setup_board_tags(¶ms);             setup_end_tag(gd->bd);         } else {             printf("FDT and ATAGS support not compiled in - hanging\n");             hang();         }         do_nonsec_virt_switch();     }

首先獲取出環境變數bootargs,這就是要傳遞給kernel的引數。 在配置檔案中定義了CONFIG_CMDLINE_TAG以及CONFIG_SETUP_MEMORY_TAGS,根據arch/arm/include/asm/bootm.h,則會定義BOOTM_ENABLE_TAGS,首先呼叫setup_start_tag,如下:

    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);     }           

params是一個全域性靜態變數用來儲存要傳給kernel的引數,這裡bd->bi_boot_params的值賦給params,因此bi_boot_params需要進行初始化,從而將params放在一個合理的記憶體區域。 這裡params為struct tag的結構,如下:

    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;         } u;     };

tag包括hdr和各種型別的tag_*,hdr來標誌當前的tag是哪種型別的tag。 setup_start_tag是初始化了第一個tag,是tag_core型別的tag。最後呼叫tag_next跳到第一個tag末尾,為下一個tag做準備。

回到boot_prep_linux,接下來呼叫setup_commandline_tag,如下:

    static void setup_commandline_tag(bd_t *bd, char *commandline)     {                    char *p;                          if (!commandline)             return;                      /* eat leading white space */         for (p = commandline; *p == ' '; p++);                          /* skip non-existent command lines so the kernel will still          * use its default command line.          */              if (*p == '\0')             return;                      params->hdr.tag = ATAG_CMDLINE;         params->hdr.size =             (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;               strcpy (params->u.cmdline.cmdline, p);               params = tag_next (params);     }

該函式設定第二個tag的hdr.tag為ATAG_CMDLINE,然後拷貝cmdline到tags的cmdline結構體中,跳到下一個tag。

回到boot_prep_linux,呼叫setup_memory_tag,如下:

    static void setup_memory_tags(bd_t *bd)     {                int i;                        for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {             params->hdr.tag = ATAG_MEM;             params->hdr.size = tag_size (tag_mem32);                                  params->u.mem.start = bd->bi_dram[i].start;             params->u.mem.size = bd->bi_dram[i].size;                          params = tag_next (params);         }        }   

過程類似,將第三個tag設為ATAG_MEM,將mem的start,size儲存在此處,如果有多片ram(CONFIG_NR_DRAM_BANKS > 1),則將下一個tag儲存下一片ram的資訊,依次類推。

回到boot_prep_linux中,呼叫setup_board_tags,這個函式是__weak屬性,我們可以在自己的板級檔案中去實現來儲存跟板子相關的引數,如果沒有實現,則是空函式。

最後呼叫setup_end_tags,如下:

    static void setup_end_tag(bd_t *bd)     {                params->hdr.tag = ATAG_NONE;         params->hdr.size = 0;     }       

最後將最末尾的tag設定為ATAG_NONE,標誌tag結束。

這樣整個引數的準備就結束了,最後在呼叫boot_jump_linux時會將tags的首地址也就是bi_boot_params傳給kernel,供kernel來解析這些tag,kernel如何解析看第四部分kenrel如何找到並解析引數

總結一下,uboot將引數以tag陣列的形式佈局在記憶體的某一個地址,每個tag代表一種型別的引數,首尾tag標誌開始和結束,首地址傳給kernel供其解析。

四 kernel如何找到並解析引數

uboot在呼叫boot_jump_linux時最後kernel_entry(0, machid, r2);

按照二進位制規範eabi,machid存在暫存器r1,r2即tag的首地址存在暫存器r2.

檢視kernel的入口函式,在arch/arm/kernel/head.S,中可以看到如下一段彙編:

        /*            * r1 = machine no, r2 = atags or dtb,          * r8 = phys_offset, r9 = cpuid, r10 = procinfo          */         bl  __vet_atags

可以看出kernel剛啟動會呼叫__vet_atags來處理uboot傳來的引數,如下:

    __vet_atags:         tst r2, #0x3            @ aligned?         bne 1f               ldr r5, [r2, #0]     #ifdef CONFIG_OF_FLATTREE         ldr r6, =OF_DT_MAGIC        @ is it a DTB?         cmp r5, r6         beq 2f     #endif         cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?         cmpne   r5, #ATAG_CORE_SIZE_EMPTY         bne 1f         ldr r5, [r2, #4]         ldr r6, =ATAG_CORE         cmp r5, r6         bne 1f           2:  mov pc, lr              @ atag/dtb pointer is ok           1:  mov r2, #0         mov pc, lr     ENDPROC(__vet_atags)

主要是對tag進行了一個簡單的校驗,檢視tag頭4個位元組(tag_core的size)和第二個4位元組(tag_core的type)。

之後對引數的真正分析處理是在start_kernel的setup_arch中,在arch/arm/kernel/setup.c中,如下:

    void __init setup_arch(char **cmdline_p)     {         struct machine_desc *mdesc;               setup_processor();         mdesc = setup_machine_fdt(__atags_pointer);         if (!mdesc)             mdesc = setup_machine_tags(machine_arch_type);         machine_desc = mdesc;         machine_name = mdesc->name;           #ifdef CONFIG_ZONE_DMA         if (mdesc->dma_zone_size) {             extern unsigned long arm_dma_zone_size;             arm_dma_zone_size = mdesc->dma_zone_size;         }                #endif                          if (mdesc->restart_mode)             reboot_setup(&mdesc->restart_mode);                  init_mm.start_code = (unsigned long) _text;         init_mm.end_code   = (unsigned long) _etext;         init_mm.end_data   = (unsigned long) _edata;         init_mm.brk    = (unsigned long) _end;               /* populate cmd_line too for later use, preserving boot_command_line */         strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);         *cmdline_p = cmd_line;               parse_early_param();

關鍵函式是setup_machine_tags,如下:

    static struct machine_desc * __init setup_machine_tags(unsigned int nr)     {         struct tag *tags = (struct tag *)&init_tags;         struct machine_desc *mdesc = NULL, *p;         char *from = default_command_line;     。。。。         if (__atags_pointer)             tags = phys_to_virt(__atags_pointer);         else if (mdesc->atag_offset)             tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);           。。。。。         if (tags->hdr.tag == ATAG_CORE) {             if (meminfo.nr_banks != 0)                 squash_mem_tags(tags);             save_atags(tags);             parse_tags(tags);         }               /* parse_early_param needs a boot_command_line */         strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);     。。。     }

首先回去獲取tags的首地址,如果收個tag是ATAG_CORE型別,則會呼叫save_atags拷貝一份tags,最後呼叫parse_tags來分析這個tag list,如下:

    static int __init parse_tag(const struct tag *tag)     {         extern struct tagtable __tagtable_begin, __tagtable_end;         struct tagtable *t;               for (t = &__tagtable_begin; t < &__tagtable_end; t++)             if (tag->hdr.tag == t->tag) {                 t->parse(tag);                 break;             }                  return t < &__tagtable_end;     }                     /*        * Parse all tags in the list, checking both the global and architecture      * specific tag tables.      */              static void __init parse_tags(const struct tag *t)     {                for (; t->hdr.size; t = tag_next(t))             if (!parse_tag(t))                 printk(KERN_WARNING                     "Ignoring unrecognised tag 0x%08x\n",                     t->hdr.tag);     }   

遍歷tags list,找到在tagstable中匹配的處理函式(hdr.tag一致),來處理響應的tag。

這個tagtable的處理函式是在呼叫__tagtable來註冊的,如下:

    static int __init parse_tag_cmdline(const struct tag *tag)     {     #if defined(CONFIG_CMDLINE_EXTEND)         strlcat(default_command_line, " ", COMMAND_LINE_SIZE);         strlcat(default_command_line, tag->u.cmdline.cmdline,             COMMAND_LINE_SIZE);     #elif defined(CONFIG_CMDLINE_FORCE)         pr_warning("Ignoring tag cmdline (using the default kernel command line)\n");     #else         strlcpy(default_command_line, tag->u.cmdline.cmdline,             COMMAND_LINE_SIZE);     #endif         return 0;     }           __tagtable(ATAG_CMDLINE, parse_tag_cmdline);

看這個對cmdline型別的tag的處理,就是將tag中的cmdline拷貝到default_command_line中。還有其他如mem型別的引數也會註冊這個處理函式,來匹配處理響應的tag。這裡就先以cmdline的tag為例。

這樣遍歷並處理完tags list之後回到setup_machine_tags,將from(即default_command_line)中的cmdline拷貝到boot_command_line,

最後返回到setup_arch中,

        /* populate cmd_line too for later use, preserving boot_command_line */         strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);         *cmdline_p = cmd_line;               parse_early_param();

將boot_command_line拷貝到start_kernel給setup_arch的cmdline_p中,這裡中間拷貝的boot_command_line是給parse_early_param來做一個早期的引數分析的。

到這裡kernel就完全接收並分析完成了uboot傳過來的args。

簡單的講,uboot利用函式指標及傳參規範,它將

l   R0: 0x0 l   R1: 機器號 l   R2: 引數地址 三個引數傳遞給核心。

其中,R2暫存器傳遞的是一個指標,這個指標指向一個TAG區域。

UBOOT和Linux核心之間正是通過這個擴充套件了的TAG區域來進行復雜引數的傳遞,如 command line,檔案系統資訊等等,使用者也可以擴充套件這個TAG來進行更多引數的傳遞。TAG區域的首地址,正是R2的值。

原文:https://blog.csdn.net/skyflying2012/article/details/35787971