1. 程式人生 > >十.linux開發之uboot移植(十)——uboot原始碼分析4-uboot的命令體系

十.linux開發之uboot移植(十)——uboot原始碼分析4-uboot的命令體系

一、uboot命令體系簡介

    • 1、uboot命令體系實現程式碼在哪裡

uboot命令體系的實現程式碼在uboot/common/cmd_xxx.c中。有若干個.c檔案和命令體系有關。(還有command.c main.c也是和命令有關的)。

uboot實現命令體系的方法是每一個uboot命令對應一個函式,與shell的實現是一致的。

  • 2、命令引數以argc&argv傳給函式

(1)有些uboot的命令還支援傳遞引數。也就是說命令背後對應的函式接收的引數列表中有argc和argv,然後命令體系會把我們執行命令時的命令+引數(md 30000000 10)以argc(3)和argv(argv[0]=md, argv[1]=30000000 argv[2]=10)的方式傳遞給執行命令的函式。

二.uboot命令解析和執行過程分析

(1) uboot在啟動進入BL2階段後最終執行在main_loop函式,如果在自動倒計時時沒有按下字元鍵,uboot將自動啟動kernel;如果按下了字元鍵,uboot將進入人機互動命令列的主迴圈,讀取命令、解析命令、執行命令。

  • 2.uboot命令的解析

main_loop函式中會先執行getenv (“bootcmd”),如果bootcmd環境變數設定的是啟動kenel的命令,則在自動倒計時結束後如果沒有字元輸入,則uboot會自動執行bootcmd的命令,預設即執行啟動kernel。如果自動倒計時結束前有字元輸入,則進入命令列提示符狀態阻塞等待使用者輸入命令。

readline函式讀取使用者輸入命令,進而通過run_command函式解析、執行命令

run_command函式會將接收的命令用parse_line函式解析,主要是將接收的命令字串根據空格、分號分割成幾部分

利用find_cmd函式遍歷查詢命令集,看uboot中是否有輸入的命令,如果沒有輸入的命令,列印提示符。如果有當前輸入的命令,呼叫當前輸入命令的命令結構體的函式指標成員cmd執行命令對應的函式。

clipboard.png

clipboard.png

**int run_command (const char \*cmd, int flag)**

**{**

**.......................**

**while (\*
str) {** **//對命令字串進行簡單分割** **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);** **//解析命令字串** **if ((argc = parse_line (finaltoken, argv)) == 0) {** **rc = -1;/\* no command at all \*/** **continue;** **}** **//遍歷查詢命令集中是否有當前輸入命令** **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** **//呼叫命令結構體的成員函式指標cmd對應的命令函式** **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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145

關鍵點:find_cmd函式命令的遍歷查詢:下面會仔細分析這個函式

3.uboot命令的執行

run_command函式中解析、遍歷查詢命令後,如果找到會通過呼叫命令結構體的成員cmd函式指標呼叫當前命令對應的命令函式do_xxxx。uboot命令的定義模板示例如下:

clipboard.png

\#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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

CONFIG_CMD_ECHO巨集可以決定定義的命令是否編譯進當前uboot中,一般需要在開發板標頭檔案中定義。命令定義必須包括命令結構體的定義和命令函式的定義。U_BOOT_CMD巨集定義了命令結構體,do_echo函式則是命令的具體執行函式。

三.uboot如何處理命令集1

總結:

uboot的命令體系在工作時,一個命令對應一個cmd_tbl_t結構體的一個例項,然後uboot支援多少個命令,就需要多少個結構體例項。

uboot命令集中的每個命令對應一個cmd_tbl_t型別變數,使用者輸入一個命令時,uboot命令體系會到命令集中查詢輸入的命令,如果找到就執行,沒有找到就提示命令沒有找到資訊。

clipboard.png

**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_scmd_tbl_t;**
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

(1)name:命令名稱,字串格式。

(2)maxargs:命令最多可以接收多少個引數

(3)repeatable:指示這個命令是否可重複執行。重複執行是uboot命令列的一種工作機制,就是直接按回車則執行上一條執行的命令。

(4)cmd:函式指標,命令對應的函式的函式指標,將來執行這個命令的函式時使用這個函式指標來呼叫。

(5)usage:命令的短幫助資訊。對命令的簡單描述。

(6)help:命令的長幫助資訊。細節的幫助資訊。

(7)complete:函式指標,指向這個命令的自動補全的函式。

四.uboot如何處理命令集2

  • 1.U_BOOT_CMD巨集分析

uboot命令體系沒有采用陣列、連結串列來實現,

是每一個命令對應一個cmd_tbl_t命令型別結構體,通過對cmd_tbl_t命令型別結構體的段屬性設定,將命令集儲存在了程式中的自定義段.u_boot_cmd中,程式在連結階段會將命令集分配在程式中的自定義段

連結指令碼中命令集自定義段如下:

\__u_boot_cmd_start = .;//命令集段起始地址

.u_boot_cmd : { \*(.u_boot_cmd) }//命令集中的命令

\__u_boot_cmd_end = .;//命令集段的結束地址
  • 1
  • 2
  • 3
  • 4
  • 5

clipboard.png

cmd_tbl_t命令型別結構體的段屬性設定如下:

**\#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 \*/**
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

clipboard.png

clipboard.png

* U_BOOT_CMD巨集實際上定義了一個cmd_tbl_t型別命令結構體,U_BOOT_CMD巨集的六個引數就是cmd_tbl_t型別命令結構體對應的六個成員。*

通過將每個命令的cmd_tbl_t命令型別結構體的段屬性的設定為.u_boot_cmd,可以確保uboot命令集中的所有命令在連結階段都會連結分配到.u_boot_cmd自定義段,當然命令在.u_boot_cmd自定義段內是隨機排序的。

總結:這個巨集其實就是定義了一個命令對應的結構體變數,這個變數名和巨集的第一個引數有關,因此只要巨集呼叫時傳參的第一個引數不同則定義的結構體變數不會重名。

使用例項如下:

U_BOOT_CMD(version, 1,1, do_version,"version - print monitor version\\n",NULL);
  • 1

巨集展開後為:

cmd_tbl_t __u_boot_cmd_**version** __attribute__ ((unused,section (“.u_boot_cmd“))) = {“**version”, 1, 1, do_version, “version - print monitor version\n”, NUL**L}

實驗過程如下:使用預處理(只編譯不連結)的方法檢視

(1)先新建一個tets.c,將上面的do_version命令函式複製過去。

clipboard.png

(2)在linux下,使用預處理(只編譯不連結)命令:gcc test.c -E -O test.i

clipboard.png

(3)開啟test.i檔案,檢視編譯後的巨集展開後的內容

clipboard.png

  • 2.find_cmd函式詳解

關鍵點:find_cmd函式命令的遍歷查詢:

clipboard.png

uboot命令集實際是分配在自定義段.u_boot_cmd中的,通過在uboot程式中宣告引用自定義段.u_boot_cmd的開始地址__u_boot_cmd_start和結束地址__u_boot_cmd_end,find_cmd函式就可以通過指標訪問命令集中的命令。

**cmd_tbl_t \*find_cmd (const char \*cmd)**

**{**

**cmd_tbl_t \*cmdtp;**

**cmd_tbl_t \*cmdtp_temp = \&__u_boot_cmd_start;//命令集的首地址**

**const char \*p;**

**int len;**

**int n_found = 0;**

**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 \*/**

**}**
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

(1)find_cmd函式的任務是從當前uboot的命令集中查詢是否有某個命令。如果找到則返回這個命令結構體的指標,如果未找到返回NULL。

**(2)函式的實現思路很簡單,如果不考慮命令帶點的情況(md.b md.w這種)就更簡單了。查詢命令的思路其實就是for迴圈遍歷陣列的思路,不同的是陣列的起始地址和結束地址是用地址值來給定的,陣列中的元素個數是結構體變數型別。**

五.uboot中增加自定義命令

(1)在已有uboot/common/command.c檔案中新增一個命令,叫:mycmd

(2)在已有的.c檔案中新增命令比較簡單,直接使用U_BOOT_CMD巨集即可新增命令,給命令提供一個do_xxx的對應的函式這個命令就齊活了。

在函式中使用argc和argv來驗證傳參。

clipboard.png

**(3)新增完成後要重新編譯工程(make distclean; make x210_sd_config; make),然後燒錄新的uboot去執行即可體驗新命令。**

clipboard.png

clipboard.png

clipboard.png

(1)建立cmd_xxxx.c檔案

在uboot/common目錄下新建一個命令檔案,叫cmd_aliya.c(對應的命令名就叫aliya,對應的函式就叫do_aliya函式),然後在c檔案中新增命令對應的U_BOOT_CMD巨集和函式。注意標頭檔案包含不要漏掉。

clipboard.png

(2)在common/Makefile中新增cmd_xxxx.o目標檔案

COBJS-y+=cmd_xxxx.o

在uboot/common/Makefile中新增上aston.o,目的是讓Make在編譯時能否把cmd_aston.c編譯連結進去。

clipboard.png

(3)重新編譯。重新編譯步驟是:make distclean; make x210_sd_config; make

clipboard.png

(4)linux下燒錄到SD卡

clipboard.png

clipboard.png

總結兩種新增命令的方式,可以得出 uboot的命令體系本身較為複雜,但開發者在uboot中新增命令是很簡單的,只需要新增cmd_xxx.c檔案,修改相應Makefile檔案就行。