1、uboot中命令集的管理方式
(1)对于命令集的管理可以用数组也可以用链表,两者在逻辑上都可以实现命令集的管理,只是特性上有所不同,这是数组和链表本身这种数据结构决定的。数组的优点是可以根据下标随机查询,数组成员都是紧挨着存放的;缺点是必须在初始化时一次性申请内存,不利于扩展;链表优点是可以任意增加成员个数,不必一次性申请好内存;缺点是查询比较麻烦,要遍历链表,因为链表的成员在内存里不是连续存储的。
(2)uboot采用的既不是数组也不是链表,而是利用链接属性,这种方式同时兼具了数组和链表的优点。每个命令都用一个cmd_tbl_s结构体去表示,用U_BOOT_CMD宏去注册该命令,将命令添加自定义的段属性,因为都具有同样的段属性,在链接的时候将命令都链接到一起,这样命令的结构体在内存里都是连续分布的,这是数组的优点。我们并没有在代码里用硬编码固定命令集所占内存的大小,链接的时候有多少个命令结构体就链接多少,所以整个命令集所占内存大小是可扩展的,这是链表的优点。
(3)具体命令集的起始地址和结束地址在链接脚本里指定,__u_boot_cmd_start是起始地址__u_boot_cmd_end是结束地址,具体可以查看uboot的编译脚本。可以将__u_boot_cmd_start理解成一个cmd_tbl_s数组指针,__u_boot_cmd_end是该数组的结束地址,但是和数组不一样的是我们并不知道该数组的成员个数,但是可以用数组指针不断加加的操作去遍历整个数组。
2、命令结构体
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 *[]);
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;
(1)name:命令名称,字符串格式。
(2)maxargs:命令最多可以接收多少个参数
(3)repeatable:指示这个命令是否可重复执行。重复执行是uboot命令行的一种工作机制,就是直接按回车则执行上一条执行的命令。
(4)cmd:函数指针,命令对应的函数的函数指针,将来执行这个命令的函数时使用这个函数指针来调用。
(5)usage:命令的短帮助信息。对命令的简单描述。
(6)help:命令的长帮助信息。细节的帮助信息。
(7)complete:函数指针,指向这个命令的自动补全的函数。
3、U_BOOT_CMD宏基本分析
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {
#name, maxargs, rep, cmd, usage, help}
//举例:saveenv命令,将来输入saveenv命令,经过解析就会会去调用do_saveenv函数。
//saveenv:这个不是字符串,要加双引号才是字符串
U_BOOT_CMD(
saveenv, 1, 0, do_saveenv,
"saveenv - save environment variables to persistent storage\n",
NULL
);
//把宏定义展开
cmd_tbl_t __u_boot_cmd_saveenv Struct_Section = {
"saveenv",1, 0, do_saveenv,\
"saveenv - save environment variables to persistent storage\n",NULL}
(1)##name:把传进来的name替换在此处;
(2)#name:把name当字符串处理;
(3)attribute ((unused,section (".u_boot_cmd"))):
.u_boot_cmd是自定义的段属性,在链接的时候会把属于该段的代码全部链接在一起,集中起来存放,然后会用两个变量来指定该段的起始地址和结束地址。u_boot_cmd_start是起始地址,u_boot_cmd_end是结束地址,具体可以查看uboot的编译脚本。
4、执行命令的循环函数
5、find_cmd函数
cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start; /*Init value */
const char *p;
int len;
int n_found = 0;
//这是在判断命令是不是带点号,比如md.b
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
for (cmdtp = &__u_boot_cmd_start;
cmdtp != &__u_boot_cmd_end;
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 */
}
该函数的作用是传入解析得到的指令,然后在指令集中遍历查找是否存在该命令,如果存在则返回该命令的结构体指针,如果不存在则返回NULL。重点看函数里的for循环,是如何利用__u_boot_cmd_start和__u_boot_cmd_end来遍历整个命令集。
6、补充
如果不清楚__u_boot_cmd_start和__u_boot_cmd_end这两个变量,可以参考博客:《嵌入式开发(S5PV210)——u-boot的链接脚本分析》