有道云笔记地址:
http://note.youdao.com/noteshare?id=b1151c45c509a1e397f67b5d6c46dfc4&sub=8D928A46E09849B081DAF6819E0EF788
uboot命令体系的实现代码在uboot/common/cmd_xxx.c中。有若干个.c文件和命令体系有关。(还有command.c
main.c也是和命令有关的)。
uboot实现命令体系的方法是每一个uboot命令对应一个函数,与shell的实现是一致的。
(1)有些uboot的命令还支持传递参数。也就是说命令背后对应的函数接收的参数列表中有argc和argv,然后命令体系会把我们执行命令时的命令+参数(md
30000000 10)以argc(3)和argv(argv[0]=md, argv[1]=30000000
argv[2]=10)的方式传递给执行命令的函数。
1、从main_loop说起 (转载:http://blog.51cto.com/9291927/1792776)
(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执行命令对应的函数。
**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;**
**}**
关键点:find_cmd函数命令的遍历查找:下面会仔细分析这个函数
3.uboot命令的执行
run_command函数中解析、遍历查找命令后,如果找到会通过调用命令结构体的成员cmd函数指针调用当前命令对应的命令函数do_xxxx。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
CONFIG_CMD_ECHO宏可以决定定义的命令是否编译进当前uboot中,一般需要在开发板头文件中定义。命令定义必须包括命令结构体的定义和命令函数的定义。U_BOOT_CMD宏定义了命令结构体,do_echo函数则是命令的具体执行函数。
总结:
uboot的命令体系在工作时,一个命令对应一个cmd_tbl_t结构体的一个实例,然后uboot支持多少个命令,就需要多少个结构体实例。
uboot命令集中的每个命令对应一个cmd_tbl_t类型变量,用户输入一个命令时,uboot命令体系会到命令集中查找输入的命令,如果找到就执行,没有找到就提示命令没有找到信息。
**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)name:命令名称,字符串格式。
(2)maxargs:命令最多可以接收多少个参数
(3)repeatable:指示这个命令是否可重复执行。重复执行是uboot命令行的一种工作机制,就是直接按回车则执行上一条执行的命令。
(4)cmd:函数指针,命令对应的函数的函数指针,将来执行这个命令的函数时使用这个函数指针来调用。
(5)usage:命令的短帮助信息。对命令的简单描述。
(6)help:命令的长帮助信息。细节的帮助信息。
(7)complete:函数指针,指向这个命令的自动补全的函数。
uboot命令体系没有采用数组、链表来实现,
是每一个命令对应一个cmd_tbl_t命令类型结构体,通过对cmd_tbl_t命令类型结构体的段属性设置,将命令集存储在了程序中的自定义段.u_boot_cmd中,程序在链接阶段会将命令集分配在程序中的自定义段。
链接脚本中命令集自定义段如下:
\__u_boot_cmd_start = .;//命令集段起始地址
.u_boot_cmd : { \*(.u_boot_cmd) }//命令集中的命令
\__u_boot_cmd_end = .;//命令集段的结束地址
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 \*/**
* 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);
宏展开后为:
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命令函数复制过去。
(2)在linux下,使用预处理(只编译不链接)命令:gcc test.c -E -O test.i
(3)打开test.i文件,查看编译后的宏展开后的内容
关键点:find_cmd函数命令的遍历查找:
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)find_cmd函数的任务是从当前uboot的命令集中查找是否有某个命令。如果找到则返回这个命令结构体的指针,如果未找到返回NULL。
**(2)函数的实现思路很简单,如果不考虑命令带点的情况(md.b
md.w这种)就更简单了。查找命令的思路其实就是for循环遍历数组的思路,不同的是数组的起始地址和结束地址是用地址值来给定的,数组中的元素个数是结构体变量类型。**
(1)在已有uboot/common/command.c文件中添加一个命令,叫:mycmd
(2)在已有的.c文件中添加命令比较简单,直接使用U_BOOT_CMD宏即可添加命令,给命令提供一个do_xxx的对应的函数这个命令就齐活了。
在函数中使用argc和argv来验证传参。
**(3)添加完成后要重新编译工程(make distclean; make x210_sd_config;
make),然后烧录新的uboot去运行即可体验新命令。**
(1)创建cmd_xxxx.c文件
在uboot/common目录下新建一个命令文件,叫cmd_aliya.c(对应的命令名就叫aliya,对应的函数就叫do_aliya函数),然后在c文件中添加命令对应的U_BOOT_CMD宏和函数。注意头文件包含不要漏掉。
(2)在common/Makefile中添加cmd_xxxx.o目标文件
COBJS-y+=cmd_xxxx.o
在uboot/common/Makefile中添加上aston.o,目的是让Make在编译时能否把cmd_aston.c编译链接进去。
(3)重新编译。重新编译步骤是:make distclean; make x210_sd_config; make
(4)linux下烧录到SD卡
总结两种添加命令的方式,可以得出 uboot的命令体系本身较为复杂,但开发者在uboot中添加命令是很简单的,只需要添加cmd_xxx.c文件,修改相应Makefile文件就行。