[uboot] (第六章)uboot流程——命令行模式以及命令处理介绍
来源:互联网 发布:防攻击软件 编辑:程序博客网 时间:2024/06/11 16:00
以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为例
[uboot] uboot流程系列:
[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)
[project X] tiny210(s5pv210)从存储设备加载代码到DDR
[uboot] (第一章)uboot流程——概述
[uboot] (第二章)uboot流程——uboot-spl编译流程
[uboot] (第三章)uboot流程——uboot-spl代码流程
[uboot] (第四章)uboot流程——uboot编译流程
[uboot] (第五章)uboot流程——uboot启动流程
[uboot] (番外篇)global_data介绍
[uboot] (番外篇)uboot relocation介绍
建议先看《[uboot] (第五章)uboot流程——uboot启动流程》
=================================================================================
一、说明
命令行模式就是指uboot执行完一切必要的初始化过程之后,等待终端输入命令和处理命令的一个模式。
所以后面的章节,我们先介绍命令如何存储以及处理,再简单说明命令行模式是如何工作的
1、需要打开哪些宏
CONFIG_CMDLINE
表示是否支持命令行模式,定义如下:
./configs/bubblegum_defconfig:201:CONFIG_CMDLINE=y
./configs/tiny210_defconfig:202:CONFIG_CMDLINE=yCONFIG_SYS_GENERIC_BOARD
用于定义板子为通用类型的板子。打开这个宏之后,common/board_f.c和common/board_r.c才会被编译进去,否则,需要自己实现。
./configs/bubblegum_defconfig:7:CONFIG_SYS_GENERIC_BOARD=y
./configs/tiny210_defconfig:7:CONFIG_SYS_GENERIC_BOARD=y
打开之后,board_r.c中最终会执行run_main_loop进入命令行模式。具体参考《[uboot] (第五章)uboot流程——uboot启动流程》。CONFIG_SYS_PROMPT
命令行模式下的提示符。在tiny210中定义如下:
./configs/tiny210_defconfig:203:CONFIG_SYS_PROMPT=”TINY210 => “CONFIG_SYS_HUSH_PARSER
表示使用使用hush来对命令行进行解析。后续会继续说明。在tiny210中定义如下:
./include/configs/tiny210.h:121:#define CONFIG_SYS_HUSH_PARSER /* use “hush” command parser */对应命令需要打开对应命令的宏
以bootm命令为例,如果要支持bootm,则需要打开CONFIG_CMD_BOOTM宏,具体可以参考cmd/Makefile
./configs/bubblegum_defconfig:226:CONFIG_CMD_BOOTM=y
./configs/tiny210_defconfig:226:CONFIG_CMD_BOOTM=y
2、结合以下几个问题来看后面的章节
- 命令的数据结构,也就是代码里面如何表示一个命令?
- 如何定义一个命令,我们如何添加一个自己的命令?
- 命令的存放和获取?
- 命令行模式的处理流程?
3、API
- U_BOOT_CMD
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)
定义一个命令。 - cmd_process
enum command_ret_t cmd_process(int flag, int argc, char * const argv[], int *repeatable, ulong *ticks)
命令的处理函数,命令是作为argv[0]传入。
具体参数意义和实现参考后面。
二、命令处理数据结构的存放
1、数据结构
uboot把所有命令的数据结构都放在一个表格中,我们后续称之为命令表。表中的每一项代表着一个命令,其项的类型是cmd_tbl_t。
数据结构如下:
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};typedef struct cmd_tbl_s cmd_tbl_t;
参数说明如下:
- name:定义一个命令的名字。 其实就是执行的命令的字符串。这个要注意。
- maxargs:这个命令支持的最大参数
- repeatable:是否需要重复
- cmd:命令处理函数的地址
- usage:字符串,使用说明
- help:字符串,帮助
2、在dump里面的表示
通过以下命令解析出dump。
arm-none-linux-gnueabi-objdump -D u-boot > uboot_objdump.txt
以bootm命令为例,提取一部分信息,加上了注释信息:
23e364cc <_u_boot_list_2_cmd_2_bootm>: // bootm命令对应的数据结构符号是_u_boot_list_2_cmd_2_bootm,后续我们会说明,其起始地址是0x23e364cc23e364cc: 23e2bbc2 mvncs fp, #198656 ; 0x30800// 这里对应第一个成员name,其地址是0x23e2bbc223e364d0: 00000040 andeq r0, r0, r0, asr #32// 这里对应第二个成员maxargs,maxargs=0x4023e364d4: 00000001 andeq r0, r0, r1 // 这里对应第三个成员repeatable,repeatable=123e364d8: 23e02028 mvncs r2, #40 ; 0x28 // 这里对应第四个成员cmd,cmd命令处理函数的地址是0x23e02028,和下面的do_bootm的地址一致!!!23e364dc: 23e2bbc8 mvncs fp, #204800 ; 0x32000// 这里对应第五个成员usage,usage字符串的地址是0x23e2bbc823e364e0: 23e34cb0 mvncs r4, #45056 ; 0xb000// 这里对应第六个成员help,help字符串的地址是0x23e34cb023e02028 <do_bootm>: 23e34cb0 <bootm_help_text>:
根据上述dump就可以把bootm命令的数据结构定义都找出来了。
3、命令数据结构在u-boot.map符号表中的位置定义
通过查看u-boot.map,过滤出和u_boot_list中cmd相关的部分,对应符号表如下:
*(SORT(.u_boot_list*)) .u_boot_list_2_cmd_1 0x23e3649c 0x0 cmd/built-in.o .u_boot_list_2_cmd_1 0x23e3649c 0x0 common/built-in.o .u_boot_list_2_cmd_2_bootefi 0x23e3649c 0x18 cmd/built-in.o 0x23e3649c _u_boot_list_2_cmd_2_bootefi .u_boot_list_2_cmd_2_bootelf 0x23e364b4 0x18 cmd/built-in.o 0x23e364b4 _u_boot_list_2_cmd_2_bootelf .u_boot_list_2_cmd_2_bootm 0x23e364cc 0x18 cmd/built-in.o 0x23e364cc _u_boot_list_2_cmd_2_bootm...... .u_boot_list_2_cmd_2_true 0x23e3670c 0x18 cmd/built-in.o 0x23e3670c _u_boot_list_2_cmd_2_true .u_boot_list_2_cmd_2_version 0x23e36724 0x18 cmd/built-in.o 0x23e36724 _u_boot_list_2_cmd_2_version .u_boot_list_2_cmd_3 0x23e3673c 0x0 cmd/built-in.o .u_boot_list_2_cmd_3 0x23e3673c 0x0 common/built-in.o
可以观察到命令表是被定义在0x23e3649c( .u_boot_list_2_cmd_1)到0x23e3673c( .u_boot_list_2_cmd_3)的位置中。
并且每一个项占用了24个字节,和cmd_tbl_t结构的大小是一致的。
注意,根据注释,.u_boot_list_2_cmd_1和.u_boot_list_2_cmd_3这两个符号是由链接器自己生成的。
这里简单有个印象,bootm命令对应的数据结构符号是u_boot_list_2_cmd_2_bootm
4、如何定义一个命令
(1)我们以bootm命令的定义为例:
cmd/bootm.c中
U_BOOT_CMD( bootm, CONFIG_SYS_MAXARGS, 1, do_bootm, "boot application image from memory", bootm_help_text);// bootm就是我们的命令字符串// 在tiny210.h中定义了最大参数数量是64// #define CONFIG_SYS_MAXARGS 64 /* max number of command args */// 1表示重复一次// 对应命令处理函数是do_bootm// usage字符串是"boot application image from memory"// help字符串是bootm_help_text定义的字符串。
通过上面,可以看出是通过U_BOOT_CMD来定义了bootm命令对应的数据结构!!!
并且命令处理函数的格式如下:
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
当命令处理函数执行成功时,需要返回0.返回非0值
所以可以参照如上方式自己定义一个命令。
5、介绍一下U_BOOT_CMD的实现
include/common.h
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \ U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \ ll_entry_declare(cmd_tbl_t, _name, cmd) = \ U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp);#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp) \ { #_name, _maxargs, _rep, _cmd, _usage, \ _CMD_HELP(_help) _CMD_COMPLETE(_comp) }#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))) ll_entry_declare(cmd_tbl_t, _name, cmd)// 以bootm为例// _type=cmd_tbl_t// _name=bootm// _list=cmd// 这里最终会转化为如下数据结构// // cmd_tbl_t _u_boot_list_2_cmd_2_bootm=// {// _name=bootm,// _maxargs=CONFIG_SYS_MAXARGS,// _rep=1,// _cmd=do_bootm,// _usage="boot application image from memory",// _help=bootm_help_text,// _comp=NULL,// }// 并且这个数据结构给存放到了 .u_boot_list_2_cmd_2_bootm段中!!!和我们上述的完全一致。
三、命令的处理
1、简单流程说明:
假设传进来的命令是cmd。
* 获取命令表
* 从命令表中搜索和cmd匹配的项
* 执行对应项中的命令
后续我们我们分成“查找cmd对应的表项”、“执行对应表项中的命令”两部分进行说明
2、查找cmd对应的表项——find_cmd
通过find_cmd可以获取命令对应的命令表项cmd_tbl_t 。
(1)原理简单说明
前面我们知道了可以观察到命令表是被定义在 .u_boot_list_2_cmd_1到.u_boot_list_2_cmd_3的位置中。所以我们从这个区间获取命令表。
并且根据表项中的name是否和传进来的命令是否匹配来判断是否是我们需要的表项。
(2)对应代码
common/command.c
cmd_tbl_t *find_cmd(const char *cmd){ cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);// 获取命令表的地址,start表示指向命令表的指针,具体实现看后面 const int len = ll_entry_count(cmd_tbl_t, cmd);// 获取命令表的长度,具体实现看后面 return find_cmd_tbl(cmd, start, len);// 以命令表的指针和命令表的长度为参数,查找和cmd匹配的表项,也就是cmd_tbl_t结构,并返回给调用者。}/* find command table entry for a command */cmd_tbl_t *find_cmd_tbl(const char *cmd, cmd_tbl_t *table, int table_len){#ifdef CONFIG_CMDLINE 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; /* * Some commands allow length modifiers (like "cp.b"); * compare command name only until first dot. */ len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd); for (cmdtp = table; cmdtp != table + table_len; cmdtp++) {// 通过指针递增的方式,查找table中的每一个cmd_tbl_t if (strncmp(cmd, cmdtp->name, len) == 0) { if (len == strlen(cmdtp->name)) return cmdtp; /* full match */// 如果是命令字符串和表项中的name完全匹配,包括长度一致的,则直接返回 cmdtp_temp = cmdtp; /* abbreviated command ? */ n_found++;// 如果命令字符串和表项中的name的前面部分匹配的话(我们称为部分匹配),暂时保存下来,并且记录有几个这样的表项,主要是为了支持自动补全的功能 } } if (n_found == 1) { /* exactly one match */ return cmdtp_temp;// 如果部分匹配的表项是唯一的话,则可以将这个表项返回,主要是为了支持自动补全的功能 }#endif /* CONFIG_CMDLINE */ return NULL; /* not found or ambiguous command */}
include/linker_lists.h
#define ll_entry_start(_type, _list) \({ \ static char start[0] __aligned(4) __attribute__((unused, \ section(".u_boot_list_2_"#_list"_1"))); \ (_type *)&start; \})// 因为传进来的_list是cmd,所以这里就是获取命令表的起始地址,也就是.u_boot_list_2_cmd_1的地址,#define ll_entry_end(_type, _list) \({ \ static char end[0] __aligned(4) __attribute__((unused, \ section(".u_boot_list_2_"#_list"_3"))); \ (_type *)&end; \})// 因为传进来的_list是cmd,所以这里就是获取命令表的结束地址,也就是.u_boot_list_2_cmd_3的地址,#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; \ })// 因为传进来的_list是cmd,所以这里就是计算命令表的长度
3、执行对应表项中的命令——cmd_call
通过调用cmd_call可以执行命令表项cmd_tbl_t 中的命令
common/command.c
static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]){ int result; result = (cmdtp->cmd)(cmdtp, flag, argc, argv);// 直接执行命令表项cmd_tbl_t 中的cmd命令处理函数 if (result) debug("Command failed, result=%d\n", result);// 命令返回非0值时,报错 return result;}
4、命令处理函数——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]); // 第一个参数argv[0]表示命令,调用find_cmd获取命令对应的表项cmd_tbl_t。 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); // 调用cmd_call执行命令表项中的命令,成功的话需要返回0值 if (ticks) *ticks = get_timer(*ticks); // 判断命令执行的时间 *repeatable &= cmdtp->repeatable; // 这个命令执行的重复次数存放在repeatable中的 } if (rc == CMD_RET_USAGE) rc = cmd_usage(cmdtp); // 命令格式有问题,打印帮助信息 return rc;}
返回0表示执行成功,返回非0值表示执行失败。
后续需要执行一个命令的时候,直接调用cmd_process即可。
四、命令行模式的流程
命令行模式有两种简单的方式。正常模式是简单地获取串口数据、解析和处理命令。
hush模式则是指命令的接收和解析使用busybox的hush工具,对应代码是hush.c。
关于hush模式的作用和使用自己还不是很清楚,还要再研究一下。这里简单的写一点流程。
1、入口
通过《[uboot] (第五章)uboot流程——uboot启动流程》,我们知道了uboot在执行完所有初始化程序之后,调用run_main_loop进入主循环。
通过主循环进入了命令行模式。
common/board_r.c
static int run_main_loop(void){ /* main_loop() can return to retry autoboot, if so just run it again */ for (;;) main_loop();// 这里进入了主循环,而autoboot也是在主循环里面实现 return 0;}
main_loop实现如下:
这里了解一个缩写,cli,Command Line Interface,命令行接口,命令行界面。
common/main.c
void main_loop(void){ const char *s; bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");// 这里用于标记uboot的进度,对于tiny210来说起始什么都没做 cli_init();// cli的初始化,主要是hush模式下的初始化 run_preboot_environment_command();// preboot相关的东西,后续有用到再说明 s = bootdelay_process(); if (cli_process_fdt(&s)) cli_secure_boot_cmd(s); autoboot_command(s);// autoboot的东西,后续使用autoboot的时候再专门说明 cli_loop();// 进入cli的循环模式,也就是命令行模式 panic("No CLI available");}
通过调用cli_loop进入了命令行模式,并且不允许返回。
common/cli.c
void cli_loop(void){#ifdef CONFIG_SYS_HUSH_PARSER parse_file_outer();// 这里进入hush命令模式 /* This point is never reached */ for (;;);#elif defined(CONFIG_CMDLINE)// 这里进入通用命令行模式 cli_simple_loop();#else// 说明没有打开CONFIG_CMDLINE宏,无法进入命令行模式,直接打印一个提示 printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");#endif /*CONFIG_SYS_HUSH_PARSER*/}
最终通过cli_simple_loop进入通用命令行模式,或者通过parse_file_outer进入hush命令行模式。
因为通用命令行模式相对较为简单,所以这边先说明通用命令行模式。
2、通用命令行模式
代码如下:
void cli_simple_loop(void){ static char lastcommand[CONFIG_SYS_CBSIZE + 1] = { 0, }; int len; int flag; int rc = 1; for (;;) { len = cli_readline(CONFIG_SYS_PROMPT);// 打印CONFIG_SYS_PROMPT,然后从串口读取一行作为命令,存储在console_buffer中// 在tiny210中定义如下:./configs/tiny210_defconfig:203:CONFIG_SYS_PROMPT="TINY210 => " flag = 0; /* assume no special flags for now */ if (len > 0) strlcpy(lastcommand, console_buffer, CONFIG_SYS_CBSIZE + 1);// 如果获得了一个新行时,命令会存储在console_buffer,将命令复制到lastcommand中 else if (len == 0) flag |= CMD_FLAG_REPEAT;// 只是得到一个单纯的换行符时,设置重复标识,后续重复执行上一次命令 if (len == -1) puts("<INTERRUPT>\n");// 获得非数据值时,直接打印中断 else rc = run_command_repeatable(lastcommand, flag);// 否则,执行lastcommand中的命令 if (rc <= 0) { /* invalid command or not repeatable, forget it */ lastcommand[0] = 0;// 命令执行出错时,清空lastcommand,防止下一次重复执行这个命令 } }}/* run_command_repeatable实现如下 */int run_command_repeatable(const char *cmd, int flag){ return cli_simple_run_command(cmd, flag);}/* cli_simple_run_command实现如下 */int cli_simple_run_command(const char *cmd, int flag){ char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd */ char *token; /* start of token in cmdbuf */ char *sep; /* end of token (separator) in cmdbuf */ char finaltoken[CONFIG_SYS_CBSIZE]; char *str = cmdbuf; char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */ int argc, inquotes; int repeatable = 1; int rc = 0; debug_parser("[RUN_COMMAND] cmd[%p]=\"", cmd); if (DEBUG_PARSER) { /* use puts - string may be loooong */ puts(cmd ? cmd : "NULL"); puts("\"\n"); } clear_ctrlc(); /* forget any previous Control C */ if (!cmd || !*cmd) return -1; /* empty command */ if (strlen(cmd) >= CONFIG_SYS_CBSIZE) { puts("## Command too long!\n"); return -1; } strcpy(cmdbuf, cmd); /* Process separators and check for invalid * repeatable commands */ debug_parser("[PROCESS_SEPARATORS] %s\n", cmd); while (*str) {// 这里过滤掉一些对命令进行处理的部分代码 /* find macros in this token and replace them */ cli_simple_process_macros(token, finaltoken); /* Extract arguments */ argc = cli_simple_parse_line(finaltoken, argv);// 对命令进行加工处理,转化成argv和argc格式。 if (argc == 0) { rc = -1; /* no command at all */ continue; } if (cmd_process(flag, argc, argv, &repeatable, NULL)) rc = -1;// 调用cmd_process对命令进行处理// 关于这个的实现我们在上述三、4中说明过了 /* Did the user stop this? */ if (had_ctrlc()) return -1; /* if stopped then not repeatable */ } return rc ? rc : repeatable;}
3、hush命令行模式
hush的实现自己也没搞太懂,简单的说明一下流程
static int parse_file_outer(FILE *f){ int rcode; struct in_str input; setup_file_in_str(&input);// 安装一个输入流,具体没怎么研究 rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);// 处理流数据 return rcode;}后续的流程简单说明如下:parse_stream_outer——》run_list————》run_list_real——————》run_pipe_real————————》cmd_process
可以观察到,最终还是调用了cmd_process来对命令进行处理,上述第三节已经说明了,这里不重复说明了。
- [uboot] (第六章)uboot流程——命令行模式以及命令处理介绍
- [uboot] (第六章)uboot流程——命令行模式以及命令处理介绍
- uboot中命令行模式以及命令处理
- [uboot] (第二章)uboot流程——uboot-spl编译流程
- [uboot] (第三章)uboot流程——uboot-spl代码流程
- [uboot] (第四章)uboot流程——uboot编译流程
- [uboot] (第五章)uboot流程——uboot启动流程
- [uboot] (第二章)uboot流程——uboot-spl编译流程
- [uboot] (第三章)uboot流程——uboot-spl代码流程
- [uboot] (第四章)uboot流程——uboot编译流程
- [uboot] (第五章)uboot流程——uboot启动流程
- [uboot] (第一章)uboot流程——概述
- [uboot] (第一章)uboot流程——概述
- [uboot] (番外篇)uboot dm-gpio使用方法以及工作流程
- [uboot] (番外篇)uboot dm-gpio使用方法以及工作流程
- 常用uboot命令介绍
- 常用uboot命令介绍
- uboot相关命令介绍
- laravel框架自带的邮件类
- 二分查找实现----面试总结
- P1737选择客栈
- 2016/11/13
- WCF服务创建与调用
- [uboot] (第六章)uboot流程——命令行模式以及命令处理介绍
- IP地址、子网掩码、网关的关系
- “穷”则思变--Python的完全安装大法
- 浅析——B树,B+树,B*树以及分析MySQL的两种引擎
- ROS开发环境之Qt Creator
- java学习注意事项
- 【NOIP 模拟题】[T2]宝藏(树形dp)
- 机械键盘测试(1)——序
- poj 2823 Sliding Window(单调队列模板)