1.Uboot:Start.s -> start_armboot ()-> main_loop ()
main_loop()会调用abortboot (bootdelay)判断在delay time内有没有键按下,并给出prompt"Hit any key to stop autoboot",若没有键按下则run_command (s, 0)(s为默认的bootcmd)。这里默认的bootcmd需要根据内核的位置设置,若bootcmd未赋值,Uboot就会一直等待。如果敲入bootm,也会启动kernel,并且传入参数。如果使用tftp从pc中下载kernel并启动也是可以的。
00271: void main_loop (void)
00272: {
00273: #ifndef CFG_HUSH_PARSER
1.uboot正常启动后,会调用main_loop(void)函数,进入main_loop()之后,如果在规定的时间(CONFIG_BOOTDELAY)内,没有检查到任何按键事件的发生,就会去加载OS,并启动系统,比如把linux内核压缩镜像从nand flash中读到sdram ,然后执行它。如果在CONFIG_BOOTDELAY时间内,用户按下键盘上的任意一个按键,uboot就会进入与用户交互的状态。
如果用户在配置文件中定义了CONFIG_SYS_HUSH_PARSER,就会通过parse_file_outer(),去接收并解析用户命令,否则进入一个for(;;)循环中,通过 readline (CONFIG_SYS_PROMPT)接收用户命令,然后调用run_command(cmd,flag)去解析并执行命令。
如果定义了CONFIG_SYS_HUSH_PARSER,命令接收和解析讲采用busybox 中的hush(对应hush.c)工具来实现,与uboot原始的命令解析方法相比,该工具更加智能。这里主要讲uboot中基于hush的命令解析流程。不过hush的实现太过复杂 ,鉴于自己水平太次,只是简单追踪下流程。 当在配置文件中定义了CONFIG_SYS_HUSH_PARSER,main_loop会调用parse_file_outer(),进入hush,然后里面是一大堆和hush相关的机制,暂时不做分析,最终会调用到hush中的run_pipe_real(struct pipe *pi),在该函数中经过一些列解析 ,最终会调用到对应的命令执行函数。
00274: static char lastcommand[CFG_CBSIZE] = { 0, };
00275: int len;
00276: int rc = 1;
00277: int flag;
00278: #endif
2.uboot main_loop命令的解析方法有两种:
第一种就是最笨的办法,直接到u_boot_cmd段中进行字符串比较,找到相同的那么就执行,如果命令比较少,相对来说对性能没有太大的损失,但是当命令很多时这种方法就不是很合适了。Uboot使用另外一种查找办法解决这个问题:使用hush表,只要用户在configs/MPC8349ADS.h文件中定义:#define CFG_HUSH_PARSER就可以实现了。这种方法与上面的方法基本相同,只是在查找方式上做了优化,这样可以提高查找速度,具体的代码可以查看common/main.c文件
00280: #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=0)
00281: char *s;
00282: int bootdelay;
00283: #endif
00327:
00336: #ifdef CFG_HUSH_PARSER
00337: u_boot_hush_start ();//初始化hush并对其结构体进行填充
00338: #endif
00343:
00344: #ifdef CONFIG_FASTBOOT
00345: if (fastboot_preboot())//这个函数是用来读键盘的状态,我们没有使用键盘,返回的是0;
00346: run_command("fastboot", 0);
00347: #endif
00368: #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=0)
00369: s = getenv ("bootdelay");//获取环境变量中bootdelay的数值
00370: bootdelay = s ? (int)simple_strtol(s, NULL, 10) :CONFIG_BOOTDELAY;
如果s非零,则将s字符串转化成数字并赋值给bootdelay,如果S等于零,则将CONFIG_BOOTDELAY的值赋值给bootdelay
00393: s = getenv ("bootcmd");
//获得环境变量bootcmd的值,也就是获取启动命令
00397: if (bootdelay >= 0 && s && !abortboot (bootdelay)) //倒计时,如果倒计时内没有按下任何键,则执行if中的内容,启动内核
----------------------------
00211: static __inline__ int abortboot(int bootdelay)
00212: {
00213: int abort = 0;
00218: printf("Hit any key to stop autoboot: %2d ",bootdelay);
00220:
00221: #if defined CONFIG_ZERO_BOOTDELAY_CHECK
00222: /*
00223: * Check if key already pressed
00224: * Don't check if bootdelay < 0
00225: */
00226: if (bootdelay >= 0) {
00227: if (tstc()) { /* we got a key press *///tstc主要用于检查是否有输入
00228: (void) getc(); /* consume input *///如果有输入则用getc获取这个输入
00229: puts ("\b\b\b 0");//当有一个输入后,倒计时的数立刻变为0
00230: abort = 1; /* don't auto boot *///将abort置1,这样函数返回后,就不会执行if中的语句,也就不会自动启动内核
00231: }
00232: }
00233: #endif
00234:
00235: while ((bootdelay > 0) && (!abort)) {//这里开始真正的倒计时,只要bootdelay大于0,且abort没有被置1,那么就会继续循环倒计时
00236: int i;
00238: --bootdelay; //每当进来一次,bootdelay的值减1
00239: /* delay 100 * 10ms */
00240: for (i=0; !abort && i<100; ++i) {
00241: if (tstc()) { /* we got a key press *///循环检测是否有输入
00242: abort = 1; /* don't auto boot */ //如果有输入,则将abort置1
00243: bootdelay = 0; /* no more delay *///并且将bootdelay的值赋值为0
00249: break;
00250: }
00251: udelay(10000);
00252: }
00253:
00254: printf("\b\b\b%2d ", bootdelay);
00255: } ? end while (bootdelay>0)&&(!abort) ?
00257: putc('\n');
00264: return abort;
00265: } ? end abortboot ?
------------------------
00397: {
00405: parse_string_outer(s, FLAG_PARSE_SEMICOLON |FLAG_EXIT_FROM_LOOP);//运行启动内核的命令
00412: }
00413:
00427: #endif /* CONFIG_BOOTDELAY */
--------------------------
01284: int run_command (const char *cmd, int flag)
01285: {
01286: cmd_tbl_t *cmdtp;
01287: char cmdbuf[CFG_CBSIZE]; /* working copy of cmd */
01288: char *token; /* start of token in cmdbuf */
01289: char *sep;/* end of token (separator) in cmdbuf */
01290: char finaltoken[CFG_CBSIZE];
01291: char *str = cmdbuf;
01292: char *argv[CFG_MAXARGS + 1]; /* NULL terminated */
01293: int argc, inquotes;
01294: int repeatable = 1;
01295: int rc = 0;
01296:
01303: clear_ctrlc(); /* forget any previous Control C *///清除ctrl+c
01304:
01305: if (!cmd || !*cmd) { //如果命令为空,则返回
01306: return -1; /* empty command */
01307: }
01308:
01309: if (strlen(cmd) >= CFG_CBSIZE) {
01310: puts ("## Command too long!\n");
01311: return -1;
01312: }
01313:
01314: strcpy (cmdbuf, cmd);//将命令复制到cmdbuf中
01315:
01316: /* Process separators and check for invalid
01317: * repeatable commands
01318: */
01323: while (*str) {
01324:
01325: /*
01326: * Find separator, or string end
01327: * Allow simple escape of ';' by writing "\;"
01328: *//* 分隔命令循环:根据';' 将命令序列分隔成多条命令,结果放入token */
01329: for (inquotes = 0, sep = str; *sep; sep++) {
01330: if ((*sep=='\'') &&(*(sep-1) != '\\'))
01332: inquotes=!inquotes;
01334: if (!inquotes &&(*sep == ';') && /* separator */( sep != str) &&/* past string start */(*(sep-1) != '\\')) /* and NOT escaped */
01338: break;
01339: }
01340:
01341: /*
01342: * Limit the token to data between separators
01343: */
01344: token = str;
01345: if (*sep) {
01346: str = sep + 1;
01346: /* start of command for next pass */
01347: *sep = '\0';
01348: }
01349: else
01350: str = sep;
01354:
01355: /* find macros in this token and replace them */
01356: process_macros (token, finaltoken); //处理命令中的宏替换
01357:
01358: /* Extract arguments */
01359: if ((argc = parse_line (finaltoken, argv)) ==0) { //解析命令参数
01360: rc = -1; /* no command at all */
01361: continue;
01362: }
01363:
01364: /* Look up command in command table */
01365: if ((cmdtp = find_cmd(argv[0])) == NULL) {//在命令表中寻找命令
01366: printf ("Unknown command '%s' - try 'help'\n", argv[0]);//找不到就打印这句话
01367: rc = -1; /* give up after bad command */
01368: continue; //返回继续分割命令
01369: }
01370:
01371: /* found - check max args */
01372: if (argc > cmdtp->maxargs) { //判断命令最大参数个数
01373: printf ("Usage:\n%s\n", cmdtp->usage);//如果超出命令最大参数个数,打印用法信息
01374: rc = -1;
01375: continue; //返回继续分割命令
01376: }
01377:
01378: #if defined(CONFIG_CMD_BOOTD)/* avoid "bootd" recursion */
01380: if (cmdtp->cmd == do_bootd) {
01384: if (flag & CMD_FLAG_BOOTD) {
01385: puts ("'bootd' recursion detected\n");
01386: rc = -1;
01387: continue;
01388: } else {
01389: flag |= CMD_FLAG_BOOTD;
01390: }
01391: }
01392: #endif
01393:
01394: /* OK - call function to do the command */
01395: if ((cmdtp->cmd) (cmdtp, flag, argc, argv) !=0) {//执行命令的处理函数
01396: rc = -1;
01397: }
01398:
01399: repeatable &= cmdtp->repeatable;
01400:
01401: /* Did the user stop this? */
01402: if (had_ctrlc ()) //判断用户是否终止,ctrl+c
01403: return -1;
01403: /* if stopped then not repeatable */
01404: } ? end while *str ?
01405:
01406: return rc ? rc : repeatable;
01407: } ? end run_command ?
--------------------------
00435:
00436: /*
00437: * Main Loop for Monitor Command Processing
/****************进入交互模式************************/
00438: */
00439: #ifdef CFG_HUSH_PARSER
00440: parse_file_outer();
00441: /* This point is never reached */
00442: for (;;);
00443: #else
00444: for (;;) {
00453: len = readline (CFG_PROMPT);
00455: flag = 0; /* assume no special flags for now */
00456: if (len > 0)
00457: strcpy (lastcommand, console_buffer);
00458: else if (len == 0)
00459: flag |= CMD_FLAG_REPEAT;
00474:
00475: if (len == -1)
00476: puts ("
00477: else
00478: rc = run_command (lastcommand, flag);
00480: if (rc <= 0) {
00481: /* invalid command or not repeatable, forget it */
00482: lastcommand[0] = 0;
00483: }
00484: } ? end for ;; ?
00485: #endif /*CFG_HUSH_PARSER*/
00486: } ? end main_loop ?
1.uboot命令集可能的管理方式
1)数组。结构体数组,数组中每一个结构体成员就是一个命令的所有信息。
2)链表。链表的每个节点data段就是一个命令结构体,所有的命令都放在一条链表上。这样就解决了数组方式的不灵活。坏处是需要额外的内存开销,然后各种算法(遍历、插入、删除等)需要一定复杂度的代码执行。
3)有第三种吗?uboot没有使用数组或者链表,而是使用了一种新的方式来实现这个功能。
2.命令结构体cmd_tbl_t
00041: struct cmd_tbl_s {
00042: char *name; /* Command Name */
00043: int maxargs; /* maximum number of arguments */
00044: int repeatable; /* autorepeat allowed? */
00045: /* Implementation function */
00046: int (*cmd)(struct cmd_tbl_s *, int, int, char*[]);
00047: char *usage; /* Usage message (short) */
00048: #ifdef CFG_LONGHELP
00049: char *help; /* Help message (long) */
00050: #endif
00051: #ifdef CONFIG_AUTO_COMPLETE
00052: /* do auto completion on the arguments */
00053: int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
00054: #endif
00055: }
typedef struct cmd_tbl_s cmd_tbl_t;
1)name:命令名称,字符串格式。
2)maxargs:命令最多可以接收多少个参数
3)repeatable:指示这个命令是否可重复执行。重复执行是uboot命令行的一种工作机制,就是直接按回车则执行上一条执行的命令。
4)cmd:函数指针,命令对应的函数的函数指针,将来执行这个命令的函数时使用这个函数指针来调用。
5)usage:命令的短帮助信息。对命令的简单描述。
6)help:命令的长帮助信息。细节的帮助信息。
7)complete:函数指针,指向这个命令的自动补全的函数。
总结:uboot的命令体系在工作时,一个命令对应一个cmd_tbl_t结构体的一个实例,然后uboot支持多少个命令,就需要多少个结构体实例。uboot的命令体系把这些结构体实例管理起来,当用户输入了一个命令时,uboot会去这些结构体实例中查找(查找方法和存储管理的方法有关)。如果找到则执行命令,如果未找到则提示命令未知。
3.uboot实现命令管理的思路
1)填充1个结构体实例构成一个命令
2)给命令结构体实例附加特定段属性(用户自定义段),链接时将带有该段属性的内容链接在一起排列(挨着的,不会夹杂其他东西,也不会丢掉一个带有这种段属性的,但是顺序是乱序的)。
3)uboot重定位时将该段整体加载到DDR中。加载到DDR中的uboot镜像中带有特定段属性的这一段其实就是命令结构体的集合,有点像一个命令结构体数组。
4)段起始地址和结束地址(链接地址、定义在u-boot.lds中)决定了这些命令集的开始和结束地址。
4.uboot命令定义具体实现分析
1)U_BOOT_CMD宏基本分析
这个宏定义在uboot/common/command.h中。
U_BOOT_CMD(
version, 1, 1, do_version,
"version - print monitor version\n",
NULL
);
这个宏替换后变成:
cmd_tbl_t __u_boot_cmd_version __attribute__ ((unused,section (".u_boot_cmd"))) = {#name, maxargs, rep, cmd, usage, help}
这里的"#",作用是将_name传递的值字符串化
总结:这个U_BOOT_CMD宏的理解,关键在于结构体变量的名字和段属性。名字使用##作为连字符,附加了用户自定义段属性,以保证链接时将这些数据结构链接在一起排布。
2)链接脚本。
5.添加u-boot命令
也就是在进入main_loop()函数后,在等待的时间里可以识别的命令。
需要修改5个地方:
最主要的还是自己写的命令实现文件。
那么为什么要修改这几个文件呢?下面先分析一下正常的执行过程:
对应的函数执行过程是:
其中main_loop() run_command() 都在comman/main.c中,而find_cmd(),find_cmd_tbl()是在common/command.c中的。
1)实现命令的具体功能,在comman文件夹中建立对应的.c文件。
2)如果要添加指令,首先为了能让系统找到该指令,所以要在命令表中注册一下。
#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}
"##"与"#"都是预编译操作符,"##"有字符串连接的功能,"#"表示后面紧接着的是一个字符串。
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
其中,unused表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
凡通过U_BOOT_CMD定义的cmd_tbl_t变量会全部被放在.u_boot_cmd段当中。这也是在你写的.c文件的末尾必须要写的,为了完成注册这个动作。
6.find_cmd函数详解
1)find_cmd函数的任务是从当前uboot的命令集中查找是否有某个命令。如果找到则返回这个命令结构体的指针,如果未找到返回NULL。
00367: cmd_tbl_t *find_cmd (const char *cmd)
00368: {
00369: cmd_tbl_t *cmdtp;
00370: cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start;
00370: /*Init value */
00371: const char *p;
00372: int len;
00373: int n_found = 0;
00374:
00375: /*
00376: * Some commands allow length modifiers (like "cp.b");
00377: * compare command name only until first dot.
00378: */
00379: len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
00381: for (cmdtp = &__u_boot_cmd_start;cmdtp != &__u_boot_cmd_end;cmdtp++) {
00384: if (strncmp (cmd, cmdtp->name, len) == 0) {
00385: if (len == strlen (cmdtp->name))
00386: return cmdtp; /* full match */
00387:
00388: cmdtp_temp = cmdtp;
00388: /* abbreviated command ? */
00389: n_found++;
00390: }
00391: }
00392: if (n_found == 1) { /* exactly one match */
00393: return cmdtp_temp;
00394: }
00395:
00396: return NULL; /* not found or ambiguous command */
00397: } ? end find_cmd ?
2)函数的实现思路很简单,如果不考虑命令带点的情况(md.b md.w这种)就更简单了。查找命令的思路其实就是for循环遍历数组的思路,不同的是数组的起始地址和结束地址是用地址值来给定的,数组中的元素个数是结构体变量类型。
7.U_BOOT_CMD宏详解
这个宏其实就是定义了一个命令对应的结构体变量,这个变量名和宏的第一个参数有关,因此只要宏调用时传参的第一个参数不同则定义的结构体变量不会重名。
比如说:U_BOOT_CMD(nandboot,0,0,do_nandboot,"boot from nand","--help") 通过宏展开就是:
cmd_tbl_t __u_boot_cmd_nandboot __attribute__((unused, section(".u_boot_cmd"))) = {"nandboot", 0, 0, do_nandboot, "boot from nand","--help"}
所谓注册就是把一个特定命令的信息填在这个结构体中,然后把这个结构体放到一个表中,用于查找和跳转。
8.uboot中增加自定义命令
8.1在已有的c文件中直接添加命令
1)在uboot/common/command.c中添加一个命令,叫:mycmd
2)在已有的.c文件中添加命令比较简单,直接使用U_BOOT_CMD宏即可添加命令,给命令提供一个do_xxx的对应的函数这个命令就齐活了。
3)添加完成后要重新编译工程(make distclean; make x210_sd_config; make),然后烧录新的uboot去运行即可体验新命令。
4)还可以在函数中使用argc和argv来验证传参。
8.2自建一个c文件并添加命令
1)在uboot/common目录下新建一个命令文件,叫cmd_aston.c(对应的命令名就叫aston,对应的函数就叫do_aston函数),然后在c文件中添加命令对应的U_BOOT_CMD宏和函数。注意头文件包含不要漏掉。
2)在uboot/common/Makefile中添加上aston.o,目©的是让Make在编译时能否把cmd_aston.c编译链接进去。
3)重新编译烧录。重新编译步骤是:make distclean; make x210_sd_config; make
8.3体会:uboot命令体系的优点
1)uboot的命令体系本身稍微复杂,但是他写好之后就不用动了。我们后面在移植uboot时也不会去动uboot的命令体系。我们最多就是向uboot中去添加命令,就像本节课所做的这样。
2)向uboot中添加命令非常简单。