UBOOT——命令体系
uboot中命令行的实现原理:
uboot中设备的输入输出是通过串口来作为标准输入输出的,我们可以通过securecrt软件来接受设备从串口发出的信息,也可以通过securecrt软件写入串口向设备中发送命令;
所以在设备调试的时候通常都是通过打印串口信息调试的;
第一步:在主函中设置一个死循环用来接受处理命令,并打印信息;uboot中这个函数是main_loop函数;
main_loop函数所做的具体的事情有:
1:读取sercurecrt命令行中发送的串口信息,并把读取的字符串信息保存在一个局部变量字符串数组lastcommand中:函数为 len = readline (CFG_PROMPT);
2:执行lastcommand字符串数组中保存的命令rc = run_command (lastcommand, flag);
第二步:在readline函数中所要做的事情就是把从命令行中接收到的信息进行初步处理,把处理好以后的字符串放入全局变量console_buffer中;
readline函数所做的具体事情:
1:输出x210 #
2:调用cread_line函数从securecrt中命令行中读取命令,并进行初步的处理;
分析一下cread_line函数中所做的事情:
1:ichar = getcmd_getch();这个函数是从串口中读取信息;如果读取到'\n'或者'\r'的话表示输入完成;
2:之后的代码就是处理键盘的特殊字符详细解释可以看一下这篇博客
http://blog.chinaunix.net/uid-30031530-id-5152127.html
第三步:rc = run_command (lastcommand, flag)函数中要进一步处理lastcommand中的字符串
1:调用process_macros函数 处理字符串中的转义字符;
2:调用parse_line函数统计argc、赋值argv
3:调用find_cmd命令查找相应命令返回命令的结构体指针;
4:(cmdtp->cmd) (cmdtp, flag, argc, argv)以函数指针的方式来执行命令函数;
接下来一个一个函数进行分析,首先当start_armboot函数会循环在一个main_loop函数中,控制台界面实际是在这个函数中输入命令、解析命令、查找、执行命令
一下是删除条件编译以后的main_loop函数
void main_loop (void)
{
#ifndef CFG_HUSH_PARSER
static char lastcommand[CFG_CBSIZE] = { 0, }; //定义一个字符数组来存储所有读取到的命令
int len;
int rc = 1;
int flag;
#endif
/*
* 下面这段代码是执行bootdelay倒计时,
*
*/
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
char *s;
int bootdelay;
#endif
#ifdef CFG_HUSH_PARSER
u_boot_hush_start ();
#endif
//extern void act8937_charging_loop();
//act8937_charging_loop(); /* Evan Tan, 2010-12-13 */
printf("Enter into Normal mode\n");
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
/*
* 首先从环境变量中读取bootdelay的值,如果读取成功则把从环境变量中读取到的bootdealy
* 的值复制给bootdelay,如果不成功则从default_env数组中读取bootdelay并赋值
* abortboot 函数的作用就是执行倒计时,执行完倒计时以后执行bootcmd命令,启动linux内核。abortboot之后分析
*/
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
s = getenv ("bootcmd");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
run_command (s, 0);
}
/*
* Main Loop for Monitor Command Processing
*/
#ifdef CFG_HUSH_PARSER
parse_file_outer();
/* This point is never reached */
for (;;);
#else
#endif
/*
* 首先把从控制台输入的字符保存到 全局变量字符数组console_buffer中,然后在用strcpy函数
* 把字符串赋值到lastcommand中,最后通过 run_comand函数来执行
*/
len = readline (CFG_PROMPT);
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
if (len == -1)
puts ("<INTERRUPT>\n");
else
rc = run_command (lastcommand, flag);
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
#endif /*CFG_HUSH_PARSER*/
}
下面分析一下run_comand函数
下面这个结构体是命令结构体
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;
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;
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
*/
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 */
/* find macros in this token and replace them */ //替换转义字符
process_macros (token, finaltoken);
/* Extract arguments */ //parse_line函数为命令解析函数
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) { //find_cmd函数为命令查找函数
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;
}
命令解析函数解析:
int parse_line (char *line, char *argv[])
{
int nargs = 0;
#ifdef DEBUG_PARSER
printf ("parse_line: \"%s\"\n", line);
#endif
while (nargs < CFG_MAXARGS) { //line命令字符数组指针,argv 指针数字,存放命令参数字符串指针地址
/* skip any white space */
while ((*line == ' ') || (*line == '\t')) { //判断是否为空格或者 \t,如果是++
++line;
}
if (*line == '\0') { /* end of line, no more args */ //*line为\0表示line为空 说明命令为空
argv[nargs] = NULL;
#ifdef DEBUG_PARSER
printf ("parse_line: nargs=%d\n", nargs);
#endif
return (nargs);
}
argv[nargs++] = line; /* begin of argument string */ //把命令地址赋值给rgv[0]
/* find end of string */
while (*line && (*line != ' ') && (*line != '\t')) { //判断*line为空或者为空格或者为/t
++line;
}
if (*line == '\0') { /* end of line, no more args */ //为空表明遍历命令字符完成返回nargs的值
argv[nargs] = NULL;
#ifdef DEBUG_PARSER
printf ("parse_line: nargs=%d\n", nargs);
#endif
return (nargs);
}
*line++ = '\0'; /* terminate current arg */ //为\t或者空格,则把\t或者空格改为\0 循环即可
}
printf ("** Too many args (max. %d) **\n", CFG_MAXARGS);
#ifdef DEBUG_PARSER
printf ("parse_line: nargs=%d\n", nargs);
#endif
return (nargs);
}
命令查找函数,本来我们可以把所有命令的结构体放在一个数组里或者一个链表里,但是uboot中用了一个新的方法,就是定义了一个专门的段来存放所有的命令结构体,这个段的起始地址和结束地址都放在连接脚本里:
下面图中就是uboot中连接脚本定义的这个段的首地址和结束地址。
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 */
}
uboot中定义命令结构体单独存放在某个代码段是通过一下两个宏来实现的
#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}
详细解析
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage) /
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}
这样一来,凡通过U_BOOT_CMD定义的cmd_tbl_t变量会全部被放在.u_boot_cmd段当中(可以看UBOOT的链接脚本xxx.lds),具体怎么放是链接器的工作。
这里要看的是##name和#name这两个操作.##name将字符直接跟在后面, #name会将name这个字符中以“..."的形式放置。
例如:定义一个命令boot
U_BOOT_CMD(boot, 0, 0, fun, "boot xxx");
展开以后会变成:
cmd_tbl_t __u_boot_cmd_boot __attribute___((unused, section(".u_boot_cmd"))) = {"boot", 0, 0, fun, "boot xxx"}
大部分基本不变,将Struct_Section展开了,还将##name换成了boot, 将#name换成了"boot"。应该不难看出##/#的作用吧。
从上面来看,我们是不是可以在程序运行时去定义一个变量呢??我们可以通过##xxx来定义一个变量,然后还可以通过这种形式来使用它。
总体来说是通过宏定义来定义变量,准确地说应该是结构体变量。并且把这些同一种结构体的变量放在一个段中,充分的利用了连接器的作用,很少看到,但是确实很实用。这样做的好处是所有开发各个模块的研发人员不必去维护一个全局的结构体数组,而且你也不知道别人用的是数组中的哪一个下表,这种方法就很好的解决了这种烦恼,值得推广。
看一下uboot中的命令实例
#if defined(CONFIG_CMD_ECHO)
int
do_echo (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
int i, putnl = 1;
for (i = 1; i < argc; i++) {
char *p = argv[i], c;
if (i > 1)
putc(' ');
while ((c = *p++) != '\0') {
if (c == '\\' && *p == 'c') {
putnl = 0;
p++;
} else {
putc(c);
}
}
}
if (putnl)
putc('\n');
return 0;
}
U_BOOT_CMD(
echo, CFG_MAXARGS, 1, do_echo,
"echo - echo args to console\n",
"[args..]\n"
" - echo args to console; \\c suppresses newline\n"
);
#endif
总结:
uboot中命令行的实现原理:
uboot中设备的输入输出是通过串口来作为标准输入输出的,我们可以通过securecrt软件来接受设备从串口发出的信息,也可以通过securecrt软件写入串口向设备中发送命令;
所以在设备调试的时候通常都是通过打印串口信息调试的;
第一步:在主函中设置一个死循环用来接受处理命令,并打印信息;uboot中这个函数是main_loop函数;
main_loop函数所做的具体的事情有:
1:读取sercurecrt命令行中发送的串口信息,并把读取的字符串信息保存在一个局部变量字符串数组lastcommand中:函数为 len = readline (CFG_PROMPT);
2:执行lastcommand字符串数组中保存的命令rc = run_command (lastcommand, flag);
第二步:在readline函数中所要做的事情就是把从命令行中接收到的信息进行初步处理,把处理好以后的字符串放入全局变量console_buffer中;
readline函数所做的具体事情:
1:输出x210 #
2:调用cread_line函数从securecrt中命令行中读取命令,并进行初步的处理;
分析一下cread_line函数中所做的事情:
1:ichar = getcmd_getch();这个函数是从串口中读取信息;如果读取到'\n'或者'\r'的话表示输入完成;
2:之后的代码就是处理键盘的特殊字符详细解释可以看一下这篇博客
http://blog.chinaunix.net/uid-30031530-id-5152127.html
第三步:rc = run_command (lastcommand, flag)函数中要进一步处理lastcommand中的字符串
1:调用process_macros函数 处理字符串中的转义字符;
2:调用parse_line函数统计argc、赋值argv
3:调用find_cmd命令查找相应命令返回命令的结构体指针;
4:(cmdtp->cmd) (cmdtp, flag, argc, argv)以函数指针的方式来执行命令函数;
代码如下:
static int cread_line(const char *const prompt, char *buf, unsigned int *len)
{
unsigned long num = 0;
unsigned long eol_num = 0;
unsigned long rlen;
unsigned long wlen;
char ichar;
int insert = 1;
int esc_len = 0;
int rc = 0;
char esc_save[8];
while (1) {
rlen = 1;
#ifdef CONFIG_BOOT_RETRY_TIME
while (!tstc()) { /* while no incoming data */
if (retry_time >= 0 && get_ticks() > endtime)
return (-2); /* timed out */
}
#endif
ichar = getcmd_getch();
if ((ichar == '\n') || (ichar == '\r')) {
putc('\n');
break;
}
/*
* handle standard linux xterm esc sequences for arrow key, etc.
*/
if (esc_len != 0) {
if (esc_len == 1) {
if (ichar == '[') {
esc_save[esc_len] = ichar;
esc_len = 2;
} else {
cread_add_str(esc_save, esc_len, insert,
&num, &eol_num, buf, *len);
esc_len = 0;
}
continue;
}
switch (ichar) {
case 'D': /* <- key */
ichar = CTL_CH('b');
esc_len = 0;
break;
case 'C': /* -> key */
ichar = CTL_CH('f');
esc_len = 0;
break; /* pass off to ^F handler */
case 'H': /* Home key */
ichar = CTL_CH('a');
esc_len = 0;
break; /* pass off to ^A handler */
case 'A': /* up arrow */
ichar = CTL_CH('p');
esc_len = 0;
break; /* pass off to ^P handler */
case 'B': /* down arrow */
ichar = CTL_CH('n');
esc_len = 0;
break; /* pass off to ^N handler */
default:
esc_save[esc_len++] = ichar;
cread_add_str(esc_save, esc_len, insert,
&num, &eol_num, buf, *len);
esc_len = 0;
continue;
}
}
switch (ichar) {
case 0x1b:
if (esc_len == 0) {
esc_save[esc_len] = ichar;
esc_len = 1;
} else {
puts("impossible condition #876\n");
esc_len = 0;
}
break;
case CTL_CH('a'):
BEGINNING_OF_LINE();
break;
case CTL_CH('c'): /* ^C - break */
*buf = '\0'; /* discard input */
return (-1);
case CTL_CH('f'):
if (num < eol_num) {
getcmd_putch(buf[num]);
num++;
}
break;
case CTL_CH('b'):
if (num) {
getcmd_putch(CTL_BACKSPACE);
num--;
}
break;
case CTL_CH('d'):
if (num < eol_num) {
wlen = eol_num - num - 1;
if (wlen) {
memmove(&buf[num], &buf[num+1], wlen);
putnstr(buf + num, wlen);
}
getcmd_putch(' ');
do {
getcmd_putch(CTL_BACKSPACE);
} while (wlen--);
eol_num--;
}
break;
case CTL_CH('k'):
ERASE_TO_EOL();
break;
case CTL_CH('e'):
REFRESH_TO_EOL();
break;
case CTL_CH('o'):
insert = !insert;
break;
case CTL_CH('x'):
case CTL_CH('u'):
BEGINNING_OF_LINE();
ERASE_TO_EOL();
break;
case DEL:
case DEL7:
case 8:
if (num) {
wlen = eol_num - num;
num--;
memmove(&buf[num], &buf[num+1], wlen);
getcmd_putch(CTL_BACKSPACE);
putnstr(buf + num, wlen);
getcmd_putch(' ');
do {
getcmd_putch(CTL_BACKSPACE);
} while (wlen--);
eol_num--;
}
break;
case CTL_CH('p'):
case CTL_CH('n'):
{
char * hline;
esc_len = 0;
if (ichar == CTL_CH('p'))
hline = hist_prev();
else
hline = hist_next();
if (!hline) {
getcmd_cbeep();
continue;
}
/* nuke the current line */
/* first, go home */
BEGINNING_OF_LINE();
/* erase to end of line */
ERASE_TO_EOL();
/* copy new line into place and display */
strcpy(buf, hline);
eol_num = strlen(buf);
REFRESH_TO_EOL();
continue;
}
#ifdef CONFIG_AUTO_COMPLETE
case '\t': {
int num2, col;
/* do not autocomplete when in the middle */
if (num < eol_num) {
getcmd_cbeep();
break;
}
buf[num] = '\0';
col = strlen(prompt) + eol_num;
num2 = num;
if (cmd_auto_complete(prompt, buf, &num2, &col)) {
col = num2 - num;
num += col;
eol_num += col;
}
break;
}
#endif
default:
cread_add_char(ichar, insert, &num, &eol_num, buf, *len);
break;
}
}
*len = eol_num;
buf[eol_num] = '\0'; /* lose the newline */
if (buf[0] && buf[0] != CREAD_HIST_CHAR)
cread_add_to_hist(buf);
hist_cur = hist_add_idx;
return (rc);
}