u-boot的命令詳解

進入U-BOOT的控制界面後,可以運行各種命令,比如下載文件到內存,擦除、讀寫FLASH,運行內存、NOR FLASH中的程序,查看、修改、比較內存中的數據等。U-BOOT的命令可以說是U-BOOT學習的核心,啓動kernel就是通過U-BOOT命令來啓動的。
本文記錄了關於U-BOOT命令的學習和分析。U-BOOT命令的學習大致可以分爲三個部分:U-BOOT命令的存儲格式、命令的檢索和命令的執行。

一、U-BOOT命令的存儲格式

U-BOOT是一個結構體,由6個成員組成。
@name: 命令的名稱
@maxargs:命令參數的最大個數
@repeatable:命令是否允許repeat
@cmd:函數指針,指向命令的執行函數
@usage:命令的簡介
@help:命令的幫助信息

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
};

爲方便使用,定義了一個宏,這個宏用來定義U-BOOT命令。

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

#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}

以do_bootm命令爲例,其定義方法爲:

U_BOOT_CMD(
    bootm,  CFG_MAXARGS,    1,  do_bootm,
    "bootm   - boot application image from memory\n",
    "[addr [arg ...]]\n    - boot application image stored in memory\n"
    "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
    "\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
    "\tWhen booting a Linux kernel which requires a flat device-tree\n"
    "\ta third argument is required which is the address of the of the\n"
    "\tdevice-tree blob. To boot that kernel without an initrd image,\n"
    "\tuse a '-' for the second argument. If you do not pass a third\n"
    "\ta bd_info struct will be passed instead\n"
#endif
);

經編譯器預編譯後,do_bootm的命令成爲:

cmd_tbl_t __u_boot_cmd_bootm 
__attribute__ ((unused,section (".u_boot_cmd")))= 
{bootm, CFG_MAXARGS, 1, do_bootm, "boom...\n", "[addr...\n"}

__u_boot_cmd_bootm結構體的屬性爲
attribute ((unused,section (“.u_boot_cmd”)))
使得U-BOOT所有的命令都放到一段名爲”.u_boot_cmd”存儲空間中。

二、U-BOOT命令的檢索

下圖是命令檢索的過程。
這裏寫圖片描述

命令檢索函數find_cmd定義在common/command.c中。
參數爲需要檢索的命令,若可以找到返回該命令的地址,否則返回NULL。

cmd_tbl_t *find_cmd (const char *cmd)
{
    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);

    // 從u-boot命令存儲的首地址__u_boot_cmd_start至末尾地址__u_boot_cmd_end開始檢索命令
    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 */
}

三、U-BOOT命令的執行

int run_command (const char *cmd, int flag)
{
    cmd_tbl_t *cmdtp;
    char cmdbuf[CFG_CBSIZE];    /* working copy of cmd      */
    char *token;            /* start of token in cmdbuf */
    char *sep;          /* end of token (separator) in cmdbuf */
    char finaltoken[CFG_CBSIZE];
    char *str = cmdbuf;
    char *argv[CFG_MAXARGS + 1];    /* NULL terminated  */
    int argc, inquotes;
    int repeatable = 1;
    int rc = 0;

    clear_ctrlc();      /* forget any previous Control C */

    // 若爲空指令則退出
    if (!cmd || !*cmd) {
        return -1;  /* empty command */
    }

    // 若指令超長(大於等於CFG_CBSIZE),則退出
    if (strlen(cmd) >= CFG_CBSIZE) {
        puts ("## Command too long!\n");
        return -1;
    }

    // 拷貝命令到cmdbuf
    strcpy (cmdbuf, cmd);

    /* Process separators and check for invalid
     * repeatable commands
     */

    while (*str) {

        /*
         * Find separator, or string end
         * Allow simple escape of ';' by writing "\;"
         */
         // 找出當前str所指向的命令的結尾,忽略註釋部分('xxxxxxx')和(\;),
         // sep指向末尾 ';' 或 '\0'
        for (inquotes = 0, sep = str; *sep; sep++) {
            // 用inquotes表示當前索引是否在註釋部分。
            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;
        // sep指向的內容不是'\0',表明還有下一條命令。
        if (*sep) {
            str = sep + 1;  /* start of command for next pass */
            *sep = '\0';
        }
        // sep爲'\0',表明命令結束。
        else
            str = sep;  /* no more commands for next pass */


        /* find macros in this token and replace them */
        // 尋找在token中的宏,並用宏代表的值替換,如token爲test$(macro),
        // macro的值爲MACRO,則輸出結果finaltoken爲testMACRO
        process_macros (token, finaltoken);

        /* Extract arguments */
        // 提取命令,將finaltoken表示的命令放到argv指向的數組中,如finaltoken表示的命令爲cmd para1 para2,
        // 則argv[0]=cmd, argv[1]=para1, argv[2]=para2,返回值爲分解命令的個數3. 
        if ((argc = parse_line (finaltoken, argv)) == 0) {
            rc = -1;    /* no command at all */
            continue;
        }

        /* Look up command in command table */
        // 在u-boot命令表中查找argv[0]中的命令,若找到則將地址賦給cmdtp,否則返回NULL
        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;
        }

        // 執行該命令。
        /* OK - call function to do the command */
        if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
            rc = -1;
        }

        repeatable &= cmdtp->repeatable;

        /* Did the user stop this? */
        if (had_ctrlc ())
            return 0;   /* if stopped then not repeatable */
    }

    return rc ? rc : repeatable;
}

parse_line()函數用來解析命令
@char *line,是命令行字符串的首地址,命令行的命令或參數以’ ‘或’\t’分開,以’\0’爲結尾。
@char *argv[],存放解析後的命令
@return value: number of argv element.
例如. *line = “bootm 0x30007FC0”
則argv[0] = “bootm”
argv[1] = “0x30007FC0”
return value is 2

int parse_line (char *line, char *argv[])
{
    int nargs = 0;

    while (nargs < CFG_MAXARGS) {
        /* skip any white space */
        // 忽略命令行中的空格和製表符
        while ((*line == ' ') || (*line == '\t')) {
            ++line;
        }

        // 若沒有其它的參數了。
        if (*line == '\0') {    /* end of line, no more args    */
            argv[nargs] = NULL;
#ifdef DEBUG_PARSER
        printf ("parse_line: nargs=%d\n", nargs);
#endif
            return (nargs);
        }
        // 將查找到的命令的首地址賦給argv。
        argv[nargs++] = line;   /* begin of argument string */

        /* find end of string */
        // 找到當前參數的末尾,末尾有三種情況:'\0', ' ' or '\t'
        while (*line && (*line != ' ') && (*line != '\t')) {
            ++line;
        }

        // 若再沒有其他的參數了
        if (*line == '\0') {    /* end of line, no more args    */
            argv[nargs] = NULL;
#ifdef DEBUG_PARSER
        printf ("parse_line: nargs=%d\n", nargs);
#endif
            return (nargs);
        }

        // 若還有其他的參數,則先結束當前的參數。
        *line++ = '\0';     /* terminate current arg     */
    }

    printf ("** Too many args (max. %d) **\n", CFG_MAXARGS);

#ifdef DEBUG_PARSER
    printf ("parse_line: nargs=%d\n", nargs);
#endif
    return (nargs);
}

四、總結

U-BOOT命令是結構體形式的,通過關鍵字attribute ((unused,section (“.u_boot_cmd”)))將所有定義的命令都存放在一塊連續的存儲空間中,這種存儲方式的好處是減少了命令檢索的時間。

發佈了31 篇原創文章 · 獲贊 47 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章