文章目录
说在前面
- uboot的命令体系在平时移植过程中基本不用动
因为基本都是写好的代码,不用动态添加删减。 - 学习的目的是了解原理,学习个中的方法
学习命令处理的方式。
1.命令解析
2.命令查找
3.命令执行
main_loop
一、uboot最的最后是在一个由for循环构成的死循环里面执行main_loop() -----------------------main_loop在common/main.c里面
二、进入main_loop()
其实main.c里面有很多函数,我要找的是main_loop,在main_loop后面定义的函数其实都是main_loop需要用到的,这些函数都在main.c所包含的头文件里面有进行声明。
因此可以大胆猜测其他的文件也是类似,所需要调用的函数是写在主函数下面,然后在函数开头用一个头文件包含所调用的函数的声明(其实很多代码都是这样的操作风格。)
run_command
注意:
1.在这里众多函数里面,跟命令体系关联的就是run_command函数。
再次看上面的图,其实这里的run_command里面,只有一个条件编译有用,其他#if define (xxxxxxxx)很多,可以不用理会,通过SI左侧的那一条显示栏可以清晰告诉你只用到这一个条件编译。
2.命令与函数的对应关系
例如
函数do_run对应的命令就是run
run也是一个命令,run后面跟的是环境变量。
例如环境变量里面的bootcmd
bootcmd=movi read kernel 30008000; bootm 30008000
可以直接run bootcmd执行这个环境变量bootcmd的等号后面的命令。
命令解析-parse_line函数
parse_line
通过查找可以看到,parse_line这个函数在run_command中被调用了一次,目的是这样:/ Extract arguments /,
提取参数,把命令行里面的参数提取出来,把一行命令分解。进行提取。
这里是在run_command里面看到parse_line的运用。
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) {
rc = -1; /* no command at all */
continue;
}
一、finaltoken
这是一个数组。数组大小是控制台io buffer的大小
char finaltoken[CFG_CBSIZE]; //在run_command里面有定义
#define CFG_CBSIZE 256 /* Console I/O Buffer Size */ 在x210_sd.h里面有定义
二、argv
这是一个指针数组,数组大小是最大的命令参数个数+1,数组中每个元素都是指针,指针指向的元素类型是char类型。
link ---- main函数的传参里面的char* argv[ ]
char *argv[CFG_MAXARGS + 1]; /* NULL terminated */
#define CFG_MAXARGS 16 /* max number of command args */
这个parse_line函数很好分析的,具体函数代码就不贴上来了,可以在SI里面自己搜,假设传参
finaltoken[0] = md
finaltoken[1] = 空格
finaltoken[2] = 30000000
finaltoken[3] = \0
这样的假设也不是凭空假设的,应该是跟控制台的I/O buffer有关,如果再分析下去又追溯到很远了,所以先这样假设,然后代入parse_line里面进行分析。分析过程略过,
经过分析之后,结论是经过parse_line函数之后,就可以把在控制台输入的命令给拆分成一份一份,并且得到argc的个数以及argv[]里面的个个元素是什么了。即完成了命令的解析。
命令的查找-find_cmd
这个函数的作用是查找得到的命令到底是哪一个命令
find_cmd在run_command里面被调用的
它跟parse_line一样都在一个while循环里面,不断执行直到找到了对应的函数。
传入find_cmd的参数是argv[0],通过parse_line分析得到的
查看find_cmd函数,简单分析后得知它的返回值是一个cmd_tbl_t类型的参数cmdtp,另外还发现原来链接脚本里面的某部分的起始和结束名也可以作用在函数内部。
下一步,研究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 *[]);
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_s cmd_tbl_t;
总的来说,find_cmd的返回值返回的是一个结构体类型的变量,这个结构体类型里面包含了很多参数,有:
1.命令名字
2.最大的传参个数
3.是否可以重复执行-就是下一步按一下回车就继续执行上一次的命令
4.一个函数指针,指向该命令对应的函数
5.用法简介
6.详细帮助信息。
这里最重要的是函数指针。
命令执行-从一个例子出发去分析如何执行。
int
do_version (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
extern char version_string[];
printf ("\n%s\n", version_string);
return 0;
}
U_BOOT_CMD(
version, 1, 1, do_version,
"version - print monitor version\n",
NULL
);
int do_version()就是version命令所对应的执行函数。
函数体内部执行的是打印version信息。
接着U_BOOT_CMD是执行这个函数的宏。宏里里面的参数与cmd_tbl_s结构体的对应关系如下。
cmd_tbl_s | 宏的传参 |
---|---|
name | version |
maxargs | 1 |
repeatable | 1 |
函数指针 | do_version |
usage | “version - print monitor version\n” |
help | NULL |
U_BOOT_CMD宏是怎么工作的?
#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}
这里面涉及到了## 连字符还有__attribute__赋予属性这两个概念,我也没搞得很懂。
这里的##name是跟宏#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) 里面的name连起来了,以do_version的宏为例
这个宏替换后变成:
cmd_tbl_t __u_boot_cmd_version attribute ((unused,section (".u_boot_cmd"))) = {#name, maxargs, rep, cmd, usage, help}
##name变成了version,
__attribute__后面的section".u_boot_cmd"就是uboot的链接脚本里面自定义命令段里面的内容。
大概意思是把这些命令都链接到这个段里面。
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
还有一点,最后面的{#name, maxargs, rep, cmd, usage, help}
里面的#name,这里#的意思是
“#”的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量,通过替换后在其左右各加上一个双引号。
也就是把name变成一个字符串。
我查了一下除了version命令传参不是一个字符串之外,其他的cmd都直接传的字符串,这里可能是为了更加严谨所以使用#name吧。
总结一下:
命令解析-通过parse_line进行命令的解析
命令查找-通过find_cmd进行命令的查找,返回值cmdtp
命令执行-关键的是cmd_tbl_s结构体,里面有一条线-函数指针,
通过U_BOOT_CMD进行命令的执行,利用这根线去调用真正对应的函数。
在uboot中添加自定义命令
直接在command.c文件里面添加
在uboot/common/command.c里面添加自定义的命令,直接复制别的命令然后进行相应修改即可
执行结果
命令的名字是park,执行的内容会打印test for add cmd
help信息里面是help~~
因为我设置了最大传参个数是1,如果是park 222的话传参2个会不符合要求,会打印usage出来 usage~~
添加命令成功!
新建cmd_xxx.c新的文件添加命令
在uboot/common文件夹里面新建了一个cmd_park2.c文件
不要忘记在Makefile里面加上cmd_park2.o
uboot/common/Makefile
重新编译之后执行
添加命令成功。
uboot的命令体系在平常移植的过程中一般不用修改,最多往里面添加命令即可。
通过这次学习,我还尝试把pares_line函数移植使用,
首先用fgets命令获取到stdin的字符串,然后用parse_line函数根据空格或者tab拆分成一份份的argv[x],简单地实现了命令的解析。
这也算是一个小小的项目经验吧!