在u-boot之start_armboot函数分析已经分析过了整个程序框架,但只是说了下什么时候运行内核,并没有具体说明是怎么执行内核的。内核启动分以下几个步骤说明:
1、启动参数bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0说明
2、run_command函数是怎么执行命令的
3、u-boot给内核传递的参数说明
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)中,这个函数是整个U-BOOT命令的核心函数。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这个命令为例子,在u-boot控制台输入bootm命令后,u-boot代码会执行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约定好将参数放在某个位置,内核去取就行了。而这里规定的地址为0x30000100。具体含义在前面第一点已经介绍。这里只需要知道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 (¶ms); #endif #ifdef CONFIG_REVISION_TAG setup_revision_tag (¶ms); #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 (¶ms); #endif #ifdef CONFIG_REVISION_TAG setup_revision_tag (¶ms); #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-cache与D-cache,清除I-cache与D-cache
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);//启动内核bd->bi_arch_number机器类型ID,bd->bi_boot_params为标记列表的开始地址
最终调用theKernel进入内核