嵌入式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_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;
二、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‘; } 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; }
命令的遍历查找:
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文件就行。