uboot流程——命令行模式以及命令处理介绍

2024-08-30 17:38

本文主要是介绍uboot流程——命令行模式以及命令处理介绍,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

[uboot] (第六章)uboot流程——命令行模式以及命令处理介绍

以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为例

[uboot] uboot流程系列: 
[project X] tiny210(s5pv210)上电启动流程(BL0-BL2) 
[project X] tiny210(s5pv210)从存储设备加载代码到DDR 
[uboot] (第一章)uboot流程——概述 
[uboot] (第二章)uboot流程——uboot-spl编译流程 
[uboot] (第三章)uboot流程——uboot-spl代码流程 
[uboot] (第四章)uboot流程——uboot编译流程 
[uboot] (第五章)uboot流程——uboot启动流程 
[uboot] (番外篇)global_data介绍 
[uboot] (番外篇)uboot relocation介绍

建议先看《[uboot] (第五章)uboot流程——uboot启动流程》

=================================================================================

一、说明

命令行模式就是指uboot执行完一切必要的初始化过程之后,等待终端输入命令和处理命令的一个模式。 
所以后面的章节,我们先介绍命令如何存储以及处理,再简单说明命令行模式是如何工作的

1、需要打开哪些宏

  • CONFIG_CMDLINE 
    表示是否支持命令行模式,定义如下: 
    ./configs/bubblegum_defconfig:201:CONFIG_CMDLINE=y 
    ./configs/tiny210_defconfig:202:CONFIG_CMDLINE=y

  • CONFIG_SYS_GENERIC_BOARD 
    用于定义板子为通用类型的板子。打开这个宏之后,common/board_f.c和common/board_r.c才会被编译进去,否则,需要自己实现。 
    ./configs/bubblegum_defconfig:7:CONFIG_SYS_GENERIC_BOARD=y 
    ./configs/tiny210_defconfig:7:CONFIG_SYS_GENERIC_BOARD=y 
    打开之后,board_r.c中最终会执行run_main_loop进入命令行模式。具体参考《[uboot] (第五章)uboot流程——uboot启动流程》。

  • CONFIG_SYS_PROMPT 
    命令行模式下的提示符。在tiny210中定义如下: 
    ./configs/tiny210_defconfig:203:CONFIG_SYS_PROMPT=”TINY210 => “

  • CONFIG_SYS_HUSH_PARSER 
    表示使用使用hush来对命令行进行解析。后续会继续说明。在tiny210中定义如下: 
    ./include/configs/tiny210.h:121:#define CONFIG_SYS_HUSH_PARSER /* use “hush” command parser */

  • 对应命令需要打开对应命令的宏 
    以bootm命令为例,如果要支持bootm,则需要打开CONFIG_CMD_BOOTM宏,具体可以参考cmd/Makefile 
    ./configs/bubblegum_defconfig:226:CONFIG_CMD_BOOTM=y 
    ./configs/tiny210_defconfig:226:CONFIG_CMD_BOOTM=y

2、结合以下几个问题来看后面的章节

  • 命令的数据结构,也就是代码里面如何表示一个命令?
  • 如何定义一个命令,我们如何添加一个自己的命令?
  • 命令的存放和获取?
  • 命令行模式的处理流程?

3、API

  • U_BOOT_CMD 
    #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) 
    定义一个命令。
  • cmd_process 
    enum command_ret_t cmd_process(int flag, int argc, char * const argv[], int *repeatable, ulong *ticks) 
    命令的处理函数,命令是作为argv[0]传入。

具体参数意义和实现参考后面。

二、命令处理数据结构的存放

1、数据结构

uboot把所有命令的数据结构都放在一个表格中,我们后续称之为命令表。表中的每一项代表着一个命令,其项的类型是cmd_tbl_t。 
数据结构如下:

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 * const []);char        *usage;     /* Usage message    (short) */
#ifdef  CONFIG_SYS_LONGHELPchar        *help;      /* Help  message    (long)  */
#endif
};typedef struct cmd_tbl_s    cmd_tbl_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

参数说明如下:

  • name:定义一个命令的名字。 其实就是执行的命令的字符串。这个要注意。
  • maxargs:这个命令支持的最大参数
  • repeatable:是否需要重复
  • cmd:命令处理函数的地址
  • usage:字符串,使用说明
  • help:字符串,帮助

2、在dump里面的表示

通过以下命令解析出dump。

arm-none-linux-gnueabi-objdump -D u-boot > uboot_objdump.txt
  • 1

以bootm命令为例,提取一部分信息,加上了注释信息:

23e364cc <_u_boot_list_2_cmd_2_bootm>:                  
// bootm命令对应的数据结构符号是_u_boot_list_2_cmd_2_bootm,后续我们会说明,其起始地址是0x23e364cc
23e364cc:   23e2bbc2    mvncs   fp, #198656 ; 0x30800
// 这里对应第一个成员name,其地址是0x23e2bbc2
23e364d0:   00000040    andeq   r0, r0, r0, asr #32
// 这里对应第二个成员maxargs,maxargs=0x40
23e364d4:   00000001    andeq   r0, r0, r1 
// 这里对应第三个成员repeatable,repeatable=1
23e364d8:   23e02028    mvncs   r2, #40 ; 0x28 
// 这里对应第四个成员cmd,cmd命令处理函数的地址是0x23e02028,和下面的do_bootm的地址一致!!!
23e364dc:   23e2bbc8    mvncs   fp, #204800 ; 0x32000
// 这里对应第五个成员usage,usage字符串的地址是0x23e2bbc8
23e364e0:   23e34cb0    mvncs   r4, #45056  ; 0xb000
// 这里对应第六个成员help,help字符串的地址是0x23e34cb023e02028 <do_bootm>:    
23e34cb0 <bootm_help_text>:  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

根据上述dump就可以把bootm命令的数据结构定义都找出来了。

3、命令数据结构在u-boot.map符号表中的位置定义

通过查看u-boot.map,过滤出和u_boot_list中cmd相关的部分,对应符号表如下:

 *(SORT(.u_boot_list*)).u_boot_list_2_cmd_10x23e3649c        0x0 cmd/built-in.o.u_boot_list_2_cmd_10x23e3649c        0x0 common/built-in.o.u_boot_list_2_cmd_2_bootefi0x23e3649c       0x18 cmd/built-in.o0x23e3649c                _u_boot_list_2_cmd_2_bootefi.u_boot_list_2_cmd_2_bootelf0x23e364b4       0x18 cmd/built-in.o0x23e364b4                _u_boot_list_2_cmd_2_bootelf.u_boot_list_2_cmd_2_bootm0x23e364cc       0x18 cmd/built-in.o0x23e364cc                _u_boot_list_2_cmd_2_bootm
.......u_boot_list_2_cmd_2_true0x23e3670c       0x18 cmd/built-in.o0x23e3670c                _u_boot_list_2_cmd_2_true.u_boot_list_2_cmd_2_version0x23e36724       0x18 cmd/built-in.o0x23e36724                _u_boot_list_2_cmd_2_version.u_boot_list_2_cmd_30x23e3673c        0x0 cmd/built-in.o.u_boot_list_2_cmd_30x23e3673c        0x0 common/built-in.o
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

可以观察到命令表是被定义在0x23e3649c( .u_boot_list_2_cmd_1)到0x23e3673c( .u_boot_list_2_cmd_3)的位置中。 
并且每一个项占用了24个字节,和cmd_tbl_t结构的大小是一致的。 
注意,根据注释,.u_boot_list_2_cmd_1和.u_boot_list_2_cmd_3这两个符号是由链接器自己生成的。 
这里简单有个印象,bootm命令对应的数据结构符号是u_boot_list_2_cmd_2_bootm

4、如何定义一个命令

(1)我们以bootm命令的定义为例: 
cmd/bootm.c中

U_BOOT_CMD(bootm,  CONFIG_SYS_MAXARGS, 1,  do_bootm,"boot application image from memory", bootm_help_text
);
// bootm就是我们的命令字符串
// 在tiny210.h中定义了最大参数数量是64
// #define CONFIG_SYS_MAXARGS              64      /* max number of command args */
// 1表示重复一次
// 对应命令处理函数是do_bootm
// usage字符串是"boot application image from memory"
// help字符串是bootm_help_text定义的字符串。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

通过上面,可以看出是通过U_BOOT_CMD来定义了bootm命令对应的数据结构!!! 
并且命令处理函数的格式如下:

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
  • 1

当命令处理函数执行成功时,需要返回0.返回非0值 
所以可以参照如上方式自己定义一个命令。

5、介绍一下U_BOOT_CMD的实现

include/common.h

#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)      \U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \ll_entry_declare(cmd_tbl_t, _name, cmd) =            \U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,    \_usage, _help, _comp);#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,      \_usage, _help, _comp)           \{ #_name, _maxargs, _rep, _cmd, _usage,         \_CMD_HELP(_help) _CMD_COMPLETE(_comp) }#define ll_entry_declare(_type, _name, _list)                \_type _u_boot_list_2_##_list##_2_##_name __aligned(4)        \__attribute__((unused,                \section(".u_boot_list_2_"#_list"_2_"#_name)))ll_entry_declare(cmd_tbl_t, _name, cmd)
// 以bootm为例
// _type=cmd_tbl_t
// _name=bootm
// _list=cmd
// 这里最终会转化为如下数据结构
// 
// cmd_tbl_t _u_boot_list_2_cmd_2_bootm=
// {
// _name=bootm,
// _maxargs=CONFIG_SYS_MAXARGS,
// _rep=1,
// _cmd=do_bootm,
// _usage="boot application image from memory",
// _help=bootm_help_text,
// _comp=NULL,
// }
// 并且这个数据结构给存放到了 .u_boot_list_2_cmd_2_bootm段中!!!和我们上述的完全一致。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

三、命令的处理

1、简单流程说明:

假设传进来的命令是cmd。 
* 获取命令表 
* 从命令表中搜索和cmd匹配的项 
* 执行对应项中的命令

后续我们我们分成“查找cmd对应的表项”、“执行对应表项中的命令”两部分进行说明

2、查找cmd对应的表项——find_cmd

通过find_cmd可以获取命令对应的命令表项cmd_tbl_t 。

(1)原理简单说明 
前面我们知道了可以观察到命令表是被定义在 .u_boot_list_2_cmd_1到.u_boot_list_2_cmd_3的位置中。所以我们从这个区间获取命令表。 
并且根据表项中的name是否和传进来的命令是否匹配来判断是否是我们需要的表项。

(2)对应代码 
common/command.c

cmd_tbl_t *find_cmd(const char *cmd)
{cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
// 获取命令表的地址,start表示指向命令表的指针,具体实现看后面const int len = ll_entry_count(cmd_tbl_t, cmd);
// 获取命令表的长度,具体实现看后面return find_cmd_tbl(cmd, start, len);
// 以命令表的指针和命令表的长度为参数,查找和cmd匹配的表项,也就是cmd_tbl_t结构,并返回给调用者。
}/* find command table entry for a command */
cmd_tbl_t *find_cmd_tbl(const char *cmd, cmd_tbl_t *table, int table_len)
{
#ifdef CONFIG_CMDLINEcmd_tbl_t *cmdtp;cmd_tbl_t *cmdtp_temp = table;  /* Init value */const char *p;int len;int n_found = 0;if (!cmd)return NULL;/** Some commands allow length modifiers (like "cp.b");* compare command name only until first dot.*/len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);for (cmdtp = table; cmdtp != table + table_len; cmdtp++) {
// 通过指针递增的方式,查找table中的每一个cmd_tbl_t if (strncmp(cmd, cmdtp->name, len) == 0) {if (len == strlen(cmdtp->name))return cmdtp;   /* full match */
// 如果是命令字符串和表项中的name完全匹配,包括长度一致的,则直接返回cmdtp_temp = cmdtp; /* abbreviated command ? */n_found++;
// 如果命令字符串和表项中的name的前面部分匹配的话(我们称为部分匹配),暂时保存下来,并且记录有几个这样的表项,主要是为了支持自动补全的功能}}if (n_found == 1) {         /* exactly one match */return cmdtp_temp;
// 如果部分匹配的表项是唯一的话,则可以将这个表项返回,主要是为了支持自动补全的功能}
#endif /* CONFIG_CMDLINE */return NULL;    /* not found or ambiguous command */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

include/linker_lists.h

#define ll_entry_start(_type, _list)                    \
({                                  \static char start[0] __aligned(4) __attribute__((unused,    \section(".u_boot_list_2_"#_list"_1")));         \(_type *)&start;                        \
})
// 因为传进来的_list是cmd,所以这里就是获取命令表的起始地址,也就是.u_boot_list_2_cmd_1的地址,#define ll_entry_end(_type, _list)                  \
({                                  \static char end[0] __aligned(4) __attribute__((unused,      \section(".u_boot_list_2_"#_list"_3")));         \(_type *)&end;                          \
})
// 因为传进来的_list是cmd,所以这里就是获取命令表的结束地址,也就是.u_boot_list_2_cmd_3的地址,#define ll_entry_count(_type, _list)                    \({                              \_type *start = ll_entry_start(_type, _list);        \_type *end = ll_entry_end(_type, _list);        \unsigned int _ll_result = end - start;          \_ll_result;                     \})
// 因为传进来的_list是cmd,所以这里就是计算命令表的长度
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3、执行对应表项中的命令——cmd_call

通过调用cmd_call可以执行命令表项cmd_tbl_t 中的命令 
common/command.c

static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{int result;result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
// 直接执行命令表项cmd_tbl_t 中的cmd命令处理函数if (result)debug("Command failed, result=%d\n", result);
// 命令返回非0值时,报错return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4、命令处理函数——cmd_process

代码如下: 
common/command.c

enum command_ret_t cmd_process(int flag, int argc, char * const argv[],int *repeatable, ulong *ticks)
{enum command_ret_t rc = CMD_RET_SUCCESS;cmd_tbl_t *cmdtp;/* Look up command in command table */cmdtp = find_cmd(argv[0]);// 第一个参数argv[0]表示命令,调用find_cmd获取命令对应的表项cmd_tbl_t。if (cmdtp == NULL) {printf("Unknown command '%s' - try 'help'\n", argv[0]);return 1;}/* found - check max args */if (argc > cmdtp->maxargs)rc = CMD_RET_USAGE;// 检测参数是否正常/* If OK so far, then do the command */if (!rc) {if (ticks)*ticks = get_timer(0);rc = cmd_call(cmdtp, flag, argc, argv);// 调用cmd_call执行命令表项中的命令,成功的话需要返回0值if (ticks)*ticks = get_timer(*ticks);// 判断命令执行的时间*repeatable &= cmdtp->repeatable;// 这个命令执行的重复次数存放在repeatable中的}if (rc == CMD_RET_USAGE)rc = cmd_usage(cmdtp);// 命令格式有问题,打印帮助信息return rc;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

返回0表示执行成功,返回非0值表示执行失败。 
后续需要执行一个命令的时候,直接调用cmd_process即可。

四、命令行模式的流程

命令行模式有两种简单的方式。正常模式是简单地获取串口数据、解析和处理命令。 
hush模式则是指命令的接收和解析使用busybox的hush工具,对应代码是hush.c。 
关于hush模式的作用和使用自己还不是很清楚,还要再研究一下。这里简单的写一点流程。

1、入口

通过《[uboot] (第五章)uboot流程——uboot启动流程》,我们知道了uboot在执行完所有初始化程序之后,调用run_main_loop进入主循环。 
通过主循环进入了命令行模式。 
common/board_r.c

static int run_main_loop(void)
{/* main_loop() can return to retry autoboot, if so just run it again */for (;;)main_loop();
// 这里进入了主循环,而autoboot也是在主循环里面实现return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

main_loop实现如下: 
这里了解一个缩写,cli,Command Line Interface,命令行接口,命令行界面。 
common/main.c

void main_loop(void)
{const char *s;bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
// 这里用于标记uboot的进度,对于tiny210来说起始什么都没做cli_init();
// cli的初始化,主要是hush模式下的初始化run_preboot_environment_command();
// preboot相关的东西,后续有用到再说明s = bootdelay_process();if (cli_process_fdt(&s))cli_secure_boot_cmd(s);autoboot_command(s);
// autoboot的东西,后续使用autoboot的时候再专门说明cli_loop();
// 进入cli的循环模式,也就是命令行模式panic("No CLI available");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

通过调用cli_loop进入了命令行模式,并且不允许返回。 
common/cli.c

void cli_loop(void)
{
#ifdef CONFIG_SYS_HUSH_PARSERparse_file_outer();
// 这里进入hush命令模式/* This point is never reached */for (;;);
#elif defined(CONFIG_CMDLINE)
// 这里进入通用命令行模式cli_simple_loop();
#else
// 说明没有打开CONFIG_CMDLINE宏,无法进入命令行模式,直接打印一个提示printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");
#endif /*CONFIG_SYS_HUSH_PARSER*/
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

最终通过cli_simple_loop进入通用命令行模式,或者通过parse_file_outer进入hush命令行模式。 
因为通用命令行模式相对较为简单,所以这边先说明通用命令行模式。

2、通用命令行模式

代码如下:

void cli_simple_loop(void)
{static char lastcommand[CONFIG_SYS_CBSIZE + 1] = { 0, };int len;int flag;int rc = 1;for (;;) {len = cli_readline(CONFIG_SYS_PROMPT);
// 打印CONFIG_SYS_PROMPT,然后从串口读取一行作为命令,存储在console_buffer中
// 在tiny210中定义如下:./configs/tiny210_defconfig:203:CONFIG_SYS_PROMPT="TINY210 => "flag = 0;   /* assume no special flags for now */if (len > 0)strlcpy(lastcommand, console_buffer,CONFIG_SYS_CBSIZE + 1);
// 如果获得了一个新行时,命令会存储在console_buffer,将命令复制到lastcommand中else if (len == 0)flag |= CMD_FLAG_REPEAT;
// 只是得到一个单纯的换行符时,设置重复标识,后续重复执行上一次命令if (len == -1)puts("<INTERRUPT>\n");
// 获得非数据值时,直接打印中断elserc = run_command_repeatable(lastcommand, flag);
// 否则,执行lastcommand中的命令if (rc <= 0) {/* invalid command or not repeatable, forget it */lastcommand[0] = 0;
// 命令执行出错时,清空lastcommand,防止下一次重复执行这个命令}}
}/* run_command_repeatable实现如下 */
int run_command_repeatable(const char *cmd, int flag)
{return cli_simple_run_command(cmd, flag);
}/* cli_simple_run_command实现如下 */
int cli_simple_run_command(const char *cmd, int flag)
{char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd      */char *token;            /* start of token in cmdbuf */char *sep;          /* end of token (separator) in cmdbuf */char finaltoken[CONFIG_SYS_CBSIZE];char *str = cmdbuf;char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated  */int argc, inquotes;int repeatable = 1;int rc = 0;debug_parser("[RUN_COMMAND] cmd[%p]=\"", cmd);if (DEBUG_PARSER) {/* use puts - string may be loooong */puts(cmd ? cmd : "NULL");puts("\"\n");}clear_ctrlc();      /* forget any previous Control C */if (!cmd || !*cmd)return -1;  /* empty command */if (strlen(cmd) >= CONFIG_SYS_CBSIZE) {puts("## Command too long!\n");return -1;}strcpy(cmdbuf, cmd);/* Process separators and check for invalid* repeatable commands*/debug_parser("[PROCESS_SEPARATORS] %s\n", cmd);while (*str) {
// 这里过滤掉一些对命令进行处理的部分代码/* find macros in this token and replace them */cli_simple_process_macros(token, finaltoken);/* Extract arguments */argc = cli_simple_parse_line(finaltoken, argv);
// 对命令进行加工处理,转化成argv和argc格式。if (argc == 0) {rc = -1;    /* no command at all */continue;}if (cmd_process(flag, argc, argv, &repeatable, NULL))rc = -1;
// 调用cmd_process对命令进行处理
// 关于这个的实现我们在上述三、4中说明过了/* Did the user stop this? */if (had_ctrlc())return -1;  /* if stopped then not repeatable */}return rc ? rc : repeatable;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105

3、hush命令行模式

hush的实现自己也没搞太懂,简单的说明一下流程

static int parse_file_outer(FILE *f)
{int rcode;struct in_str input;setup_file_in_str(&input);
// 安装一个输入流,具体没怎么研究rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);
// 处理流数据return rcode;
}
后续的流程简单说明如下:
parse_stream_outer
——》run_list
————》run_list_real
——————》run_pipe_real
————————》cmd_process
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可以观察到,最终还是调用了cmd_process来对命令进行处理,上述第三节已经说明了,这里不重复说明了。

这篇关于uboot流程——命令行模式以及命令处理介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1121495

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

30常用 Maven 命令

Maven 是一个强大的项目管理和构建工具,它广泛用于 Java 项目的依赖管理、构建流程和插件集成。Maven 的命令行工具提供了大量的命令来帮助开发人员管理项目的生命周期、依赖和插件。以下是 常用 Maven 命令的使用场景及其详细解释。 1. mvn clean 使用场景:清理项目的生成目录,通常用于删除项目中自动生成的文件(如 target/ 目录)。共性规律:清理操作

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}