概述uboot的主要作用是用来启动linux内核。 CPU不能直接从块设备中执行代码,因此需要把块设备中的程序复制到内存中,而复制之前还需要进行很多初始化工作,如初始化时钟、串口、dram等。这些初始化工作,都是由uboot完成的。 这些初始化工作完成后,uboot将块设备中的内核代码复制到某个内存地址,然后再执行“bootm xxx”命令以启动内核代码。 uboot启动后自动运行,打印出很多信息,然后uboot进入倒数bootdelay秒,如下所示。 root@ubuntu:/home/ubuntu/Desktop/u-boot-2017.05-rc2# qemu-system-arm -M vexpress-a9 -m 512M -kernel u-boot -nographic pulseaudio: set_sink_input_volume() failed pulseaudio: Reason: Invalid argument pulseaudio: set_sink_input_mute() failed pulseaudio: Reason: Invalid argument
U-Boot 2017.05-rc2 (Nov 05 2023 - 04:42:36 -0800)
DRAM: 512 MiB WARNING: Caches not enabled Flash: 128 MiB MMC: MMC: 0 *** Warning - bad CRC, using default environment
In: serial Out: serial Err: serial Net: smc911x-0 Hit any key to stop autoboot: 0 =>
此时,如果用户没有按键操作时,则uboot会读取bootcmd这个环境变量,并使用rum_command函数来执行这个环境变量对应的命令,进入自动启动内核流程。 如果用户打断uboot的自启动过程,则进入uboot的命令行,然后uboot一直工作在命令行下,不断地重复执行“接收命令、解析命令、执行命令”的流程。 main_loop函数uboot通过调用run_main_loop函数执行循环命令程序 static int run_main_loop(void) { #ifdef CONFIG_SANDBOX sandbox_main_loop_init(); #endif /* main_loop() can return to retry autoboot, if so just run it again */ for (;;) main_loop(); return 0; }
main_loop函数定义如下: /* We come here after U-Boot is initialised and ready to process commands */ void main_loop(void) { const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
#ifdef CONFIG_VERSION_VARIABLE setenv("ver", version_string); /* set version variable */ #endif /* CONFIG_VERSION_VARIABLE */
cli_init();
run_preboot_environment_command();
#if defined(CONFIG_UPDATE_TFTP) update_tftp(0UL, NULL, NULL); #endif /* CONFIG_UPDATE_TFTP */
s = bootdelay_process(); if (cli_process_fdt(&s)) cli_secure_boot_cmd(s);
autoboot_command(s);
cli_loop(); panic("No CLI available"); }
bootstage_mark_name:打印出启动进度 cli_init():命令初始化有关,初始化 hush shell 相关的变量 run_preboot_environment_command:获取环境变量 perboot 的内容 s = bootdelay_process:此函数会读取环境变量 bootdelay 和 bootcmd 的内容 将 bootdelay 的值赋值给全局变量 stored_bootdelay,返回值为环境变量 bootcmd 的值。 autoboot_command:检查倒计时是否结束 cli_loop:命令行处理函数 autoboot_commandvoid autoboot_command(const char *s) { debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) { #if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC) int prev = disable_ctrlc(1); /* disable Control C checking */ #endif
run_command_list(s, -1, 0);
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC) disable_ctrlc(prev); /* restore Control C checking */ #endif }
#ifdef CONFIG_MENUKEY if (menukey == CONFIG_MENUKEY) { s = getenv("menucmd"); if (s) run_command_list(s, -1, 0); } #endif /* CONFIG_MENUKEY */ }
大致执行流程:判断2个条件,if成立进入执行run_command_list()启动内核,否则相当于什么都不执行,函数返回,接着执行cli_loop函数 if成立的关键,abortboot()函数,判断键盘是否按下,按下if就不成立,否则成立 abortbootstatic int abortboot(int bootdelay) { int abort = 0;
if (bootdelay >= 0) abort = __abortboot(bootdelay);
#ifdef CONFIG_SILENT_CONSOLE if (abort) gd->flags &= ~GD_FLG_SILENT; #endif
return abort; }
static int __abortboot(int bootdelay) { int abort = 0; unsigned long ts;
#ifdef CONFIG_MENUPROMPT printf(CONFIG_MENUPROMPT); #else printf("Hit any key to stop autoboot: %2d ", bootdelay); #endif
/* * Check if key already pressed */ if (tstc()) { /* we got a key press */ (void) getc(); /* consume input */ puts("\b\b\b 0"); abort = 1; /* don't auto boot */ }
while ((bootdelay > 0) && (!abort)) { --bootdelay; /* delay 1000 ms */ ts = get_timer(0); do { if (tstc()) { /* we got a key press */ abort = 1; /* don't auto boot */ bootdelay = 0; /* no more delay */ # ifdef CONFIG_MENUKEY menukey = getc(); # else (void) getc(); /* consume input */ # endif break; } udelay(10000); } while (!abort && get_timer(ts) < 1000);
printf("\b\b\b%2d ", bootdelay); }
putc('\n');
return abort; }
最终执行__abortboot函数 __abortboot:判断键盘是否按下,按下返回1,否则0倒计时并输出 从上面函数可以看到假设倒计时结束前终止了,则直接返回,执行cli_loop函数,这就是那个shell环境,我们可以在里面输入命令,设置环境变量等。 如果倒计时完了没有终止,则会执行run_command_list,s是bootcmd的内容。 cli_loop函数如用户在设定的bootdelay内有按键输入,那么将运行cli_loop执行hush shell命令解释器: void cli_loop(void) { #ifdef CONFIG_HUSH_PARSER parse_file_outer(); /* This point is never reached */ for (;;); #elif defined(CONFIG_CMDLINE) cli_simple_loop(); #else printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n"); #endif /*CONFIG_HUSH_PARSER*/ }
parse_file_outer进行必要的初始化后,也将调用hush shell的命令解释器,即parse_stream_outer函数: /* most recursion does not come through here, the exeception is * from builtin_source() */ static int parse_stream_outer(struct in_str *inp, int flag) {
struct p_context ctx; o_string temp=NULL_O_STRING; int rcode; #ifdef __U_BOOT__ int code = 1; #endif do { ......... ......... run_list(ctx.list_head); code = run_list(ctx.list_head); ......... ......... } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) && (inp->peek != static_peek || b_peek(inp))); #ifndef __U_BOOT__ return 0; #else return (code != 0) ? 1 : 0; #endif /* __U_BOOT__ */ }
上面的do-while会循环命令解析器的"命令输入解析--执行"运行模式。 其中的函数run_list执行如下的函数调用流程: run_list-->run_list_real-->run_pipe_real 最后在函数run_pipe_real中有: return cmd_process(...); 函数cmd_process最后完成u-boot命令的定位和执行。 run_command_list函数int run_command_list(const char *cmd, int len, int flag) { int need_buff = 1; char *buff = (char *)cmd; /* cast away const */ int rcode = 0;
if (len == -1) { len = strlen(cmd); #ifdef CONFIG_HUSH_PARSER /* hush will never change our string */ need_buff = 0; #else /* the built-in parser will change our string if it sees \n */ need_buff = strchr(cmd, '\n') != NULL; #endif } if (need_buff) { buff = malloc(len + 1); if (!buff) return 1; memcpy(buff, cmd, len); buff[len] = '\0'; } #ifdef CONFIG_HUSH_PARSER rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON); #else /* * This function will overwrite any \n it sees with a \0, which * is why it can't work with a const char *. Here we are making * using of internal knowledge of this function, to avoid always * doing a malloc() which is actually required only in a case that * is pretty rare. */ #ifdef CONFIG_CMDLINE rcode = cli_simple_run_command_list(buff, flag); #else rcode = board_run_command(buff); #endif #endif if (need_buff) free(buff);
return rcode; }
run_command_list会调用过程如下:
此时,流程又走到cmd_process函数。 cmd_processcmd_process 函数定义在文件 common/command.c 中,函数内容如下: enum command_ret_t cmd_process(int flag, int argc, char * const argv[], int *repeatable, ulong *ticks) { enum command_ret_t rc = CMD_RET_SUCCESS; cmd_tbl_t *cmdtp; /* Look up command in command table */ cmdtp = find_cmd(argv[0]); if (cmdtp == NULL) { printf("Unknown command '%s' - try 'help'\n", argv[0]); return 1; } /* found - check max args */ if (argc > cmdtp->maxargs) rc = CMD_RET_USAGE; #if defined(CONFIG_CMD_BOOTD) /* avoid "bootd" recursion */ else if (cmdtp->cmd == do_bootd) { if (flag & CMD_FLAG_BOOTD) { puts("'bootd' recursion detected\n"); rc = CMD_RET_FAILURE; } else { flag |= CMD_FLAG_BOOTD; } } #endif /* If OK so far, then do the command */ if (!rc) { if (ticks) *ticks = get_timer(0); rc = cmd_call(cmdtp, flag, argc, argv); if (ticks) *ticks = get_timer(*ticks); *repeatable &= cmdtp->repeatable; } if (rc == CMD_RET_USAGE) rc = cmd_usage(cmdtp); return rc; }
cmd_process 函数 调用函数 find_cmd函数 在命令表中找到指定的命令, find_cmd 函数内容如下: cmd_tbl_t *find_cmd(const char *cmd) { cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd); const int len = ll_entry_count(cmd_tbl_t, cmd); return find_cmd_tbl(cmd, start, len); }
参数 cmd 就是所查找的命令名字, uboot 中的命令表其实就是 cmd_tbl_t 结构体数组,通 过 ll_entry_star t 函数得到数组的第一个元素,也就是命令表起始地址。 通过 ll_entry_count 函数得到数组长度,也就是命令表的长度。 最终通过 find_cmd_tbl 函数在命令表中找到所需的命 令,每个命令都有一个 name 成员,所以将参数 cmd 与命令表中每个成员的 name 字段都对比 一下,如果相等的话就说明找到了这个命令,找到以后就返回这个命令。 cmd_process 函数中,找到命令以后肯定就要执行这个命令了, 调用 cmd_call函数来执行具体的命令,cmd_call 函数内容如下: static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { int result; result = (cmdtp->cmd)(cmdtp, flag, argc, argv); if (result) debug("Command failed, result=%d\n", result); return result; }
在前面的分析中我们知道, cmd_tbl_t 的 cmd 成员就是具体的命令处理函数,所以,cmd_call 函数中, 调用 cmdtp 的 cmd 成员来处理具体的命令,返回值为命令的执行结果。 cmd_process 中会检测 cmd_tbl 的返回值,如果返回值为 CMD_RET_USAGE ,就会调用 cmd_usage 函数输出命令的用法,其实就是输出 cmd_tbl_t 的 usage 成员变量。
|