其中U_BOOT_CMD命令格式如下:
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")
各個參數的意義如下:
-
name:命令名,非字符串,但在U_BOOT_CMD中用“ #”符號轉化為字符串
-
maxargs:命令的最大參數個數
-
repeatable:是否自動重復(按Enter鍵是否會重復執行)
-
command:該命令對應的響應函數指針
-
usage:簡短的使用說明(字符串)
-
help:較詳細的使用說明(字符串)
U_BOOT_CMD宏在include/command.h中定義:
-
-
cmd_tbl_t __u_boot_cmd_
“##”
與“#”
都是預編譯操作符,“##”
有字符串連接的功能,“#”
表示后面緊接着的是一個字符串。
其中Struct_Section在include/command.h中定義如下:
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
凡是帶有attribute ((unused,section (“.u_boot_cmd”))屬性聲明的變量都將被存放在”.u_boot_cmd”段中,並且即使該變量沒有在代碼中顯式的使用編譯器也不產生警告信息。
在u-Boot連接腳本 u-boot.lds中定義了”.u_boot_cmd”段:
-
. = .;
-
__u_boot_cmd_start = .; /*將 __u_boot_cmd_start指定為當前地址 */
-
.u_boot_cmd : { *(.u_boot_cmd) }
-
__u_boot_cmd_end = .; /* 將__u_boot_cmd_end指定為當前地址 */
這表明帶有“.u_boot_cmd”聲明的函數或變量將存儲在“u_boot_cmd”段。
這樣只要將u-boot所有命令對應的cmd_tbl_t變量加上“.u_boot_cmd”聲明,編譯器就會自動將其放在“u_boot_cmd”段,查找cmd_tbl_t變量時只要在 __u_boot_cmd_start 與 __u_boot_cmd_end 之間查找就可以了。
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 * const []);
-
char *usage; /* Usage message (short) */
-
-
char *help; /* Help message (long) */
-
-
-
/* do auto completion on the arguments */
-
int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
-
-
};
-
-
typedef struct cmd_tbl_s cmd_tbl_t;
一個cmd_tbl_t結構體變量包含了調用一條命令的所需要的信息。
以“boot”命令的定義經過宏展開后如下:
cmd_tbl_t __u_boot_cmd_boot __attribute__ ((unused,section (".u_boot_cmd"))) = {boot, 1, 1, do_bootd, "boot - boot default, i.e., run 'bootcmd'\n", " NULL"}
-
int do_bootd (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
-
{
-
int rcode = 0;
-
-
if (run_command(getenv("bootcmd"), flag) < 0)
-
rcode = 1;
-
return rcode;
-
}
在2013-10-rc3的最新u-boot版本中,對此有改動,我們先看定義(include/command.h)
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) { #_name, _maxargs, _rep, _cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) } #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) ll_entry_declare(cmd_tbl_t, _name, cmd) = U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp); #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
#define ll_entry_declare(_type, _name, _list) _type _u_boot_list_2_##_list##_2_##_name __aligned(4) __attribute__((unused, section(".u_boot_list_2_"#_list"_2_"#_name)))
看上去挺復雜的,其實就是將cmd放在了u_boot_list這類開頭的section里,所以我們只需在u-boot.lds里添加:. = ALIGN(4); .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); }
run_command 命令分析:
u-boot啟動第二階段最后跳到main_loop函數中循環
-
s = getenv ( "bootcmd");
-
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
-
......
-
run_command (s, 0);
-
......
-
}
從main_loop中我們知道,如果bootdelay時間內未按下按鍵則啟動Linux內核,按下按鍵則進入uboot命令行等待用戶輸入命令。
用戶輸入命令則調取run_command函數,在該函數中有下面幾個比較重要的點:
1. 從注釋我們很容易知道這段代碼是在對命令進行分離,並且u-boot支持’;’分離命令。
-
/*
-
* 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;
-
}
2. 分離參數
-
/* Extract arguments */
-
if ((argc = parse_line (finaltoken, argv)) == 0) {
-
rc = -1; /* no command at all */
-
continue;
-
}
3. 用第一個參數argv[0]在命令列表中尋找對應的命令,並返回一個cmd_tbl_t類型的實例。我們可以猜到這個結構體應該保函了有關命令的一系列內容。
-
/* 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;
-
}
在common/command.c 中查看find_cmd函數
-
/*__u_boot_cmd_start與__u_boot_cmd_end間查找命令,並返回cmdtp->name命令的cmd_tbl_t結構。*/
-
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++;
-
}
-
}
五、總結命令執行過程
① 在u-boot控制台中輸入“boot”命令執行時,u-boot控制台接收輸入的字符串“boot”,傳遞給run_command函數。
② run_command函數調用common/command.c中實現的find_cmd函數在__u_boot_cmd_start與__u_boot_cmd_end間查找命令,並返回boot命令的cmd_tbl_t結構。
③ 然后run_command函數使用返回的cmd_tbl_t結構中的函數指針調用boot命令的響應函數do_bootd,從而完成了命令的執行。
六、自制u-boot命令
__attribute__這個關鍵詞是GNU編譯器中的編譯屬性,ARM編譯器也支持這個用法。__attribute__主要用於改變所聲明或定義的函數或 數據的特性,它有很多子項,用於改變作用對象的特性。比如對函數,noline將禁止進行內聯擴展、noreturn表示沒有返回值、pure表明函數除 返回值外,不會通過其它(如全局變量、指針)對函數外部產生任何影響。當然,__attribute__肯定有很多的用法,今天就用到了section部分,所以就只針對這個做一些記錄。
提到section,就得說RO RI ZI了,在ARM編譯器編譯之后,代碼被划分為不同的段,RO Section(ReadOnly)中存放代碼段和常量,RW Section(ReadWrite)中存放可讀寫靜態變量和全局變量,ZI Section(ZeroInit)是存放在RW段中初始化為0的變量。
於是本文的大體意思就清晰了,__attribute__((section("section_name"))),其作用是將作用的函數或數據放入指定名為"section_name"對應的段中。
MDK給出的幫助文檔如下,他將__attribute__的用法歸類到編譯器特性里,以變量和函數的兩種用法做區分。
1.編譯時為變量指定段
- __attribute__((section("name")))
- RealView Compilation Tools for µVision Compiler Reference Guide Version 4.0
- Home > Compiler-specific Features > Variable attributes > __attribute__((section("name")))
- 4.5.6. __attribute__((section("name")))
- Normally, the ARM compiler places the objects it generates in sections like data and bss. However, you might require additional data sections or you might want a variable to appear in a special section, for example, to map to special hardware. The section attribute specifies that a variable must be placed in a particular data section. If you use the section attribute, read-only variables are placed in RO data sections, read-write variables are placed in RW data sections unless you use the zero_init attribute. In this case, the variable is placed in a ZI section.
- Note
- This variable attribute is a GNU compiler extension supported by the ARM compiler.
- Example
- /* in RO section */
- const int descriptor[3] __attribute__ ((section ("descr"))) = { 1,2,3 };
- /* in RW section */
- long long rw[10] __attribute__ ((section ("RW")));
- /* in ZI section *
- long long altstack[10] __attribute__ ((section ("STACK"), zero_init));/
2.編譯時為函數指定段
- __attribute__((section("name")))
- RealView Compilation Tools for µVision Compiler Reference Guide Version 4.0
- Home > Compiler-specific Features > Function attributes > __attribute__((section("name")))
- 4.3.13. __attribute__((section("name")))
- The section function attribute enables you to place code in different sections of the image.
- Note
- This function attribute is a GNU compiler extension that is supported by the ARM compiler.
- Example
- In the following example, Function_Attributes_section_0 is placed into the RO section new_section rather than .text.
- void Function_Attributes_section_0 (void)
- __attribute__ ((section ("new_section")));
- void Function_Attributes_section_0 (void)
- {
- static int aStatic =0;
- aStatic++;
- }
- In the following example, section function attribute overrides the #pragma arm section setting.
- #pragma arm section code="foo"
- int f2()
- {
- return 1;
- } // into the 'foo' area
- __attribute__ ((section ("bar"))) int f3()
- {
- return 1;
- } // into the 'bar' area
- int f4()
- {
- return 1;
- } // into the 'foo' area
- #pragma arm section