原博客:https://blog.csdn.net/xiaokangdream/article/details/79537410
如果我们在串口终端中使用过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里面):
-
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这个段里面的。