main_loop 函数定义在文件 common/main.c 里面,代码如下
-
第48行bootstage_mark_name:会打印出启动进度
-
第57行setenv:设置环境变量version,进入到uboot后,可通过version命令来打印版本信息
-
第60行cli_init:跟初始化命令有关,初始化命令链表
-
第62行run_preboot_environment_command:获取环境变量perboot的内容,perboot是一些预启动命令,一般不使用这个环境变量
-
第68行bootdelay_process:读取环境变量bootdelay和bootcmd的内容,然后将bootdelay的值赋值给全局变量stored_bootdelay,返回值为环境变量bootcmd的值,同时会根据启动方式是否是从USB启动打印信息,若从USB启动则打印Boot from USB for mfgtools,否则打印Normal Boot,如下
-
第69~70:由于未定义设备树,因此无效
-
第72行autoboot_command:函数定义在common/autoboot.c中,检测uboot倒计时是否结束,代码如下
里边条件编译比较多,主要的就只有两个函数:abortboot和run_command_list
abortboot实际执行的代码是abortboot_normal,代码如下:
若bootdelay大于0(默认值是3),则打印"Hit any key to stop autoboot:",因此会在终端上显示如下信息:
宏CONFIG_ZERO_BOOTDELAY_CHECK未定义!
第247~266就是倒计时的程序,逻辑较简单,这里不做具体分析,大概逻辑是如果倒计时结之前检测到任何输入,则将abort标记置为1,否则默认为0。
宏CONFIG_SILENT_CONSOLE未定义!
结合autoboot_command的代码,若检测到有输入则退出,否则则执行run_command_list函数,执行bootcmd命令,加载内核并跳转。(bootcmd指令是各个板子默认的启动指令,需要自定义,此指令在下一章节解析) -
第74行cli_loop:执行到这表示检测到有输入,不会自动跳转到内核
函数定义在common/cli.c中,代码如下
由于宏CONFIG_SYS_HUSH_PARSER定义过,因此实际执行的函数就是u_boot_hush_start,此函数定义在common/cli_hush.c中,如下
宏__U_BOOT__是有定义过,因此主要是setup_file_in_str和parse_stream_outer。
代码如下
主要初始化了结构体变量input,结构体定义如下
这个函数就是 hush shell 的命令解释器,负责接收命令行输入,然后解析并执行相应的命令,去掉一些预编译宏后代码如下
函数入参flag是宏FLAG_PARSE_SEMICOLON,表示以分号为分界线解析命令。
第3197行parse_stream就是调用file_get从终端上读取到输入信息,然后进行命令解析,填充到input结构体内。
第3210行run_list执行解析出来的命令,经过一些列的分隔符解析及处理,最终调用cmd_process来处理命令。
再看这个代码之前我们先看看uboot的命令是如何定义的(此种思想在内核代码中随处可见,建议深入理解。此方法在设计程序架构时非常方便,可以写出拓展性、适应性很强的框架!),uboot shell命令的代码都在cmd文件夹内,如下(未展示全部)
为了方便分析,我们选择一个最简单的指令version来看看,找到对应的version.c文件,代码如下
可以看到,代码很简单,最下边的出现了一个宏U_BOOT_CMD,这个定义在command.h中,如下
还有个ll_entry_declare_list,定义在include/linker_lists.h 中,如下
在include/linux/compiler-gcc.h中有一些gcc相关的拓展属性的用法,如下
结合上述的几个宏,我们将version.c中的定义展开如下所示:
不要嫌麻烦,上述几个嵌套的宏展开后就如上图所示,主要用到了#和##的拼接,这个用法很常见,百度也到处都有讲解,这里不展开讲,主要讲解下展开后的内容。
可以看到,展开后起始就是初始化了一个结构体而已,大家看起来就比平常自己初始化结构体多了两个attribute而已,gcc的一大特点就是提供了很多attrbute的拓展属性,在某些场合有很大的用处,感兴趣的可以百度下gcc的attribute,这里只讲解用到的几个:
- __attribute __((aligned(4))):告诉编译器在编译过程中按照n字节对齐,这边是满足结构体的对齐要求,主要是内部有几个函数指针,防止不对齐时,造成访问错误
- __attribute __((unused, section(“.u_boot_list_2_do_version_2_version”))):第一个参数unused是告诉编译器这个可能未使用,消除未使用变量的警告信息。第二个是重点内容,section是自定义段,在链接脚本中有定义,这句话的意思是将这个变量放到指定的section中,链接脚本uboot.lds如下
链接脚本中定义了存储以 .u_boot_list开头的段,并且指定了按照数字升序的排列方式SORT,看到这基本就清楚了U_BOOT_CMD其实就是初始化了一个struct cmd_tbl_s结构体变量,并将其存储到了指定的section中,仅此而已。
而这个结构体就是uboot shell的管理结构体,可以理解为框架,在平时自己做开发时,可以参考这种方式,定义一个管理结构体,然后存储在一个指定的自定义段中,在框架代码中轮训整个段,即可快速查找完所有用户定义在这里边的内容,第三方人员要使用只用使用宏进行定义即可,而框架代码不用动,不管是增加指令还是删除指令,都是一个c文件内部的事情,一个低耦合高内聚的框架就设计了出来,然而重要的是这种思想,也可以不依赖自定义段来实现。
看完了指令的定义,接下来我们看看框架代码是如何运作的。
3.2 框架运作函数定义在common.c中,是uboot shell的核心处理程序,如下
-
第509行find_cmd:输入参数argc[0]就是从终端上输入的字符串,代码如下
上文提到过链接脚本中同一个section内的排列方式,由于uboot是采用数字升序的方式,因此定义一个.u_boot_list_2_xx_1和.u_boot_list_2_xx_3就相当于可以得到整个section段的起始地址和结束地址,(此方法有点麻烦,可以直接在链接脚本内定义两个变量即可,感兴趣的可以自己研究研究)取到起始地址和长度后,传入到find_cmd_tbl函数中,如下
代码逻辑也不复杂,在命令定义的时候,都定义了一个name,这里就是轮训所有的uboot指令段,来查找是否能够有匹配的指令。 -
第510~512行:返回NULL,表示此命令未注册过,会打印错误信息,如下
-
第516行:检测参数个数是否超出预设
-
第519~529行:防止boot指令递归执行
-
第531~539行:找到命令后执行它的回调函数来执行,并记录了运行时间,以及是否自动repeat
-
第540~541行:若命令执行失败,则打印出命令的help信息
终上所述,就是uboot的启动流程!



