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