嵌入式开发——uboot中命令体系详解

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、执行命令的循环函数

参考博客:《嵌入式开发——uboot中命令执行函数(main_loop函数)》

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的链接脚本分析》

Guess you like

Origin blog.csdn.net/weixin_42031299/article/details/121172379