嵌入式linux开发uboot移植(五)――uboot命令体系

时间:2021-03-22 18:38:35

嵌入式linux开发uboot移植(五)――uboot命令体系

    本文将根据SMDKV210开发板的三星官方uboot源码分析uboot的命令体系。内容 包括uboot的命令体系的实现机制,uboot命令是如何执行的,以及如何在uboot中添加一个自定义的命令。

一、uboot命令体系简介

        uboot命令体系代码放在uboot/common中,包括cmd_xxx.ccommand.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_endfind_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_xxxxuboot命令的定义模板示例如下:

#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文件就行。


本文出自 “生命不息,奋斗不止” 博客,转载请与作者联系!