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:刚才提到的参数的地址

至此我们今天的工作全部结束。

你可能感兴趣的:(Boot)