*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个QQ群82642304,欢迎加入!
*.目的:整理一下RIotBoard开发板的启动流程,对自己的所学做一个整理总结,本系列Uboot代码基于2009.08版。
*.备注:整个系列只是对我所学进行总结,记录我认为是关键的点,另我能力有限,难免出现疏漏错误,如果读者有发现请多指正,以免我误导他人!
上一篇讲述了Uboot的入口到C函数入口之间的一段汇编代码,执行完这段汇编代码之后Uboot就跳转到C函数的入口start_armboot.
每个开发板的初始化流程都不一样,在这里我就不重点罗列出板子初始化的各个细节,我也没有研究的那么具体,我只记录以下我认为比较重要的点:
1. 基本的初始化:在start_armboot函数的开头有这么一段代码
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
init_sequence的定义就在start_armboot的上方,程序顺序调用了这些函数接口,初始化了板子、时间、环境变量、波特率、串口等。
执行完这些比较基本的初始化和其他初始化之后,Uboot进入main_loop函数
2. main_loop主要是接收和处理命令,如果在Uboot进入等待命令延时的时候按下任意键,Uboot就会进入命令交互模式,否则Uboot将启动内核。
main_loop先从环境变量bootdelay中取出延时启动时长:
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
然后再取出默认执行的命令,如果在延时时长内没有任意键输入,则执行该命令
s = getenv ("bootcmd");
然后等待用户的输入:
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
...
run_command (s, 0);
...
}
abortboot函数就是在bootdelay时间内判断用户是否有输入,如果没有就直接执行bootcmd的命令。如果用户有输入,则跳出该循环,进入下一个循环:
for (;;) {
...
len = readline (CONFIG_SYS_PROMPT);
...
if (len == -1)
puts ("\n" );
else
rc = run_command (lastcommand, flag);
...
}
即进入死循环,从串口读命令,然后执行命令.
3. 无论是用户输入的命令还是默认执行的命令,都是通过run_command来执行的.
每一条命令都是一个struct cmd_tbl_s的结构体,定义如下:
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 CONFIG_SYS_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
};
其中name是命令名称,不能有两个一样的命令名称.
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);是命令的接口函数,调用该接口执行命令对应的功能.
在Uboot下我们是通过U_BOOT_CMD宏来定义一条命令,例如:
U_BOOT_CMD(
version, 1, 1, do_version,
"print monitor version",
""
);
展开宏实际上就是
cmd_tbl_t __u_boot_cmd_version __attribute__ ((unused,section (".u_boot_cmd"))) = {
"version",1,1,do_version,"print monitor version",""
}
命令的名称是”version”,对应的接口函数是do_version,也就是说你在Uboot下面输入”version”,Uboot就会去调用do_version函数.
每个命令的段属性都是 “.u_boot_cmd”,这一点很重要,下面我们会提到
run_command在对命令名称进行必要的处理之后,调用find_cmd函数来查找命令.
cmd_tbl_t *find_cmd (const char *cmd)
{
int len = &__u_boot_cmd_end - &__u_boot_cmd_start;
return find_cmd_tbl(cmd, &__u_boot_cmd_start, len);
}
__u_boot_cmd_end和__u_boot_cmd_start这两个变量,它们的定义在Uboot的链接脚本u-boot.lds中.
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
我们看到在__u_boot_cmd_start和__u_boot_cmd_end之间存放的是段属性为.u_boot_cmd的数据,而我们刚才上面说到每条命令的段属性都被定义成.u_boot_cmd,也就是说每一条命令都会被链接程序链接在__u_boot_cmd_start和__u_boot_cmd_end之间.
理解里这一点,我们就不会难理解find_cmd_tbl函数,就是在__u_boot_cmd_start和__u_boot_cmd_end之间比较每条命令的名称,如果一致就返回命令的数据结构的指针.
在find_cmd有正确找到命令对应的数据结构指针后,run_command就会通过调用该命令的接口来执行命令:
/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
rc = -1;
}
执行完命令之后,就又回到main_loop等待新的命令(如果用户进入命令交互界面)或者启动内核(命令就没有返回).
下一篇我们将研究一下默认执行bootcmd命令来启动内核的流程.
Uboot的启动流程其实不是很复杂,逻辑比较直,就是比较偏底层,需要对硬件有一定的了解,命令的处理方面有一个技巧,这个技巧在内核中也有使用到,后续我们研究到内核的时候再具体说明.
暂无