进入到main_loop函数中,大致内容如下:
void main_loop (void)
{
...
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
...
s = getenv ("bootcmd");
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
...
{
printf("Booting Linux ...\n");
run_command (s, 0);
}
...
}
...
/*
run_command("menu", 0); 此处为厂家自己添加,若在uboot读秒时没有按下空格键则执行该命令
*/
...
for (;;) {
len = readline (CFG_PROMPT);
if (len > 0)
strcpy (lastcommand, console_buffer);
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
#ifdef CONFIG_BOOT_RETRY_TIME
...
rc = run_command (lastcommand, flag);
}
}
可以将main_loop的流程归于以下流程图:
在main_loop函数中,首先读取环境变量bootdelay的值。
假如在规定的秒数内没有按下空格键的话,则会执行 bootcmd 环境变量的值。
在我的JZ2440中,该环境变量的值为:bootcmd = nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
即会执行run_command (“nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0”, 0);
若在规定的秒数内按下空格,则会执行 len = readline (CFG_PROMPT),该语句是读取串口的输入,然后再执行 rc = run_command (lastcommand, flag),即执行串口输入的字符串。
所以还需要去深入了解run_command函数的执行过程。
以下是run_command函数:
int run_command (const char *cmd, int flag)
{
...
char *str = cmdbuf;
if (strlen(cmd) >= CFG_CBSIZE) {
puts ("## Command too long!\n");
return -1;
}
strcpy (cmdbuf, cmd);
...
while (*str) {
/*
* 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) {
str = sep + 1; /* start of command for next pass */
*sep = '\0';
}
else
str = sep; /* no more commands for next pass */
...
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL) {
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
rc = -1; /* give up after bad command */
continue;
}
...
/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
rc = -1;
}
}
}
在run_command函数中,主要是对字符串做一些解析,关键的部分在于find_cmd函数的调用,该函数的作用就是在一个结构体链表中去查找有无与该指令(argv[0])所相对应的结构体。若找到该结构体,则执行结构体中所涉及到的函数。
所以继续深入了解,find_cmd函数的内容如下:
cmd_tbl_t *find_cmd (const char *cmd)
{
...
/*
* Some commands allow length modifiers (like "cp.b");
* compare command name only until first dot.
*/
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
for (cmdtp = &__u_boot_cmd_start;
cmdtp != &__u_boot_cmd_end;
cmdtp++) {
if (strncmp (cmd, cmdtp->name, len) == 0) {
if (len == strlen (cmdtp->name))
return cmdtp; /* full match */
cmdtp_temp = cmdtp; /* abbreviated command ? */
n_found++;
}
}
if (n_found == 1) { /* exactly one match */
return cmdtp_temp;
}
return NULL; /* not found or ambiguous command */
}
该函数对传入进来的参数(argv[0],即cmd)做一些解析,接着在__u_boot_cmd_start与__u_boot_cmd_end的数据段中寻找有无与该参数相关的cmd_tbl_t结构体。该结构体内容如下:
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) */
char *help; /* Help message (long) */
...
};
其中第一个就是命令名,即 argv[0] (cmd),若匹配到的话则返回该结构体。
至于为什么会在__u_boot_cmd_start与__u_boot_cmd_end之间保存这些结构体,则与以下几个宏有关。
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
将宏替换掉则为:
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}
举个例子,对于 bootcmd 环境变量的值 = nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
对于其中的bootm,则在uboot源码中的Cmd_bootm.c中,则有
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"
);
所以将U_BOOT_CMD进行宏替换之后则变成
cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) =
{
bootm, CFG_MAXARGS, 1, do_bootm,
"[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"
};
这段代码的作用这是将该结构体变量放在.u_boot_cmd段之中,它位于__u_boot_cmd_start与__u_boot_cmd_end之间。至于.u_boot_cmd该段则是在链接文件u-boot.lds(可以查看我写的《初步了解UBOOT (2)》)中定义的。
从 bootcmd 环境变量的值 nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0,可以得知,一定存在两个U_BOOT_CMD,分别对应nand指令,还有bootm指令,即输入nand的话有与之对应的函数可以执行,输入bootm也有与之对应的函数可以执行,从上面的__u_boot_cmd_bootm定义可以得知,对于bootm有函数do_bootm对应,即会执行该函数,对于nand read.jffs2 0x30007FC0 kernel的作用很明白可以知道将内核从NAND FLASH的内核分区中读到0x30007FC0这个地址上,而指令bootm 0x30007FC0则是去这个地址上启动内核。
我们进一步的来分析do_bootm这个函数具体做了什么事情。