Mini2440之uboot移植之源码分析命令解析(五)
看到这里的同学,恭喜你,马上就看完了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 read\n"); puts("doc/README.generic-board and take action. Boards not\n"); 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=%d\n\n", 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_CMDLINE\n"); #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=%d\n",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 - %c\n", 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 != '\0' && ch=='\n') // 最后执行到这里 done_pipe(ctx,PIPE_SEQ); } if (ch == end_trigger && !dest->quote && ctx->w==RES_NONE) { //执行完毕,返回 debug_printf("leaving parse_stream (triggered)\n"); return 0; } if (m!=2) switch (ch) { case '#': if (dest->length == 0 && !dest->quote) { while(ch=b_peek(input),ch!=EOF && ch!='\n') { b_getch(input); } } else { b_addqchr(dest, ch, dest->quote); } break; case '\\': if (next == EOF) { syntax(); return 1; } b_addqchr(dest, '\\', dest->quote); b_addqchr(dest, b_getch(input), dest->quote); break; case '$': if (handle_dollar(dest, ctx, input)!=0) return 1; break; case '\'': dest->nonnull = 1; while(ch=b_getch(input),ch!=EOF && ch!='\'') { if(input->__promptme == 0) return 1; b_addchr(dest,ch); } if (ch==EOF) { syntax(); return 1; } break; case '"': dest->nonnull = 1; dest->quote = !dest->quote; break; case ';': done_word(dest, ctx); done_pipe(ctx,PIPE_SEQ); break; case '&': done_word(dest, ctx); if (next=='&') { b_getch(input); done_pipe(ctx,PIPE_AND); } else { syntax_err(); return 1; } break; case '|': done_word(dest, ctx); if (next=='|') { b_getch(input); done_pipe(ctx,PIPE_OR); } else { /* we could pick up a file descriptor choice here * with redirect_opt_num(), but bash doesn't do it. * "echo foo 2| cat" yields "foo 2". */ syntax_err(); return 1; } break; case SUBSTED_VAR_SYMBOL: dest->nonnull = 1; while (ch = b_getch(input), ch != EOF && ch != SUBSTED_VAR_SYMBOL) { debug_printf("subst, pass=%d\n", ch); if (input->__promptme == 0) return 1; b_addchr(dest, ch); } debug_printf("subst, term=%d\n", ch); if (ch == EOF) { syntax(); return 1; } break; default: syntax(); /* this is really an internal logic error */ return 1; } } } /* complain if quote? No, maybe we just finished a command substitution * that was quoted. Example: * $ echo "`cat foo` plus more" * and we just got the EOF generated by the subshell that ran "cat foo" * The only real complaint is if we got an EOF when end_trigger != '\0', * that is, we were really supposed to get end_trigger, and never got * one before the EOF. Can't use the standard "syntax error" return code, * so that parse_stream_outer can distinguish the EOF and exit smoothly. */ debug_printf("leaving parse_stream (EOF)\n"); if (end_trigger != '\0') return -1; return 0; }
这里粗略介绍一下这段代码的流程:
- b_getch#get_user_input:获取用户输入的命令行,存储在input->p缓冲区中(需要注意的是用户键盘按下Enter表示一个命令输入完毕,按下Enter=回车符\r+换行符\n,发送的是0x0DH、0x0AH两个字符);
- 解析命令行中的每一个字符,处理特殊字符,比如${}、<、>、``等;
- 将解析到的命令保存到ctx中,一个命令行可能解析到若干个命令;
举个例子:
1、当用户输入的命令为cmd1时
- p_context.head_list->progs->argc= 1
- p_context.head_list->progs->argv[0]=cmd
2、 当用户输入的命令为cmd1 option时
- p_context.head_list->progs->argc= 2
- p_context.head_list->progs->argv[0]=cmd1
- p_context.head_list->progs->argv[1]=option
3、 当用户输入的命令为cmd1 option1; cmd2 option2时(;表示前后输入的是两条命令)
- p_context.head_list->progs->argc= 2
- p_context.head_list->progs->argv[0]=cmd1
- p_context.head_list->progs->argv[1]=option1
- p_context.head_list->next->progs->argc= 2
- p_context.head_list->next->progs->argv[0]=cmd2
- p_context.head_list->next->progs->argv[1]=option2
b_getch定义:
#define b_getch(input) ((input)->get(input))
执行input->get方法,在执行的代码中已经出初始化了input->get = file_get,所以这里相当于执行了file_get(input):
/* This is the magic location that prints prompts * and gets data back from the user */ static int file_get(struct in_str *i) { int ch; ch = 0; /* If there is data waiting, eat it up */ if (i->p && *i->p) { ch = *i->p++; } else { /* need to double check i->file because we might be doing something * more complicated by now, like sourcing or substituting. */ while(! i->p || strlen(i->p)==0 ) { get_user_input(i); } i->promptmode=2; if (i->p && *i->p) { ch = *i->p++; } debug_printf("b_getch: got a %d\n", ch); } return ch; }
其中input->p为const char *类型,这里利用get_user_input读取用户输入的一行数据写入console_buffer缓冲区中,这里如果读取到一行数据的呢,当读取到\r\n时,将会在console_buffer末尾写入\n\0,再把console_buffer复制到the_command。然后设置input->p=the_command。
如果始终没有用户输入the_command[0] = '\0',将会一直等待用户输入。当读取到数据后,file_get将会返回按字符逐个返回读取的字符串。
3.4 run_list
当用户的命令按照以上的方式存储之后,就进入到run_list函数,函数位于common/cli_hush.c:
/* Select which version we will use */ static int run_list(struct pipe *pi) { int rcode=0; rcode = run_list_real(pi); /* free_pipe_list has the side effect of clearing memory * In the long run that function can be merged with run_list_real, * but doing that now would hobble the debugging effort. */ free_pipe_list(pi,0); return rcode; }
p_context的head_list成员变量指向第一个pipe结构变量(用来存储第一条命令),第一个pipe结构的next指向下一个pipe结构(用来存储下一条命令),...,从而形成一个链表结构;
run_list(ctx.list_head)就是处理命令的入口函数,其中ctx是p_context结构变量,里面存储了用户所输入的命令,真正将处理落到实处的函数是run_list_real#run_pipe_real。
四、命令执行
4.1 run_list_real
run_list_real定义在common/cli_hush.c文件中,这个函数代码比较多:
static int run_list_real(struct pipe *pi) { ... for (; pi; pi = (flag_restore != 0) ? rpipe : pi->next) { //遍历每一个命令 ... rcode = run_pipe_real(pi); ... } ... }
由命令行解析可能得到若干条命令,保存在ctx->list_head链表中,这里遍历链表,执行每一条命令。
4.2 run_pipe_real
run_pipe_real函数位于common/cli_hush.c:
/* run_pipe_real() starts all the jobs, but doesn't wait for anything * to finish. See checkjobs(). * * return code is normally -1, when the caller has to wait for children * to finish to determine the exit status of the pipe. If the pipe * is a simple builtin command, however, the action is done by the * time run_pipe_real returns, and the exit code is provided as the * return value. * * The input of the pipe is always stdin, the output is always * stdout. The outpipe[] mechanism in BusyBox-0.48 lash is bogus, * because it tries to avoid running the command substitution in * subshell, when that is in fact necessary. The subshell process * now has its stdout directed to the input of the appropriate pipe, * so this routine is noticeably simpler. */ static int run_pipe_real(struct pipe *pi) { int i; int nextin; int flag = do_repeat ? CMD_FLAG_REPEAT : 0; struct child_prog *child; char *p; nextin = 0; /* Check if this is a simple builtin (not part of a pipe). * Builtins within pipes have to fork anyway, and are handled in * pseudo_exec. "echo foo | read bar" doesn't work on bash, either. */ if (pi->num_progs == 1) child = & (pi->progs[0]); //pi->progs存放实际命令 if (pi->num_progs == 1 && child->group) { int rcode; debug_printf("non-subshell grouping\n"); rcode = run_list_real(child->group); return rcode; } else if (pi->num_progs == 1 && pi->progs[0].argv != NULL) { for (i=0; is_assignment(child->argv[i]); i++) { /* nothing */ } if (i!=0 && child->argv[i]==NULL) { /* assignments, but no command: set the local environment */ for (i=0; child->argv[i]!=NULL; i++) { /* Ok, this case is tricky. We have to decide if this is a * local variable, or an already exported variable. If it is * already exported, we have to export the new value. If it is * not exported, we need only set this as a local variable. * This junk is all to decide whether or not to export this * variable. */ int export_me=0; char *name, *value; name = xstrdup(child->argv[i]); debug_printf("Local environment set: %s\n", name); value = strchr(name, '='); if (value) *value=0; free(name); p = insert_var_value(child->argv[i]); set_local_var(p, export_me); if (p != child->argv[i]) free(p); } return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ } for (i = 0; is_assignment(child->argv[i]); i++) { p = insert_var_value(child->argv[i]); set_local_var(p, 0); if (p != child->argv[i]) { child->sp--; free(p); } } if (child->sp) { char * str = NULL; str = make_string(child->argv + i, child->argv_nonnull + i); parse_string_outer(str, FLAG_EXIT_FROM_LOOP | FLAG_REPARSING); free(str); return last_return_code; } /* check ";", because ,example , argv consist from * "help;flinfo" must not execute */ if (strchr(child->argv[i], ';')) { printf("Unknown command '%s' - try 'help' or use " "'run' command\n", child->argv[i]); return -1; } /* Process the command */ return cmd_process(flag, child->argc, child->argv, &flag_repeat, NULL); } return -1; }
命令的执行实际在cmd_process函数中。
4.3 cmd_process
cmd_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; /* 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; } } /* 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; }
uboot中的每个命令都存放在.uboot_boot_list段中,每个命令都有一个名为do_xxx(xxx为具体的命令名)的函数,这个do_xxx函数就是具体的命令处理函数。
cmd_process通过调用find_cmd函数在命令表中找到指定的命令。
变量ticks用来记录命令的执行时间,repeatable为命令是否自动重复执行标志。这两个变量都将返回到上层的调用函数。函数cmd_call利用传入的参数,直接调用cmdtp->cmd,即:
(cmdtp->cmd)(cmdtp, flag, argc, argv);
最后,如果命令执行的返回值为CMD_RET_USAGE,代表命令执行出错,且置标CMD_RET_USAGE ,那么将调用cmd_usage,输出简短的命令使用帮助信息。
4.4 find_cmd
find_cmd函数位于common/command.c:
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); }
find_cmd函数的参数child->argv[i]通常情况下是child->argv[0],即认为整个命令的第一部分(第一个空格之前的字符)作为命令名称,其他的作为参数。它的作用就是到.u_boot_cmd段里面寻找child->argv[0],如果没找到,就返回NULL,并提示无效的命令;如果找到了,就将该命令以cmd_tbl_t结构变量的形式返回,这段代码具体流程:
- 在find_cmd函数中通过函数ll_entry_start得到数组的第一个元素,也就是命令表起始地址,类型为cmd_tbl_t(命令表是cmd_tlb_t结构体数组);
- 通过ll_entry_count得到数组长度,也就是命令表的长度;
- 最后通过find_cmd_tlb,从 start到 start+len的array进行遍历,在命令表中根据命令名找到所需的命令
其中ll_entry_start和ll_entry_count定义在include/linker_lists.h文件中:、

/** * ll_entry_start() - Point to first entry of linker-generated array * @_type: Data type of the entry * @_list: Name of the list in which this entry is placed * * This function returns (_type *) pointer to the very first entry of a * linker-generated array placed into subsection of .u_boot_list section * specified by _list argument. * * Since this macro defines an array start symbol, its leftmost index * must be 2 and its rightmost index must be 1. * * Example: * struct my_sub_cmd *msc = ll_entry_start(struct my_sub_cmd, cmd_sub); */ #define ll_entry_start(_type, _list) \ ({ \ static char start[0] __aligned(4) __attribute__((unused, \ section(".u_boot_list_2_"#_list"_1"))); \ (_type *)&start; \ }) /** * ll_entry_end() - Point after last entry of linker-generated array * @_type: Data type of the entry * @_list: Name of the list in which this entry is placed * (with underscores instead of dots) * * This function returns (_type *) pointer after the very last entry of * a linker-generated array placed into subsection of .u_boot_list * section specified by _list argument. * * Since this macro defines an array end symbol, its leftmost index * must be 2 and its rightmost index must be 3. * * Example: * struct my_sub_cmd *msc = ll_entry_end(struct my_sub_cmd, cmd_sub); */ #define ll_entry_end(_type, _list) \ ({ \ static char end[0] __aligned(4) __attribute__((unused, \ section(".u_boot_list_2_"#_list"_3"))); \ (_type *)&end; \ }) /** * ll_entry_count() - Return the number of elements in linker-generated array * @_type: Data type of the entry * @_list: Name of the list of which the number of elements is computed * * This function returns the number of elements of a linker-generated array * placed into subsection of .u_boot_list section specified by _list * argument. The result is of an unsigned int type. * * Example: * int i; * const unsigned int count = ll_entry_count(struct my_sub_cmd, cmd_sub); * struct my_sub_cmd *msc = ll_entry_start(struct my_sub_cmd, cmd_sub); * for (i = 0; i < count; i++, msc++) * printf("Entry %i, x=%i y=%i\n", i, msc->x, msc->y); */ #define ll_entry_count(_type, _list) \ ({ \ _type *start = ll_entry_start(_type, _list); \ _type *end = ll_entry_end(_type, _list); \ unsigned int _ll_result = end - start; \ _ll_result; \ })
这里使用到了gcc __attribute__((section("section_name"))) ,gcc的__attribute__编译属性有很多子项,用于改变作用对象的特性。这里简单介绍一下section子项的作用。
__attribute__的section子项使用方式为:
__attribute__((section("section_name")))
其作用是将作用的函数或数据放入指定名为"section_name"的段。
我们再来看ll_entrt_start中的如下代码:
static char start[0] __aligned(4) __attribute__((unused, section(".u_boot_list_2_"#_list"_1")));
定义一个包含0个字节的数组start[0],且把它放在.u_boot_list_2_cmd_1段中,该段属性为unsued。这里简单介绍一下为什么会替换成u_boot_list_2_cmd_1:
#include <stdio.h> #define ll_entry_start(_list) "u_boot_list_2_"#_list"_1" //#_list会将_list替换成实际值,并加上"" int main(void) { printf(ll_entry_start(cmd)); return 0; }
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了