u-boot原理分析第二课-------u-boot命令的实现

    如果我们在串口终端中使用过u-boot,就可以知道,u-boot是可以执行相关命令的。然而,在u-boot里面有很多命令,我们得给这些命令定义一个统一的接口,让所有的命令都封装成同样的格式,这样才便于去使用和管理。那么我们应该怎样去实现这个接口呢?用结构体去实现!

    对于每个命令,肯定有它的标识符,也就是它的名字,输入命令后,执行的过程肯定是调用某个函数,所以它也得有一个函数指针。这就是命令结构体的两大最重要的部分。我们在u-boot目录里面的common目录里,打开main.c这个文件,我们在里面会看到有不少的run_command的命令,我们直接跳转到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 (CONFIG_COMMANDS & CFG_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	/* CFG_CMD_BOOTD */

		/* 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 0;	/* if stopped then not repeatable */
	}

	return rc ? rc : repeatable;
}

我们看到,在这个函数的最开始处,定义了一个cmd_tbl_t *cmdtp;这样的东西,它的数据类型叫作 cmd_tbl_t,那这是个什么类型呢?这里我先不讲,我们继续往下看,看到while这里:

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

这是解析串口输入的命令,将命令一个一个提取出来,如果有两个命令的话,会有";"进行隔开。


我们继续往下看:

		/* 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;
		}

这里的话是对参数进行解析,调用了parse_line,最后会返回一个值,保存在argc中,argc也就是参数的个数。我们继续看:

		/* 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;
		}

这里我们看到这句英文:Look up command in command table:在命令表中查询命令,也就是这段代码的解释。我们可以看到这里它调用了find_cmd函数,函数的返回值由cmdtp接受,也就是我们之前所看到的cmd_tbl_t *cmdtp;

到这里,我们不难得知,cmd_tbl_t就是我们想找的定义命令的结构体(统一接口)。

接下来,我们跳到cmd_tbl_t的定义中(在include文件夹里的command.h里面):

扫描二维码关注公众号,回复: 882345 查看本文章
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
};

我们可以看到,在这个结构体里面,有name变量,也就是command的名称;第二个参数时它最大的参数个数;第三个参数则是表示该命令是否可以重复执行,如果可以重复,则第二次直接按回车即可;第四个参数则是一个指针数组,也就是执行这个命令后应该调用的函数;第五个参数是它的短信息;第六个参数时它的长信息;第七个参数是自动补全,当你输入这个命令的一部分的时候,会自动地进行补全。

    以上就是这个命令结构体的所有内容了。我们回到之前的代码,就是在main.c的函数那里:

		if ((cmdtp = find_cmd(argv[0])) == NULL) {
			printf ("Unknown command '%s' - try 'help'\n", argv[0]);
			rc = -1;	/* give up after bad command */
			continue;
		}
我们看到,它是通过find_cmd这个函数来找到这个命令结构体的。其中这个函数传入了一个参数:argv[0],这个应该是我们命令的名字,所以它是应该是根据命令的名字,也就是标识符来找到相应的命令结构体的。为了验证我们的猜想,我们进入find_cmd这个函数看一下(common目录下的command.c函数里),
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 */
}

我们看到,在函数的开始定义了两个命令结构体 ,接着是先获取命令的长度。接下来,是一条for循环:

	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++;
		}
	}

我们看到,这里有两个参数,__u_boot_cmd_start、__u_boot_cmd_end,这两个参数我们并没有在这个文件里找到他们的定义。其实,这两个参数是在链接脚本里。

我们可以看到,在链接脚本里面,__u_boot_cmd_start和__u_boot_cmd_end的地址是在(Global Offset Table段之后)。中间有一句话,.u_boot_cmd : {*(.u_boot_cmd) } ,这句话意思就是把命令的结构体放在这个地址段里面。这两句for的意思是在放置命令结构体的地址段里面循环,从里面取出一个命令结构体,对比一下名字,是否匹配,匹配的话就返回(当然,后面还有一些其它的判断,比如是否是简写)。接下来,我们再看一下.u_boot_cmd的定义(在command.h)里:

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

#ifdef  CFG_LONGHELP

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

#else	/* no long help info */

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}

#endif	/* CFG_LONGHELP */

#endif	/* __COMMAND_H */

我们先来看到第一句:

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

这句是一个宏定义,替换成的是一个属性指定,也就是关键字__attribute__,指明它可能是unused(可能是未使用的)的,而且被指定属性的对象是放在.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}
这段代码是定义了
 CFG_LONGHELP

这个是应该是一个带参宏,我们把一个例子替换进来,比如bootm命令(在该目录下即可找到):

cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd")))= {
“bootm”,
CFG_MAXARGS,
1, do_bootm,
"bootm - boot application image from memory\n",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
}

我们可以看到,其实这就是一个命令结构体的定义,名字叫做:__u_boot_cmd_bootm,并对它进行了所有成员的初始化,且这个结构体是放在.u_boot_cmd这个段里面的。

猜你喜欢

转载自blog.csdn.net/xiaokangdream/article/details/79537410