嵌入式linux开发uboot移植(五)――uboot命令体系
本文将根据SMDKV210开发板的三星官方uboot源码分析uboot的命令体系。内容 包括uboot的命令体系的实现机制,uboot命令是如何执行的,以及如何在uboot中添加一个自定义的命令。
一、uboot命令体系简介
uboot命令体系代码放在uboot/common中,包括cmd_xxx.c、command.c 、main.c源码文件。uboot实现命令体系的方法是每一个uboot命令对应一个函数,与shell的实现是一致的。
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类型命令结构体对应的六个成员。
使用实例如下:
U_BOOT_CMD(hello, 0, 0, do_hello, "hello world help info");
宏展开后为:
cmd_tbl_t __u_boot_cmd_hello __attribute___((unused, section(".u_boot_cmd"))) = {"hello", 0, 0, do_hello, "hello world help info"}
通过将每个命令的cmd_tbl_t命令类型结构体的段属性的设置为.u_boot_cmd,可以确保uboot命令集中的所有命令在链接阶段都会链接分配到.u_boot_cmd自定义段,当然命令在.u_boot_cmd自定义段内是随机排序的。
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_LONGHELPchar *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;
二、uboot命令的解析
uboot在启动进入BL2阶段后最终执行在main_loop函数,如果在自动倒计时时没有按下字符键,uboot将自动启动kernel;如果按下了字符键,uboot将进入人机交互命令行的主循环,执行读取、解析、执行命令。
main_loop函数中会先执行getenv ("bootcmd"),如果bootcmd环境变量设置的是启动kenel的命令,则在自动倒计时结束后如果没有字符输入,则uboot会自动执行bootcmd的命令,默认即执行启动kernel。如果自动倒计时结束前有字符输入,则进入命令行提示符状态阻塞等待用户输入命令。readline函数读取用户输入命令,进而通过run_command函数解析、运行命令。run_command函数会将接收的命令用parse_line函数解析,主要是将接收的命令字符串根据空格、分号分割成几部分,利用find_cmd函数遍历查找命令集,看uboot中是否有输入的命令,如果没有输入的命令,打印提示符。如果有当前输入的命令,调用当前输入命令的命令结构体的函数指针成员cmd执行命令对应的函数。
run_command函数源码如下:
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';}elsestr = sep;/* no more commands for next pass */#ifdef DEBUG_PARSERprintf ("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_PARSERprintf ("[%s]\n", finaltoken);#endifif (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;}
命令的遍历查找:
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 */}
三、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命令添加编程实践
uboot中添加命令可以在common/commmand.c中添加,也可以重新添加一个cmd_xxxx.c文件添加。
1、command.c文件中添加命令
int do_hello (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]){printf ("\n%s\n", "hello world");return 0;}U_BOOT_CMD(hello,1,1,do_hello,"hello - print hello world help info\n",NULL);
在command.c文件中添加命令会导致命令集混乱,因此不推荐。
2、添加cmd_xxxx.c文件添加命令
A、创建cmd_xxxx.c文件
B、添加头文件
#include <common.h>
#include <command.h>
C、添加do_xxx()函数和U_BOOT_CMD宏
int do_hello (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]){printf ("\n%s\n", "hello world");return 0;}U_BOOT_CMD(hello,1,1,do_hello,"hello - print hello world help info\n",NULL);
D、在common/Makefile中添加cmd_xxxx.o目标文件
COBJS-y+=cmd_xxxx.o
E、编译工程,测试hello命令
在common目录下添加cmd_xxx.c命令文件是比较规范的操作,便于uboot命令集的规范化,推荐方式。
uboot的命令体系本身较为复杂,但开发者在uboot中添加命令是很简单的,只需要添加cmd_xxx.c文件,修改相应Makefile文件就行。
本文出自 “生命不息,奋斗不止” 博客,转载请与作者联系!