程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

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;                        \
    })
View Code
复制代码

这里使用到了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-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
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-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(880)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示