uboot学习之六---uboot命令体系基础
uboot命令体系的代码放在uboot/common/cmd_xxx.c还有command.c main.c也是
每个命令对应一个函数
每一个uboot命令背后对应一个函数,这就是uboot实现命令体系的方法,我们要找到每一个命令背后的那个函数,而且要分析这个函数和这个命令是怎样对应起来的。
命令的参数以argc和argc传给函数
有些uboot的命令还支持传递参数。也就是说命令背后对应的函数接收列表中有argc和argv,然后命令体系会把我们执行命令时的命令+参数,以argc和argv的方式传递给执行命令的函数
以help背后的函数do_hlep为例。
int do_help (cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
uboot命令解析和执行过程分析
从main_loop说起,uboot启动的第二阶段,在初始化所有应该初始化后,就是一个死循环,死循环循环体是main_loop,然后main_loop函数执行一次,就获取,解析,执行命令一次。
run_command函数解析
int run_command (const char *cmd, int flag)
{
cmd_tbl_t *cmdtp;
char cmdbuf[CFG_CBSIZE]; /* working copy of cmd */
char *token; /* start of token in cmdbuf */
char *sep; /* end of token (separator) in cmdbuf */
char finaltoken[CFG_CBSIZE];
char *str = cmdbuf;
char *argv[CFG_MAXARGS + 1]; /* NULL terminated */
int argc, inquotes;
int repeatable = 1;
int rc = 0;
#ifdef DEBUG_PARSER
printf ("[RUN_COMMAND] cmd[%p]=\"", cmd);
puts (cmd ? cmd : "NULL"); /* use puts - string may be loooong */
puts ("\"\n");
#endif
clear_ctrlc(); /* forget any previous Control C */
if (!cmd || !*cmd) {
return -1; /* empty command */
}
if (strlen(cmd) >= CFG_CBSIZE) {
puts ("## Command too long!\n");
return -1;
}
strcpy (cmdbuf, cmd);
/* Process separators and check for invalid
* repeatable commands
*/
#ifdef DEBUG_PARSER
printf ("[PROCESS_SEPARATORS] %s\n", cmd);
#endif
while (*str) {
/*
* Find separator, or string end
* Allow simple escape of ';' by writing "\;"
*/
for (inquotes = 0, sep = str; *sep; sep++) {
if ((*sep=='\'') &&
(*(sep-1) != '\\'))
inquotes=!inquotes;
if (!inquotes &&
(*sep == ';') && /* separator */
( sep != str) && /* past string start */
(*(sep-1) != '\\')) /* and NOT escaped */
break;
}
/*
* Limit the token to data between separators
*/
token = str;
if (*sep) {
str = sep + 1; /* start of command for next pass */
*sep = '\0';
}
else
str = sep; /* no more commands for next pass */
#ifdef DEBUG_PARSER
printf ("token: \"%s\"\n", token);
#endif
/* find macros in this token and replace them */
process_macros (token, finaltoken);
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) {
rc = -1; /* no command at all */
continue;
}
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL) {
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
rc = -1; /* give up after bad command */
continue;
}
/* found - check max args */
if (argc > cmdtp->maxargs) {
printf ("Usage:\n%s\n", cmdtp->usage);
rc = -1;
continue;
}
#if defined(CONFIG_CMD_BOOTD)
/* avoid "bootd" recursion */
if (cmdtp->cmd == do_bootd) {
#ifdef DEBUG_PARSER
printf ("[%s]\n", finaltoken);
#endif
if (flag & CMD_FLAG_BOOTD) {
puts ("'bootd' recursion detected\n");
rc = -1;
continue;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif
/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
rc = -1;
}
repeatable &= cmdtp->repeatable;
/* Did the user stop this? */
if (had_ctrlc ())
return -1; /* if stopped then not repeatable */
}
return rc ? rc : repeatable;
}
(1)控制台命令获取
parse_line函数把md 30000000 10这个命令解析成argv[0]=md, argv[1]=30000000 argv[3]=10
(2)find_cmd函数去uboot命令函数集合当中,搜索有没有argv[0]这个命令,如果没找到就打印
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
(3)执行命令。最后用函数指针的方式调用了对应函数
思考:关键点就在于find_cmd函数如何查找这个命令是不是uboot的合法支持的命令?这取决于uboot的命令体系机制(uboot是如何设计的这一套,命令如何去注册、存储、管理索引)
uboot如何处理命令集
可能的管理方式
(1)数组。结构体数组,数组中每一个结构体成员就是一个命令的所有信息,但是数组本身也有一定限制
(2)链表。链表的每一个节点的data段就是一个命令结构体,所有的命令都放在一条链表上,这样就解决了数组方式的不灵活,但是开销就大了,需要额外的内存开销,各种算法,遍历,插入,删除。
(3)有第三种吗?uboot没有使用数组或者链表,而是使用了一种新的方式来实现这种效果。
命令结构体cmd_tbl_t
truct 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 *[]);
char *usage; /* Usage message (short) */
#ifdef CFG_LONGHELP
char *help; /* Help message (long) */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s cmd_tbl_t;
name是命令的名字
maxargs是最大参数个数
repeatable是自动重复允许
cmd是函数指针,命令对应的函数,将来执行这个函数,使用函数指针来调用
usage:命令的短帮助信息
help:命令的长帮助信息
complete也是一个函数指针,指向这个命令的自动补全的函数,用宏来配置
总结:uboot的命令体系在工作时,就一个命令对应一个上面的这个cmd_tab_t的结构体,uboot支持多少个命令,就需要多少个结构体的实例,uboot命令体系把这些结构体的实例管理起来,当用户输入命令后,uboot回去这些结构体的实例中去查找(查找方法和存储管理的方法有关), 如果找到就执行,找不到,就返回错误。
uboot实现命令管理的思路
1)填充1个结构体实例构成一个命令
(2)给命令结构体实例附加特定段属性(用户自定义段),链接时将带有该段属性的内容链接在一起排列(挨着的,不会夹杂其他东西,也不会丢掉一个带有这种段属性的,但是顺序是乱序的)。
(3)uboot重定位时将该段整体加载到DDR中。加载到DDR中的uboot镜像中带有特定段属性的这一段其实就是命令结构体的集合,有点像一个命令结构体数组。
(4)段起始地址和结束地址(链接地址、定义在u-boot.lds中)决定了这些命令集的开始和结束地址。
uboot如何处理命令集2
uboot命令定义具体实现分析
(1)U_BOOT_CMD宏基本分析
这个宏定义在uboot/common/command.h中。
U_BOOT_CMD(
version, 1, 1, do_version,
"version - print monitor version\n",
NULL
);
这个宏替换后变成:
cmd_tbl_t __u_boot_cmd_version __attribute__ ((unused,section (".u_boot_cmd"))) = {#name, maxargs, rep, cmd, usage, help}
总结:这个U_BOOT_CMD宏的理解,关键在于结构体变量的名字和段属性。名字使用##作为连字符,附加了用户自定义段属性,以保证链接时将这些数据结构链接在一起排布。
(2)链接脚本。
find_cmd函数详解
(1)find_cmd函数的任务是从当前uboot的命令集中查找是否有某个命令。如果找到则返回这个命令结构体的指针,如果未找到返回NULL。
(2)函数的实现思路很简单,如果不考虑命令带点的情况(md.b md.w这种)就更简单了。查找命令的思路其实就是for循环遍历数组的思路,不同的是数组的起始地址和结束地址是用地址值来给定的,数组中的元素个数是结构体变量类型。
U_BOOT_CMD宏详解
(1)这个宏其实就是定义了一个命令对应的结构体变量,这个变量名和宏的第一个参数有关,因此只要宏调用时传参的第一个参数不同则定义的结构体变量不会重名。
命令举例:version命令
uboot中增加自定义命令
在已有的c文件中直接添加命令
(1)在uboot/common/command.c中添加一个命令,叫:mycmd
(2)在已有的.c文件中添加命令比较简单,直接使用U_BOOT_CMD宏即可添加命令,给命令提供一个do_xxx的对应的函数这个命令就齐活了。
(3)添加完成后要重新编译工程(make distclean; make x210_sd_config; make),然后烧录新的uboot去运行即可体验新命令。
(4)还可以在函数中使用argc和argv来验证传参。
自建一个c文件并添加命令
(1)在uboot/common目录下新建一个命令文件,叫cmd_aston.c(对应的命令名就叫aston,对应的函数就叫do_aston函数),然后在c文件中添加命令对应的U_BOOT_CMD宏和函数。注意头文件包含不要漏掉。
(2)在uboot/common/Makefile中添加上aston.o,目的是让Make在编译时能否把cmd_aston.c编译链接进去。
(3)重新编译烧录。重新编译步骤是:make distclean; make x210_sd_config; make