uboot命令體系的代碼放在uboot/common/cmd_xxx.c還有command.c main.c也是
每個命令對應一個函數
每一個uboot命令背后對應一個函數,這就是uboot實現命令體系的方法,我們要找到每一個命令背后的那個函數,而且要分析這個函數和這個命令是怎樣對應起來的。
命令的參數以argc和argc傳給函數
有些uboot的命令還支持傳遞參數。也就是說命令背后對應的函數接收列表中有argc和argv,然后命令體系會把我們執行命令時的命令+參數,以argc和argv的方式傳遞給執行命令的函數
以help背后的函數do_hlep為例。
int do_help (cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
uboot命令解析和執行過程分析
從main_loop說起,uboot啟動的第二階段,在初始化所有應該初始化后,就是一個死循環,死循環循環體是main_loop,然后main_loop函數執行一次,就獲取,解析,執行命令一次。
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 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;
}
(1)控制台命令獲取
parse_line函數把md 30000000 10這個命令解析成argv[0]=md, argv[1]=30000000 argv[3]=10
(2)find_cmd函數去uboot命令函數集合當中,搜索有沒有argv[0]這個命令,如果沒找到就打印
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
(3)執行命令。最后用函數指針的方式調用了對應函數
思考:關鍵點就在於find_cmd函數如何查找這個命令是不是uboot的合法支持的命令?這取決於uboot的命令體系機制(uboot是如何設計的這一套,命令如何去注冊、存儲、管理索引)
uboot如何處理命令集
可能的管理方式
(1)數組。結構體數組,數組中每一個結構體成員就是一個命令的所有信息,但是數組本身也有一定限制
(2)鏈表。鏈表的每一個節點的data段就是一個命令結構體,所有的命令都放在一條鏈表上,這樣就解決了數組方式的不靈活,但是開銷就大了,需要額外的內存開銷,各種算法,遍歷,插入,刪除。
(3)有第三種嗎?uboot沒有使用數組或者鏈表,而是使用了一種新的方式來實現這種效果。
命令結構體cmd_tbl_t
truct 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;
name是命令的名字
maxargs是最大參數個數
repeatable是自動重復允許
cmd是函數指針,命令對應的函數,將來執行這個函數,使用函數指針來調用
usage:命令的短幫助信息
help:命令的長幫助信息
complete也是一個函數指針,指向這個命令的自動補全的函數,用宏來配置
總結:uboot的命令體系在工作時,就一個命令對應一個上面的這個cmd_tab_t的結構體,uboot支持多少個命令,就需要多少個結構體的實例,uboot命令體系把這些結構體的實例管理起來,當用戶輸入命令后,uboot回去這些結構體的實例中去查找(查找方法和存儲管理的方法有關), 如果找到就執行,找不到,就返回錯誤。
uboot實現命令管理的思路
1)填充1個結構體實例構成一個命令
(2)給命令結構體實例附加特定段屬性(用戶自定義段),鏈接時將帶有該段屬性的內容鏈接在一起排列(挨着的,不會夾雜其他東西,也不會丟掉一個帶有這種段屬性的,但是順序是亂序的)。
(3)uboot重定位時將該段整體加載到DDR中。加載到DDR中的uboot鏡像中帶有特定段屬性的這一段其實就是命令結構體的集合,有點像一個命令結構體數組。
(4)段起始地址和結束地址(鏈接地址、定義在u-boot.lds中)決定了這些命令集的開始和結束地址。
uboot如何處理命令集2
uboot命令定義具體實現分析
(1)U_BOOT_CMD宏基本分析
這個宏定義在uboot/common/command.h中。
U_BOOT_CMD(
version, 1, 1, do_version,
"version - print monitor version\n",
NULL
);
這個宏替換后變成:
cmd_tbl_t __u_boot_cmd_version __attribute__ ((unused,section (".u_boot_cmd"))) = {#name, maxargs, rep, cmd, usage, help}
總結:這個U_BOOT_CMD宏的理解,關鍵在於結構體變量的名字和段屬性。名字使用##作為連字符,附加了用戶自定義段屬性,以保證鏈接時將這些數據結構鏈接在一起排布。
(2)鏈接腳本。
find_cmd函數詳解
(1)find_cmd函數的任務是從當前uboot的命令集中查找是否有某個命令。如果找到則返回這個命令結構體的指針,如果未找到返回NULL。
(2)函數的實現思路很簡單,如果不考慮命令帶點的情況(md.b md.w這種)就更簡單了。查找命令的思路其實就是for循環遍歷數組的思路,不同的是數組的起始地址和結束地址是用地址值來給定的,數組中的元素個數是結構體變量類型。
U_BOOT_CMD宏詳解
(1)這個宏其實就是定義了一個命令對應的結構體變量,這個變量名和宏的第一個參數有關,因此只要宏調用時傳參的第一個參數不同則定義的結構體變量不會重名。
命令舉例:version命令
uboot中增加自定義命令
在已有的c文件中直接添加命令
(1)在uboot/common/command.c中添加一個命令,叫:mycmd
(2)在已有的.c文件中添加命令比較簡單,直接使用U_BOOT_CMD宏即可添加命令,給命令提供一個do_xxx的對應的函數這個命令就齊活了。
(3)添加完成后要重新編譯工程(make distclean; make x210_sd_config; make),然后燒錄新的uboot去運行即可體驗新命令。
(4)還可以在函數中使用argc和argv來驗證傳參。
自建一個c文件並添加命令
(1)在uboot/common目錄下新建一個命令文件,叫cmd_aston.c(對應的命令名就叫aston,對應的函數就叫do_aston函數),然后在c文件中添加命令對應的U_BOOT_CMD宏和函數。注意頭文件包含不要漏掉。
(2)在uboot/common/Makefile中添加上aston.o,目的是讓Make在編譯時能否把cmd_aston.c編譯鏈接進去。
(3)重新編譯燒錄。重新編譯步驟是:make distclean; make x210_sd_config; make
