如果我们在串口终端中使用过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这个段里面的。