uboot的命令体系

一、uboot命令体系基础

1.uboot命令体系实现代码在哪里

(1)uboot命令体系的实现代码在common/cmd_xxx.c中,有若干个.c文件和命令体系有关。(还有command.c、main.c也和命令有关)

2.每个命令对应一个函数

(1)每一个uboot的命令背后都对应一个函数,这就是uboot实现命令体系的一种思路和方法。

(2)要找到每一个命令背后所对应的那个函数,而且要分析这个函数和这个命令是怎样对应起来的。

3.命令参数以argc&argv传给函数

(1)有些uboot命令还支持传递参数,也就是命令背后对应的函数接收的参数列表中有argc和argv,然后命令体系会把我们执行命令时的命令+参数以argc和argv的方式传递给执行命令的函数。例如命令:md 30000000 10,argc=3,argv(argv[0]=md,argv[1]=30000000,argv[2]=10)

以help命令为例:help命令背后对应的函数名叫:do_help,在common/command.c的236行有如下定义:

        int do_help (cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])

二、uboot命令解析和执行过程分析

1.从main_loop说起

(1)uboot启动的第二阶段,在初始化了所有该初始化的东西后,进入一个死循环,死循环的循环体就是main_loop。

(2)main_loop函数执行一次就是一个获取命令、解析命令、执行命令的过程。

(3)run_command函数用来执行命令的函数。

2.run_command函数详解

代码:1284 ~ 1407行(common/main.c)

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)控制台命令获取。

(2)命令解析:parse_line函数把"md 30000000 10"解析成argv[0]=md、argv[1]=30000000、argv[2]=10。

(3)在命令集合中查找命令:find_cmd(argv[0])函数去uboot的命令集合中搜索有没有argv[0]这个命令。

(4)执行命令:最后用函数指针的方式调用相应的函数。

思考:关键点就在于find_cmd函数如何查找到这个命令是不是uboot的合法支持的命令?这取决于uboot的命令体系机制(uboot是如何设计的,命令是如何注册、存储、管理、索引的)。

三、uboot如何处理命令集

1.可能的管理方式

(1)数组:结构体数组,数组中每一个结构体成员就是一个命令的所有信息。

(2)链表:链表的每个节点的data段就是一个命令结构体,所有的命令都放在一条链表上,这样就解决了数组方式的不灵活,坏处是需要额外的内存开销,然后各种算法(遍历、插入、删除等)需要一定复杂度的代码执行。

(3)有第三种吗?uboot没有使用数组或者链表,而是使用了一种新的方式来实现这个功能。

2.命令结构体cmd_tbl_t

/*
 * Monitor Command Table
 */

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;

extern cmd_tbl_t  __u_boot_cmd_start;
extern cmd_tbl_t  __u_boot_cmd_end;

(1)name:命令名字,字符串格式

(2)maxargs:命令最多可以接收多少个参数

(3)repeatable:表示这个命令是否可以重复执行。重复执行是uboot命令行的一种工作机制,就是按回车键执行上一条执行的命令。

(4)cmd:函数指针,命令对应的函数的函数指针,将来执行这个命令的函数时,使用这个函数指针来调用

(5)usage:命令的短帮助信息,命令的简单描述

(6)help:命令的长帮助信息,命令的详细描述

(7)complete:函数指针,执行这个命令的自动补全函数

总结:uboot的命令体系在工作时,一个命令对应一个cmd_tbl_t结构体的实例,uboot支持多少命令,就需要多少个结构体的实例。uboot的命令体系把这些结构体实例管理起来,当用户输入命令后,uboot就去这些结构体实例中查找(查找方法和存储管理的方法有关),如果找到就执行,找不到就提示命令未知。

3.uboot实现命令管理的思路

(1)填充一个结构体实例对应一个命令。

(2)给命令结构体实例附加特定段属性(用户自定义段),链接时将带有该段属性的内容链接在一起排列(挨着的,不会夹杂其他东西,也不会丢掉一个带有这种段属性的,但是顺序是乱的)。

(3)uboot重定位时将该段整体加载到DDR中。加载到DDR中的uboot镜像中带有特定段属性的这一段其实就是命令结构体的结合,有点像命令结构体数组。

(4)段起始地址和结束地址(链接地址定义在uboot.lds中)决定了命令集的开始和结束地址。

4.U_BOOT_CMD

它的定义位于common/command.h文件中,如下所示:

#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}

(1)这个宏其实就是定义了命令对应的结构体变量,这个变量名和宏的第一个参数有关,因此只要宏调用时传参的第一个参数不同,那么定义的结构体变量不会重名。

(2)举例说明

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"))) = \
	{"version", 1, 1, do_version, "version - print monitor version\n", NULL};

总结:对于U_BOOT_CMD宏的理解,关键在于结构体变量的名字和段属性,名字使用##作为连字符,附加了用户自定义段属性,以保证链接时将这些数据结构链接在一起排布。

5.find_cmd函数

代码:344 ~ 374行(common/command.c)

/***************************************************************************
 * find command table entry for a command
 */
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;

	/*
	 * 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 = &__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 */
}

(1)find_cmd函数的任务是从当前uboot的命令集中查找是否有某个命令,如果找到则返回这个命令结构体的指针,如果未找到则返回NULL。

(2)函数的实现思路很简单,如果不考虑命令带点的情况(md.b、md.w这种)就更简单了,查找命令的思路其实就是for循环遍历数组的思路,不同的是数组的起始地址和结束地址是用地址值来给定的,数组中的元素个数是结构体变量类型。

四、uboot中增加自定义命令

1.在已有的.c文件中直接添加命令

(1)在common/command.c中添加一个命令,叫:mycmd

(2)在已有的.c文件中添加命令比较简单,直接使用U_BOOT_CMD宏即可添加命令,然后再给命令提供一个对应的do_xxx函数就齐活了。

(3)添加完成后要重新编译工程(make distclean; make 210_sd_config; make),然后烧录新的uboot去运行即可体验新命令。

(4)还可以在函数中使用argc和argv来验证传参。

2.自建一个.c文件并添加命令

(1)在common目录下新建一个命令文件,叫cmd_aston.c(对应的命令名就叫aston,对应的函数就叫do_aston),然后在.c文件中添加对应的U_BOOT_CMD宏和函数,注意头文件包含不要漏掉。

(2)在common/Makefile中添加cmd_aston.o,目的是让Make在编译时能办cmd_astion.c编译链接进去。

(3)重新编译烧录,重新编译步骤:make distclean; make 210_sd_config; make。

3.体会:uboot命令体系的优点

(1)uboot的命令体系本身稍微复杂,但是写好之后就不用动了。在移植uboot时也不会去动uboot的命令体系,最多就是想uboot中添加命令。

(2)向uboot中添加命令非常简单。

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值