看到这里的同学,恭喜你,马上就看完了u-boot的源码了。
一、run_main_loop
我们介绍到了init_sequence_r的最后一个函数run_main_loop,该函数位于common/board_r.c文件中。
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; }
如果我们配置了CONFIG_BOOTCOMMAND 那么u-boot将会启动linux内核。
否则,进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后解析命令,执行命令。
我们先放一张main_loop的执行流程图,可以对着后面的讲解来看:
二、main_loop
main_loop函数位于common/main.c文件中。
/* 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"); #ifndef CONFIG_SYS_GENERIC_BOARD puts("Warning: Your board does not use generic board. Please readn"); puts("doc/README.generic-board and take action. Boards notn"); puts("upgraded by the late 2014 may break or be removed.n"); #endif #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"); }
该函数做的都是与平台无关的工作,主要包括:
- cli_init:用来初始化hush shell使用的变量top_vars ;
- bootdelay_process和autoboot_command:u-boot预启动相关函数,读取环境变量bootdelay和bootcmd的配置值,在u-boot启动延时计数期间内如无用户按键输入干预,那么将执行bootcmd配置中的命令(如果配置宏CONFIG_BOOTCOMMAND);如果有按键按下或者倒计时结束,将会进入u-boot命令行模式;
- cli_loop:死循环,进入u-boot命令行模式,解析命令,并执行对应函数;
2.1 cli_init
cli_init函数定义在common/cli.c:
void cli_init(void) { #ifdef CONFIG_SYS_HUSH_PARSER u_boot_hush_start(); #endif #if defined(CONFIG_HUSH_INIT_VAR) hush_init_var(); #endif }
CONFIG_SYS_HUSH_PARSER在CONFIG_SYS_HUSH_PARSER中定义。 u_boot_hush_start(common/cli_hush.c) 定义:
int u_boot_hush_start(void) { if (top_vars == NULL) { top_vars = malloc(sizeof(struct variables)); top_vars->name = "HUSH_VERSION"; top_vars->value = "0.01"; top_vars->next = NULL; top_vars->flg_export = 0; top_vars->flg_read_only = 1; #ifdef CONFIG_NEEDS_MANUAL_RELOC u_boot_hush_reloc(); #endif } return 0; }
其中top_vars定义:
struct variables shell_ver = { "HUSH_VERSION", "0.01", 1, 1, 0 }; struct variables *top_vars = &shell_ver;
从上可知:cli_init用来初始化hush shell使用的变量top_vars 。
2.2 run_preboot_environment_command
run_preboot_environment_command函数定义在common/main.c:
static void run_preboot_environment_command(void) { #ifdef CONFIG_PREBOOT char *p; p = getenv("preboot"); if (p != NULL) { # ifdef CONFIG_AUTOBOOT_KEYED int prev = disable_ctrlc(1); /* disable Control C checking */ # endif run_command_list(p, -1, 0); # ifdef CONFIG_AUTOBOOT_KEYED disable_ctrlc(prev); /* restore Control C checking */ # endif } #endif /* CONFIG_PREBOOT */ }
如果定义了CONFIG_PREBOOT,该函数将会从环境变量获取preboot的定义,该变量包含了一些预启动命令,一般环境变量中不包含该项配置。
由于CONFIG_PREBOOT宏未定义,所以这里均不执行。
2.3 bootdelay_process
bootdelay_process函数定义在common/autoboot.c:
const char *bootdelay_process(void) { char *s; int bootdelay; #ifdef CONFIG_BOOTCOUNT_LIMIT unsigned long bootcount = 0; unsigned long bootlimit = 0; #endif /* CONFIG_BOOTCOUNT_LIMIT */ #ifdef CONFIG_BOOTCOUNT_LIMIT bootcount = bootcount_load(); bootcount++; bootcount_store(bootcount); setenv_ulong("bootcount", bootcount); bootlimit = getenv_ulong("bootlimit", 10, 0); #endif /* CONFIG_BOOTCOUNT_LIMIT */ s = getenv("bootdelay"); bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; #ifdef CONFIG_OF_CONTROL bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay", bootdelay); #endif debug("### main_loop entered: bootdelay=%dnn", bootdelay); #if defined(CONFIG_MENU_SHOW) bootdelay = menu_show(bootdelay); #endif bootretry_init_cmd_timeout(); #ifdef CONFIG_POST if (gd->flags & GD_FLG_POSTFAIL) { s = getenv("failbootcmd"); } else #endif /* CONFIG_POST */ #ifdef CONFIG_BOOTCOUNT_LIMIT if (bootlimit && (bootcount > bootlimit)) { printf("Warning: Bootlimit (%u) exceeded. Using altbootcmd.n", (unsigned)bootlimit); s = getenv("altbootcmd"); } else #endif /* CONFIG_BOOTCOUNT_LIMIT */ s = getenv("bootcmd"); process_fdt_options(gd->fdt_blob); stored_bootdelay = bootdelay; return s; }
bootdelay_process从环境变量获取bootdelay和bootcmd配置值,将提取的bootdelay配置值转换成整数,赋值给全局变量stored_bootdelay。最后返回bootcmd的配置值。
u-boot在执行中,会输出如下调试信息:
initcall: 0000f4e4 (relocated to 33f304e4) ### main_loop entered: bootdelay=5
2.4 autoboot_command
autoboot_command函数定义在common/autoboot.c:
void 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 */ }
stored-bootdelay为u-boot的启动延时计数值,如果倒计时正常结束,那么将执行run_command_list,此函数会执行参数s指定的一系列命令,也就是bootcmd中配置中的命令,一般配置为linux内核启动命令,因此linux内核启动。
如果在倒计时结束前按下回车键,run_command_list就不会执行,autoboot_command相当于空函数,然后执行cli_loop函数,这个是命令行处理函数,负责接收处理输入命令。
三、cli_loop命令行解析
由于cli_loop的实现比较复杂,这里单独介绍。cli_loop定义在common/cli.c文件中:
void cli_loop(void) { #ifdef CONFIG_SYS_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_CMDLINEn"); #endif /*CONFIG_SYS_HUSH_PARSER*/ }
这里进行了循环执行命令的代码:
- 第一种形式是采用HUSH解析的方式;
- 第二种形式是采用cli_simple_loop的方式;
第二种调用比较简单。主要是直接从串口读取一行命令:
len = cli_readline(CONFIG_SYS_PROMPT);
然后调用如下函数开始执行:
rc = run_command_repeatable(lastcommand, flag);->cli_simple_run_command
然而smdk2410默认采用的HUSH解析的方式。下面我们重点介绍第一种,这种方式的函数调用流程如下图:
3.1 parse_file_outer
parse_file_outer函数定义在common/cli_hush.c:
int parse_file_outer(void) { int rcode; struct in_str input; setup_file_in_str(&input); rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON); return rcode; }
__U_BOOT__在common/cli_hush.c文件中定义,这里省略了该宏相关的代码。其中setup_file_in_str函数初始化了input结构参数:
static void setup_file_in_str(struct in_str *i) { i->peek = file_peek; i->get = file_get; i->__promptme=1; i->promptmode=1; i->p = NULL; }
3.2 parse_stream_outer
parse_stream_outer函数定义在common/cli_hush.c:
/* 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; int code = 1; do { ctx.type = flag; initialize_context(&ctx); update_ifs_map(); if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING)) mapset((uchar *)";$&|", 0); inp->promptmode=1; rcode = parse_stream(&temp, &ctx, inp, flag & FLAG_CONT_ON_NEWLINE ? -1 : 'n'); if (rcode == 1) flag_repeat = 0; if (rcode != 1 && ctx.old_flag != 0) { syntax(); flag_repeat = 0; } if (rcode != 1 && ctx.old_flag == 0) { done_word(&temp, &ctx); done_pipe(&ctx,PIPE_SEQ); code = run_list(ctx.list_head); if (code == -2) { /* exit */ b_free(&temp); code = 0; /* XXX hackish way to not allow exit from main loop */ if (inp->peek == file_peek) { printf("exit not allowed from main input shell.n"); continue; } break; } if (code == -1) flag_repeat = 0; } else { if (ctx.old_flag != 0) { free(ctx.stack); b_reset(&temp); } if (inp->__promptme == 0) printf("<INTERRUPT>n"); inp->__promptme = 1; temp.nonnull = 0; temp.quote = 0; inp->p = NULL; free_pipe_list(ctx.list_head,0); } b_free(&temp); /* loop on syntax errors, return on EOF */ } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) && (inp->peek != static_peek || b_peek(inp))); return (code != 0) ? 1 : 0; }
部分FLAG宏定义如下:
#define FLAG_EXIT_FROM_LOOP 1 #define FLAG_PARSE_SEMICOLON (1 << 1) /* symbol ';' is special for parser */ #define FLAG_REPARSING (1 << 2) /* >=2nd pass */ #define FLAG_CONT_ON_NEWLINE (1 << 3) /* continue when we see n */
parse_stream_outer函数就是 hush shell的命令解释器,使用do-while循环接收命令行输入,然后利用函数parse_stream函数解析,调用run_list函数在经过一系列函数调用cmd_process函数来处理命令。
在将命令的处理之前,先看几个重要的数据结构:
/* This holds pointers to the various results of parsing */ struct p_context { struct child_prog *child; struct pipe *list_head; struct pipe *pipe; reserved_style w; int old_flag; /* for figuring out valid reserved words */ struct p_context *stack; int type; /* define type of parser : ";$" common or special symbol */ /* How about quoting status? */ };
该数据结构变量是一个命令索引,关键的就是其中的两个成员,struct pipe *list_head和struct pipe *pipe;
再来看数据结构struct pipe,这个结构其实就是一个链表节点的结构:
struct pipe { int num_progs; /* total number of programs in job */ struct child_prog *progs; /* array of commands in pipe */ struct pipe *next; /* to track background commands */ pipe_style followup; /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */ reserved_style r_mode; /* supports if, for, while, until */ };
该数据结构用来存储用户的输入命令的,命令存储在成员变量struct child_prog* progs里面,其中的next用来指向下一个struct pipe数据结构变量(当一次输入多个命令时用到);
struct child_prog { char **argv; /* program name and arguments */ /* was quoted when parsed; copy of struct o_string.nonnull field */ int *argv_nonnull; int argc; /* number of program arguments */ struct pipe *group; /* if non-NULL, first in group or subshell */ int sp; /* number of SPECIAL_VAR_SYMBOL */ int type; };
该数据结构中:
- char ** argv就是存放命令的地方,比如一个命令由若干个单词组成,cd /home,则argv[0]=cd、argv[1]=/home;
- argc表示该命令含有的参数的个数;
总的来说,他们的关系是:p_context的head_list成员变量指向第一个pipe结构变量(用来存储第一条命令),第一个pipe结构的next指向下一个pipe结构(用来存储下一条命令),以此类推,形成一个由命令组成的链表;
3.3 parse_stream解析命令行
parse_stream函数定义在common/cli_hush.c:
/* return code is 0 for normal exit, 1 for syntax error */ static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *input, int end_trigger) { unsigned int ch, m; int next; /* Only double-quote state is handled in the state variable dest->quote. * A single-quote triggers a bypass of the main loop until its mate is * found. When recursing, quote state is passed in via dest->quote. */ debug_printf("parse_stream, end_trigger=%dn",end_trigger); while ((ch=b_getch(input))!=EOF) { //EOF => ctrl + C m = map[ch]; if (input->__promptme == 0) return 1; next = (ch == 'n') ? 0 : b_peek(input); debug_printf("parse_stream: ch=%c (%d) m=%d quote=%d - %cn", ch >= ' ' ? ch : '.', ch, m, dest->quote, ctx->stack == NULL ? '*' : '.'); if (m==0 || ((m==1 || m==2) && dest->quote)) { b_addqchr(dest, ch, dest->quote); } else { if (m==2) { /* unquoted IFS */ if (done_word(dest, ctx)) { return 1; } /* If we aren't performing a substitution, treat a newline as a * command separator. */ if (end_trigger != '