函数main_loop和u-boot命令执行

来源:互联网 发布:北京大学安金鹏 知乎 编辑:程序博客网 时间:2024/06/05 11:58
一.main_loop函数执行流程和命令解释器
run_main_loop是board_r中函数运行列表init_fnc_t init_sequence_r[]最后一个函数,它又调用了main_loop,且run_main_loop永不返回。
static int run_main_loop(void){    /* main_loop() can return to retry autoboot, if so just run it again */    for (;;)        main_loop();    return 0;}
main_loop定义在common/main.c中:
void main_loop(void){    const char *s;    bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");    modem_init();#ifdef CONFIG_VERSION_VARIABLE    setenv("ver", version_string);  /* set version variable */#endif /* CONFIG_VERSION_VARIABLE */    cli_init();    run_preboot_environment_command();    s = bootdelay_process();    if (cli_process_fdt(&s))        cli_secure_boot_cmd(s);    autoboot_command(s);    cli_loop();}
bootstage_mark_name函数调用了show_boot_progress,利用它显示启动进程(progress),此处为空函数。
setenv设置环境变量ver为version_string,后者在common/cmd_version.c中定义为:
const char __weak version_string[] = U_BOOT_VERSION_STRING;

U_BOOT_VERSION_STRING在version.h中定义为:

#define U_BOOT_VERSION_STRING U_BOOT_VERSION " (" U_BOOT_DATE " - " \    U_BOOT_TIME " " U_BOOT_TZ ")" CONFIG_IDENT_STRING
其中U_BOOT_VERSION ,U_BOOT_DATE,U_BOOT_TIME,U_BOOT_TZ均由u-boot构建系统自动产生,
它们分别代表u-boot版本号,编译日期和时间,以及时间区。
如果定义了CONFIG_SYS_HUSH_PARSER,那么配置u-boot使用hush shell来作为执行器。hush shell是一种轻量型的shell。
cli_init用来初始化hush shell使用的一些变量。hush shell的实现机制比较复杂,以下的hush shell相关实现代码都不做详尽跟踪分析。
有兴趣的可参见源代码和相关的网络文章。

run_preboot_environment_command函数从环境变量中获取"preboot"的定义,该变量包含了一些预启动命令,
一般环境变量中不包含该项配置。
bootdelay_process从环境变量中取出"bootdelay"和"bootcmd"的配置值,将取出的"bootdelay"配置值转换成整数,
赋值给全局变量stored_bootdelay,最后返回"bootcmd"的配置值。bootdelay为u-boot的启动延时计数值,计数期间内
如无用户按键输入干预,那么将执行"bootcmd"配置中的命令。
由于没有定义CONFIG_OF_CONTROL,函数cli_process_fdt返回false,接下来执行autoboot_command,
该函数在common/autoboot.c中实现:

void autoboot_command(const char *s){    if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {        run_command_list(s, -1, 0);    }}
全局变量stored_bootdelay在上面已做说明。静态函数abortboot中包含了CONFIG_AUTOBOOT_KEYED宏预处理分支,该宏定义用来使能用户名密码登录,这里它没有定义,而后调用了abortboot_normal,在执行的时间stored_bootdelay(秒)内,如无用户按键输入干预,那么abortboot_normal函数将返回0,否则返回1。 当无用户按键干预时,接下来将调用run_command_list执行上述从环境变量中读取的"bootcmd"配置值。注意该函数的参数s。run_command_list中调用了hush shell的命令解释器(parse_stream_outer函数),解释bootcmd中的启动命令。环境变量bootcmd中的启动命令,用来设置linux必要的启动环境,然后加载和启动linux内核。u-boot启动linux内核后,将控制权交给linux内核,至此不再返回。
如用户在设定的bootdelay内无按键输入,那么将运行cli_loop执行hush shell命令解释器:
void cli_loop(void){    parse_file_outer();    /* This point is never reached */    for (;;);}
parse_file_outer进行必要的初始化后,也将调用hush shell的命令解释器,即parse_stream_outer函数:
static int parse_stream_outer(structin_str*inp,intflag){    do {        ...        ...        run_list(...);    } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&  //#define FLAG_EXIT_FROM_LOOP 1            (inp->peek != static_peek || b_peek(inp)));}
上面的do-while会循环命令解析器的"命令输入解析--执行"运行模式。
其中的函数run_list执行如下的函数调用流程:
run_list-->run_list_real-->run_pipe_real
最后在函数run_pipe_real中有:
return cmd_process(...);
函数cmd_process最后完成u-boot命令的定位和执行。
二.u-boot命令执行
命令处理函数均在common/command.c中实现,上述函数cmd_process定义如下:
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 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;}
u-boot中使用宏U_BOOT_CMD来定义命令,该宏在include/command.h中定义如下:
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)      \    U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
U_BOOT_CMD是宏U_BOOT_CMD_COMPLETE最后一个参数_comp为NULL的特例,_comp表示变量是否自动完成:
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \    ll_entry_declare(cmd_tbl_t, _name, cmd) =           \  /*注意这里是cmd而非_cmd*/        U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,  \                        _usage, _help, _comp);
其中包含的宏U_BOOT_CMD_MKENT_COMPLETE定义为:
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,      \                _usage, _help, _comp)           \        { #_name, _maxargs, _rep, _cmd, _usage,         \            _CMD_HELP(_help) _CMD_COMPLETE(_comp) }

上面的_CMD_HELP根据配置可选为使用完整或简短帮助说明。_CMD_COMPLETE则根据配置决定是否使用自动完成函数。U_BOOT_CMD_MKENT_COMPLETE宏其实是组织输入参数,对ll_entry_declare进行数据填充。ll_entry_declare在文件include/linker_lists.h中定义:

#define ll_entry_declare(_type, _name, _list)               \    _type _u_boot_list_2_##_list##_2_##_name __aligned(4)       \            __attribute__((unused,              \            section(".u_boot_list_2_"#_list"_2_"#_name)))
参数_type为cmd_tbl_t,这里定义一个cmd_tbl_t结构体,并把它放在符号段.u_boot_list_2_"#_list"_2_"#_name中,其中的_list和_name根据宏参数进行字符串替换。
下面,我们举例说明上述宏的实现机制。比如有如下的定义:
U_BOOT_CMD(    env, CONFIG_SYS_MAXARGS, 1, do_env,    "environment handling commands", env_help_text);    

U_BOOT_CMD_COMPLETE (    env, CONFIG_SYS_MAXARGS, 1, do_env,    "environment handling commands", env_help_text,NULL);
带入宏参及其展开为 :
U_BOOT_CMD_COMPLETE(env, CONFIG_SYS_MAXARGS , 1, do_env , "environment handling commands" , env_help_text , NULL ) \    ll_entry_declare(cmd_tbl_t, env , cmd) =           \        U_BOOT_CMD_MKENT_COMPLETE(env , CONFIG_SYS_MAXARGS , 1, do_env , "environment handling commands" , env_help_text , NULL);
其中的ll_entry_declare带入宏参及其展开为 :
ll_entry_declare(cmd_tbl_t , env , cmd )            \    cmd_tbl_t _u_boot_list_2_cmd_2_env __aligned(4)       \            __attribute__((unused,              \            section(".u_boot_list_2_cmd_2_env )))
其中的U_BOOT_CMD_MKENT_COMPLETE带入宏参及其展开为:
U_BOOT_CMD_MKENT_COMPLETE(env , CONFIG_SYS_MAXARGS , 1, do_env , _usage, _help, _comp)     \        { "env", CONFIG_SYS_MAXARGS , 1, do_env , "environment handling commands" , env_help_text ,NULL}
那么上述U_BOOT_CMD_COMPLETE最终展开为:
cmd_tbl_t _u_boot_list_2_cmd_2_env __aligned(4)       \            __attribute__((unused, section(".u_boot_list_2_cmd_2_env ))) ={ "env", CONFIG_SYS_MAXARGS , 1, do_env , "environment handling commands" , env_help_text ,NULL}
其中的cmd_tbl_t定义为:
<pre code_snippet_id="1890747" snippet_file_name="blog_20160921_20_3676404" name="code" class="cpp">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 * const []);    char        *usage;     /* Usage message    (short) */#ifdef  CONFIG_SYS_LONGHELP    char        *help;      /* Help  message    (long)  */#endif#ifdef CONFIG_AUTO_COMPLETE    /* do auto completion on the arguments */    int     (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);#endif};
typedef struct cmd_tbl_s    cmd_tbl_t;

该结构体包含了命令名,命令实现函数,命令使用简短说明usage的输出字符串,帮助回调函数,参数变量自动完成函数等。u-boot使用该结构体来描述一个完整的命令。

U_BOOT_CMD_COMPLETE宏用来定义一个cmd_tbl_t结构体变量,初始化该结构体中的相应成员,并把该结构体变量存放在4字节对齐的.u_boot_list_2_cmd_2_env符号段中。如前所述,宏U_BOOT_CMD将最后一个参数_comp置为NULL,对U_BOOT_CMD_COMPLETE做了进一步的封装。所有使用U_BOOT_CMD和U_BOOT_CMD_COMPLETE定义的命令最后都集中放在以.u_boot_list_2_cmd_2开头的符号段中。即.u_boot_list_2_cmd_2_##name,这里的name是命令名。

我们回到函数上述的命令处理函数cmd_process,被其调用的find_cmd将根据命令名查找相应的cmd_tbl_t变量符号段,其实现如下:

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);}
ll_entry_start定义如下:
#define ll_entry_start(_type, _list)                    \({                                  \    static char start[0] __aligned(4) __attribute__((unused,    \        section(".u_boot_list_2_"#_list"_1")));         \    (_type *)&start;                        \})
那么,在上述函数find_cmd中,语句  
cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
定义一个包含0个字节的数组start[0],且把它放在.u_boot_cmd_2_list_1段中,该段属性为unsued。注意在u-boot.lds中有:
 .u_boot_list : {  KEEP(*(SORT(.u_boot_list*)));    
.u_boot_list中所有符号是按字符表的先后顺序排列的,.u_boot_list_2_list_1会放在所有使用U_BOOT_CMD和U_BOOT_CMD_COMPLETE
定义的符号段的最前面,即.u_boot_cmd_2_list_1为以.u_boot_list_2_cmd_2开头的符号段中的第一个。它定义为0个字节的数组start[0],
编译器并不为它分配存储空间,那么它将指向以.u_boot_list_2_cmd_2开头的符号段中的第一个符号。
同理ll_entry_end用end[0]来标识.u_boot_list_2_cmd_2_xxx段的结尾,接下来的函数ll_entry_count返回的就是start - end的值,
即符号段.u_boot_list_2_cmd_2_xxx总字节长度。然后调用find_cmd_tbl,根据传入的.u_boot_list_2_cmd_2_xxx段的首地址和
函数ll_entry_count 返回的长度,根据命令名查找相应的符号段,即相命令对应的cmd_tbl_t结构体变量,然后返回该结构体指针。
find_cmd_tbl的实现如下:
cmd_tbl_t *find_cmd_tbl(const char *cmd, cmd_tbl_t *table, int table_len){    cmd_tbl_t *cmdtp;    cmd_tbl_t *cmdtp_temp = table;  /* Init value */    const char *p;    int len;    int n_found = 0;    if (!cmd)        return NULL;     len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);    for (cmdtp = table; cmdtp != table + table_len; 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 */}
查找到命令名对应的cmd_tbl_t结构体变量后,cmd_process接下来将调用函数cmd_call执行cmd_tbl_t中的命令。
cmd_process中相应的代码段如下:
  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);
变量ticks用来记录命令的执行时间,repeatable为命令是否自动重复执行标志。这两个变量都将返回到上层的调用函数。
函数cmd_call利用传入的参数,直接调用cmdtp->cmd,即:
 (cmdtp->cmd)(cmdtp, flag, argc, argv);
最后,如果命令执行的返回值为CMD_RET_USAGE,代表命令执行出错,且置标CMD_RET_USAGE ,那么将调用cmd_usage,
输出简短的命令使用帮助信息。cmd_usage实现如下:
int cmd_usage(const cmd_tbl_t *cmdtp){    printf("%s - %s\n\n", cmdtp->name, cmdtp->usage);#ifdef  CONFIG_SYS_LONGHELP    printf("Usage:\n%s ", cmdtp->name);    if (!cmdtp->help) {        puts ("- No additional help available.\n");        return 1;    }    puts(cmdtp->help);    putc('\n');#endif  /* CONFIG_SYS_LONGHELP */    return 1;}
三.u-boot中的子命令
部分u-boot的命令包含子命令,如env命令,它由子命令save,set,edit等组成。类似的还有sf命令。这些主命令执行时必须指定子命令。
u-boot中子命令的实现不再使用上面的gcc关键字section来指定段,只是直接定义了一个cmd_tbl_t表,并使用子命令及其运行参数初始化该表。对于上述讨论中使用U_BOOT_CMD定义的命令,它们是散放在文件各处的,很难用一个全局的cmd_tbl_t表将这些命令统一定义初始化。而子命令不同,它只定义在一个或少量文件中,该cmd_tbl_t表为static类型,可以在定义时直接填充。当然,主命令还是由宏U_BOOT_CMD来定义引入。

0 0
原创粉丝点击