u-boot之内核是怎么启动的

uboot之start_armboot函数分析已经分析过了整个程序框架,但只是说了下什么运行内核,并没有具体说明是怎么执行内核的。内核启动分以下几个步骤说明:

1、启动参数bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0说明

2、run_command函数是怎么执行命令的

3、uboot给内核传递的参数说明

4、内核启动流程

1、启动参数bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0说明

这个参数分了两条uboot命令首先从kernel区拷贝相应大小的内核到内存0x30007FC0 处;然后执行 bootm 0x30007FC0命令,这条命令后面会详细解释流程。执行了这条命令后就可以从0x30007FC0处启动内核了。这里需要再说明一下其实真正的内核位于0x30008000处,因为前面64字节是内核的一些信息,具体定义如下:

typedef struct image_header {
    uint32_t    ih_magic;    /* Image Header Magic Number    *///内核的编号 
    uint32_t    ih_hcrc;    /* Image Header CRC Checksum    */      //内核头部数据的CRC校验
    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    */   //内核数据的CRC校验
    uint8_t        ih_os;        /* Operating System        *///内核是什么操作系统
    uint8_t        ih_arch;    /* CPU architecture        */      //内核的CPU架构
    uint8_t        ih_type;    /* Image Type            */     //内核印象文件类型
    uint8_t        ih_comp;    /* Compression Type        *///亚索类型
    uint8_t        ih_name[IH_NMLEN];    /* Image Name        *///内核映像文件名称
} image_header_t;

2、run_command函数是怎么执行命令的

 run_command函数在Main.c (common)中,这个函数是整个UBOOT命令的核心函数。uboot的链接文件中存在着.u_boot_cmd段,这个段的内容就是存放的所有的UBOOT命令

这个段的定义在command.h(include)中,如下:其中

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
//分解为=>U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) cmd_tbl_t __u_boot_cmd_name __attribute__ ((unused,section (".u_boot_cmd"))) = {name, maxargs, rep, cmd, usage, help}
//name为命令的名字
//maxargs为命令的最大数量
//rep为命令是否可以重复,表示按下回车是否可以运行上一次的命令
//cmd表示具体命令的执行函数指针,这个函数不在.u_boot_cmd这个段内,只是函数的指针存放在.u_boot_cmd这个段内
//usage表示简短的用法说明
//help表示长的说明
struct cmd_tbl_s {
char    *name;    /* Command Name    */
int    maxargs;    /* maximum number of arguments    */
int    repeatable;    /* autorepeat allowed?    */
/* Implementation function    */
int    (*cmd)(struct cmd_tbl_s *, int, int, char *[]);
char    *usage;    /* Usage message    (short)    */
#ifdef    CFG_LONGHELP
char    *help;    /* Help message    (long)    */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int    (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};

举个例子以bootm这个命令为例子,在uboot控制台输入bootm命令后,uboot代码会执行run_command(bootm, 0);这个函数。然后调用do_bootm函数。

U_BOOT_CMD(
     bootm,    CFG_MAXARGS,    1,    do_bootm,
     "bootm   - boot application image from memory\n",
     "[addr [arg ...]]\n    - boot application image stored in memory\n"
     "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
     "\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
    "\tWhen booting a Linux kernel which requires a flat device-tree\n"
    "\ta third argument is required which is the address of the of the\n"
    "\tdevice-tree blob. To boot that kernel without an initrd image,\n"
    "\tuse a '-' for the second argument. If you do not pass a third\n"
    "\ta bd_info struct will be passed instead\n"
#endif
);

照着执行bootm命令继续下去,run_command(bootm, 0)函数首先做一些参数的检查

clear_ctrlc();        /* forget any previous Control C *///清除前一条命令 

    if (!cmd || !*cmd) {//检查cmd命令是否存在
        return -1;    /* empty command */
    }

    if (strlen(cmd) >= CFG_CBSIZE) {//检查参数个数是否超过最大限制
        puts ("## Command too long!\n");
        return -1;
    }

    strcpy (cmdbuf, cmd);//将cmd全部拷贝到cmdbuf中

run_command(bootm, 0)继续运行,一直循环寻找str中的命令。str在初始化的时候就已经被赋为cmdbuf了。大致说明一下程序的流程:

a、首先就查询命令有几条

b、展开调用环境变量的命令

c、取得参数的个数

d、查找命令,从.u_boot_cmd段中找出符合的命令

e、执行命令的运行函数

while (*str) {//从输入的字符中取出有效命令  by andy

        /*
         * Find separator, or string end
         * Allow simple escape of ';' by writing "\;"
         */
        for (inquotes = 0, sep = str; *sep; sep++) {//一直查询到最后一个字节为结束符
            if ((*sep=='\'') &&     //查找连接符合  
                (*(sep-1) != '\\'))
                inquotes=!inquotes;  //如果是连接符的话

            if (!inquotes &&
                (*sep == ';') &&    /* separator        *///说明是多个连续的命令
                ( sep != str) &&    /* past string start    */
                (*(sep-1) != '\\'))    /* and NOT escaped    */
                break;
        }

        /*
         * Limit the token to data between separators
         */
        token = str;
        if (*sep) {//如果不止一条命令 by andy
            str = sep + 1;    /* start of command for next pass *///指向下一条命令的开头,当前seq指向的是;
            *sep = '\0';
        }
        else
            str = sep;    /* no more commands for next pass */
#ifdef DEBUG_PARSER
        printf ("token: \"%s\"\n", token);
#endif

        /* find macros in this token and replace them */
        process_macros (token, finaltoken);//展开环境变量  by andy,以$()或${}取得变量最终得到finaltoken

        /* Extract arguments */
        if ((argc = parse_line (finaltoken, argv)) == 0) {//取得变量个数以及变量的值存放在argv中,变量个数包括命令,所以应该大于0  by andy
            rc = -1;    /* no command at all */
            continue;
        }

        /* Look up command in command table */
        if ((cmdtp = find_cmd(argv[0])) == NULL) {//查找命令,找到后返回的是cmd_tbl_t结构体指针  by andy
            printf ("Unknown command '%s' - try 'help'\n", argv[0]);
            rc = -1;    /* give up after bad command */
            continue;
        }

        /* found - check max args */
        if (argc > cmdtp->maxargs) {//验证参数的个数是否超过限制  by andy
            printf ("Usage:\n%s\n", cmdtp->usage);
            rc = -1;
            continue;
        }

#if (CONFIG_COMMANDS & CFG_CMD_BOOTD)
        /* avoid "bootd" recursion */
        if (cmdtp->cmd == do_bootd) {
#ifdef DEBUG_PARSER
            printf ("[%s]\n", finaltoken);
#endif
            if (flag & CMD_FLAG_BOOTD) {
                puts ("'bootd' recursion detected\n");
                rc = -1;
                continue;
            } else {
                flag |= CMD_FLAG_BOOTD;
            }
        }
#endif    /* CFG_CMD_BOOTD */

        /* OK - call function to do the command */
        if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {//调用命令函数执行  by andy
            rc = -1;
        }

        repeatable &= cmdtp->repeatable;

        /* Did the user stop this? */
        if (had_ctrlc ())//是否有crtl+c按键按下,按下的话就结束下一条命令的处理  by andy
            return 0;    /* if stopped then not repeatable */
    }

对从.u_boot_cmd段中找出符合的命令的代码做一下注释

cmd_tbl_t *find_cmd (const char *cmd)
{
    cmd_tbl_t *cmdtp;
    cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start;    /*Init value */
    const char *p;
    int len;
    int n_found = 0;

    /*
     * Some commands allow length modifiers (like "cp.b");
     * compare command name only until first dot.
     */
    len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);//取得命令长度,不包含.后面的字符  by andy

    for (cmdtp = &__u_boot_cmd_start;//__u_boot_cmd_start在链接文件中定义  by andy
         cmdtp != &__u_boot_cmd_end;//__u_boot_cmd_end在链接文件中定义  by andy
         cmdtp++) {
        if (strncmp (cmd, cmdtp->name, len) == 0) {//比较在搜索的段中是否有相同的命令名称  by andy
            if (len == strlen (cmdtp->name))
                return cmdtp;    /* full match *///完全匹配到了  by andy

            cmdtp_temp = cmdtp;    /* abbreviated command ? */
            n_found++;
        }
    }
    if (n_found == 1) {            /* exactly one match *///命令找到了  by andy
        return cmdtp_temp;
    }

    return NULL;    /* not found or ambiguous command */
}

3、u-boot给内核传递的参数说明

Bootloader与内核的交互式单向的。内核和u-boot约定好将参数放在某个位置,内核去取就行了。而这里规定的地址为。具体含义可以参考uboot之start_armboot函数分析这里只需要知道0x30000100是内核与u-boot交互的数据的首地址。

    /* adress of boot parameters */
    gd->bd->bi_boot_params = 0x30000100;//与内核交互的数据存放的地址设置  by andy

u-boot与内核交互的数据称为标记列表,位于Setup.h (include\asm-arm) ,它是以ATAG_CORE开始、ATAG_NONE结束。它的结构为:

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_header 的结构为
struct tag_header {
    u32 size;//标记的大小
    u32 tag;//标记的类型
};

主要的标记列表有ATAG_CORE开始标记、ATAG_MEN内存标记、ATAG_CMDLINE命令行标记、ATAG_NONE结束标记

#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)
    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
    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 ();del by andy
    }
#endif

ATAG_CORE开始标记初始化

static void setup_start_tag (bd_t *bd)//设置标记atag_core 
{
    params = (struct tag *) bd->bi_boot_params;//标记的初始化地址为0x30000100

    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);//计算出下一个标记的地址
}

ATAG_MEN内存标记初始化

static void setup_memory_tags (bd_t *bd)
{
    int i;

    for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {//只有一个SDRAM  
        params->hdr.tag = ATAG_MEM;                  //设置内存标记的符号
        params->hdr.size = tag_size (tag_mem32);//计算内存标记的大小

        params->u.mem.start = bd->bi_dram[i].start;//SDRAM的开始地址
        params->u.mem.size = bd->bi_dram[i].size;//SDRAM的结束地址

        params = tag_next (params);//计算下一个标记的地址
    }
}

ATAG_CMDLINE命令行标记初始化

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);//计算下一个标记的地址
}

ATAG_NONE结束标记

static void setup_end_tag (bd_t *bd)
{
    params->hdr.tag = ATAG_NONE;//结束标记的标志
    params->hdr.size = 0;              //结束标记的大小为0
}

4、内核启动流程

接着第二条后面的内容分析。运行bootm命令后已经找到了bootm命令的执行函数,接下来进入bootm命令的执行函数do_bootm函数分析,它位于Cmd_bootm.c (common) 文件中。

首先是一些参数的检查以及打印一些内核准备运行的符号

    if (argc < 2) {//如果参数小于2,从默认地址0x33000000 启动  by andy
        addr = load_addr;
    } else {
        addr = simple_strtoul(argv[1], NULL, 16);//否则取出第二个参数addr = 0x30007FC0
    }

    SHOW_BOOT_PROGRESS (1);//
    printf ("## Booting image at %08lx ...\n", addr);//打印内核从哪里启动  by andy
 

接着往下看是从内核的头64个字节中取出内核的头部到header变量中

memmove (&header, (char *)addr, sizeof(image_header_t));//拷贝内存0x30007FC0 到header,这里的数据是内核提供的,主要用于作比较看内核是否符号启动条件

接着是比较U-BOOT支持的内核与需要启动的内核是否一致

    if (ntohl(hdr->ih_magic) != IH_MAGIC) {//如果uboot启动的内核和位于内存的内核不匹配 则不启动内核  by andy
#ifdef __I386__    /* correct image format not implemented yet - fake it */
        if (fake_header(hdr, (void*)addr, -1) != NULL) {
            /* to compensate for the addition below */
            addr -= sizeof(image_header_t);
            /* turnof verify,
             * fake_header() does not fake the data crc
             */
            verify = 0;
        } else
#endif    /* __I386__ */
        {
        puts ("Bad Magic Number\n");
        SHOW_BOOT_PROGRESS (-1);
        return 1;
        }
    }
    SHOW_BOOT_PROGRESS (2);//

接下去是进行从内核取出的头部数据的CRC校验

    data = (ulong)&header;         //取出头部数据的地址
    len  = sizeof(image_header_t);//取出头部数据的大小

    checksum = ntohl(hdr->ih_hcrc);//取出头部数据的crc校验值
    hdr->ih_hcrc = 0;

    if (crc32 (0, (uchar *)data, len) != checksum) {//如果内核的头数据crc校验出错也不启动内核  by andy
        puts ("Bad Header Checksum\n");
        SHOW_BOOT_PROGRESS (-2);
        return 1;
    }
    SHOW_BOOT_PROGRESS (3);//空 by andy

CRC校验正确后打印头部信息如下

   Image Name:   Linux-2.6.22.6
   Created:      2013-08-23   7:33:38 UTC
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    1848668 Bytes =  1.8 MB
   Load Address: 30008000
   Entry Point:  30008000
   Verifying Checksum ... OK

具体的函数为

/* for multi-file images we need the data part, too */
    print_image_hdr ((image_header_t *)addr);//打印内核信息  by andy
print_image_hdr (image_header_t *hdr)
{
#if (CONFIG_COMMANDS & CFG_CMD_DATE) || defined(CONFIG_TIMESTAMP)
    time_t timestamp = (time_t)ntohl(hdr->ih_time);
    struct rtc_time tm;
#endif

    printf ("   Image Name:   %.*s\n", IH_NMLEN, hdr->ih_name);//打印内核名称Linux-2.6.22.6
#if (CONFIG_COMMANDS & CFG_CMD_DATE) || defined(CONFIG_TIMESTAMP)
    to_tm (timestamp, &tm);
    printf ("   Created:      %4d-%02d-%02d  %2d:%02d:%02d UTC\n",
        tm.tm_year, tm.tm_mon, tm.tm_mday,
        tm.tm_hour, tm.tm_min, tm.tm_sec);//打印创建时间2013-08-23   7:33:38 UTC
#endif    /* CFG_CMD_DATE, CONFIG_TIMESTAMP */
    puts ("   Image Type:   "); print_type(hdr);//打印内核类型ARM Linux Kernel Image (uncompressed)
    printf ("\n   Data Size:    %d Bytes = ", ntohl(hdr->ih_size));
    print_size (ntohl(hdr->ih_size), "\n");//打印内核大小1848668 Bytes =  1.8 MB
    printf ("   Load Address: %08x\n"
        "   Entry Point:  %08x\n",
         ntohl(hdr->ih_load), ntohl(hdr->ih_ep));//打印装载地址与切入点地址Load Address: 30008000 Entry Point:  30008000

    if (hdr->ih_type == IH_TYPE_MULTI) {
        int i;
        ulong len;
        ulong *len_ptr = (ulong *)((ulong)hdr + sizeof(image_header_t));

        puts ("   Contents:\n");
        for (i=0; (len = ntohl(*len_ptr)); ++i, ++len_ptr) {
            printf ("   Image %d: %8ld Bytes = ", i, len);
            print_size (len, "\n");
        }
    }
}

接着是校验内核数据

data = addr + sizeof(image_header_t);//真正的linux内核位于的地方  by andy
    len  = ntohl(hdr->ih_size);//内核的长度

    if (verify) {//如果需要验证  by andy
        puts ("   Verifying Checksum ... ");
        if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {//crc校验内核数据  by andy
            printf ("Bad Data CRC\n");
            SHOW_BOOT_PROGRESS (-3);
            return 1;
        }
        puts ("OK\n");//验证成功  by andy
    }
    SHOW_BOOT_PROGRESS (4);//

    len_ptr = (ulong *)data;//data 为真正的linux内核地址为0x30008000  by andy

接下去是运行do_bootm_linux函数,准备启动内核了

 do_bootm_linux  (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);//设置标记列表,然后启动内核

接着分析do_bootm_linux函数,它在Armlinux.c (lib_arm)中定义。前面主要是一些参数的检查,就不列出程序了。只列出一些关键的程序

#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");//取得命令行  by andy
#endif

    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);//theKernel函数指针hdr->ih_ep为内核切入点的地址,是内核传过来的  by andy

/*参数列表初始化*/
#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)
    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
    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 ();del by andy
    }
#endif

    cleanup_before_linux ();//关闭I-CATCH与D-CATCH,清除I-cache与D-cache

    theKernel (0, bd->bi_arch_number, bd->bi_boot_params);//启动内核bd->bi_arch_number机器类型ID,bd->bi_boot_params为标记列表的开始地址

最终调用theKernel进入内核

猜你喜欢

转载自www.cnblogs.com/andyfly/p/9355063.html