u-boot分析(三)---boot命令實現以及內核的啓動

  上片博文總結出了u-boot的工作流程,今天我們來分析,u-boot的兩個比較重要的內容

1.        U-boot命令的實現

2.        U-boot如何啓動內核

l  命令實現

我們的u-boot可以解析輸入的命令,比如printsetenvsaveenv等命令,我們下來對其的實現進行分析。

我們昨天分析到BL2最後停在了main_loop處,那麼我們輸入的命令肯定也是在這個函數中實現的,我們找到該函數,在main_loop函數中run_command函數很容易引起我們的關注,跳到該函數進行分析,在該函數中有下面幾個比較重要的點

1.        從註釋我們很容易知道這段代碼是在對命令進行分離,並且u-boot支持’;’分離命令。

 

 1         /*
 2          * Find separator, or string end
 3          * Allow simple escape of ';' by writing "\;"
 4          */
 5         for (inquotes = 0, sep = str; *sep; sep++) {
 6             if ((*sep=='\'') &&
 7                 (*(sep-1) != '\\'))
 8                 inquotes=!inquotes;
 9 
10             if (!inquotes &&
11                 (*sep == ';') &&    /* separator        */
12                 ( sep != str) &&    /* past string start    */
13                 (*(sep-1) != '\\'))    /* and NOT escaped    */
14                 break;
15         }

2.        分離參數

 

1         /* Extract arguments */
2         if ((argc = parse_line (finaltoken, argv)) == 0) {
3             rc = -1;    /* no command at all */
4             continue;
5         }

3.        用第一個參數argv[0]在命令列表中尋找對應的命令,並返回一個cmd_tbl_t類型的實例。我們可以猜到這個結構體應該保函了有關命令的一系列內容。

1 /* Look up command in command table */
2         if ((cmdtp = find_cmd(argv[0])) == NULL) {
3             printf ("Unknown command '%s' - try 'help'\n", argv[0]);
4             rc = -1;    /* give up after bad command */
5             continue;
6         }

 

n  我們先看find_cmd,通過代碼跟蹤我們會在find_cmd_tbl函數中找到核心代碼

 1 for (cmdtp = table;
 2          cmdtp != table + table_len;
 3          cmdtp++) {
 4         if (strncmp (cmd, cmdtp->name, len) == 0) {
 5             if (len == strlen (cmdtp->name))
 6                 return cmdtp;    /* full match */
 7 
 8             cmdtp_temp = cmdtp;    /* abbreviated command ? */
 9             n_found++;
10         }
11     }

 

通過上面代碼我們知道了其查找方法,但是相信很多人和我一樣很疑惑這個命令表到底在什麼地方。

按照我們對上面代碼的閱讀,和猜測我們可以知道這個表的開始地址是table,我們可以輕鬆的找到table的來源。

1 cmd_tbl_t *find_cmd (const char *cmd)
2 {
3     int len = &__u_boot_cmd_end - &__u_boot_cmd_start;
4     return find_cmd_tbl(cmd, &__u_boot_cmd_start, len);
5 }

 

通過上面代碼我們知道table等於__u_boot_cmd_start,通過全局搜索,我們找到這個地址的來源是\arch\arm\cpu\armv7\u-boot.lds

1     __u_boot_cmd_start = .;
2     .u_boot_cmd : { *(.u_boot_cmd) }
3     __u_boot_cmd_end = .;

 

 

 

__u_boot_cmd_start__u_boot_cmd_end之間放了一個.u_boot_cmd段,我們再對這個段名進行搜索找到了下面的宏

1 #define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))//強制設置段屬性爲.u_boot_cmd

 

其肯定通過該宏又定義了什麼東西,再經過搜索我們找到以下內容

1 #define U_BOOT_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,comp) \
2     cmd_tbl_t __u_boot_cmd_##name Struct_Section = \
3         U_BOOT_CMD_MKENT_COMPLETE(name,maxargs,rep,cmd,usage,help,comp)

 

其又定義了一個宏,通過對宏的閱讀我們可以知道,通過U_BOOT_CMD_COMPLETE這個宏可以定義一個cmd_tbl_t類型的結構體,並且將該結構體的段屬性強制設置爲.u_boot_cmd,再對這個宏搜索,找到的只有幾個命令,完全對不上,但是我們又找到下面的宏

1 #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
2     U_BOOT_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,NULL)

 

通過對U_BOOT_CMD的搜索我們找到了大量的命令定義。

至此我們可以完全清楚了這個命令表示怎麼來的,其是通過U_BOOT_CMD這樣一個宏去定義命令。隨便可以找到例子:

1 U_BOOT_CMD(
2     help,    CONFIG_SYS_MAXARGS,    1,    do_help,
3     "print command description/usage",
4     "\n"
5     "    - print brief description of all commands\n"
6     "help command ...\n"
7     "    - print detailed usage of 'command'"
8 );

 

n  再來看cmd_tbl_t結構體,其中保函了命令名,最大參數,以及對應函數等內容。

 1 struct cmd_tbl_s {
 2     char        *name;        /* Command Name            */
 3     int        maxargs;    /* maximum number of arguments    */
 4     int        repeatable;    /* autorepeat allowed?        */
 5                     /* Implementation function    */
 6     int        (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
 7     char        *usage;        /* Usage message    (short)    */
 8 #ifdef    CONFIG_SYS_LONGHELP
 9     char        *help;        /* Help  message    (long)    */
10 #endif
11 #ifdef CONFIG_AUTO_COMPLETE
12     /* do auto completion on the arguments */
13     int        (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
14 #endif
15 };

 

至此我們可以通過上面的內容實現我們自己的簡單u-boot命令,下面是我實現的hello命令

 1 #include <common.h>
 2 #include <command.h>
 3 
 4 int do_hello(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
 5 {
 6     printf("hello u-boot");
 7     return 0;
 8 }
 9 
10 U_BOOT_CMD(
11     hello,    CONFIG_SYS_MAXARGS,    1,    do_hello,
12     "print hello",/*短幫助信息*/
13     "\n hello cmd ............"//長幫幫助信息 
14     );

 

加入上面內容,並且修改common目錄下的makefile,然後重新編譯u-boot,就會完成我們自己的u-boot命令,至此我們的u-boot命令實現分析完畢。

l  啓動內核

我們的u-boot可以通過nandtftp等方式,將我們的內核加載至內存,對於這個過程今天就不重點去分析了,今天我們重點分析從內存中如何啓動內核,我們都知道啓動內核的時候要用到bootm命令,按照我們上面分析命令實現的經驗,可以猜出其必然會去運行do_bootm函數。下面我們主要分析這個函數的實現。

我們的bootm只能啓動uImage,然而uImage = zImage(真正的內核) + 頭信息。所以我們首先來看看頭信息:

 

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;

 

這裏面存放了大量的內核信息,我們也可以找到do_bootm中用這些信息進行內核的校驗,加載地址的校驗等工作。

假設我們啓動的是linuxu-boot支持多種系統啓動,下面代碼列出),我們的u-boot會執行到do_bootm_linux函數

 

 1 static boot_os_fn *boot_os[] = {
 2 #ifdef CONFIG_BOOTM_LINUX
 3     [IH_OS_LINUX] = do_bootm_linux,
 4 #endif
 5 #ifdef CONFIG_BOOTM_NETBSD
 6     [IH_OS_NETBSD] = do_bootm_netbsd,
 7 #endif
 8 #ifdef CONFIG_LYNXKDI
 9     [IH_OS_LYNXOS] = do_bootm_lynxkdi,
10 #endif
11 #ifdef CONFIG_BOOTM_RTEMS
12     [IH_OS_RTEMS] = do_bootm_rtems,
13 #endif
14 #if defined(CONFIG_BOOTM_OSE)
15     [IH_OS_OSE] = do_bootm_ose,
16 #endif
17 #if defined(CONFIG_CMD_ELF)
18     [IH_OS_VXWORKS] = do_bootm_vxworks,
19     [IH_OS_QNX] = do_bootm_qnxelf,
20 #endif
21 #ifdef CONFIG_INTEGRITY
22     [IH_OS_INTEGRITY] = do_bootm_integrity,
23 #endif
24 };

 

下面我們來分析do_bootm_linux函數主要有以下內容

a)        和內核進行交接工作,爲內核設置啓動參數

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG)
    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 (images->rd_start && images->rd_end)
        setup_initrd_tag (bd, images->rd_start, images->rd_end);
#endif
    setup_end_tag(bd);
#endif

 

這些代碼主要是將參數,按照固定的格式寫到固定的地方。內核啓動後將會去這個地址讀取參數。

b)        跳到入口地址,啓動內核

1 kernel_entry = (void (*)(int, int, uint))images->ep;
2 kernel_entry(0, machid, bd->bi_boot_params);

 

machid:我們的機器ID

bd->bi_boot_params:剛纔提到的參數的地址

至此我們今天的工作全部結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章