uboot在进行系统启动和内核加载的时候被分为俩个阶段,
第一阶段主要是写汇编代码,我没有仔细研究,只是看了看移植好的针对mini2440的tekkman的uboot代码。
第二个阶段为由C写成,易于研究和学习。掌握了基本的流程。
(1)第一阶段的功能
Ø 硬件设备初始化
Ø 加载U-Boot第二阶段代码到RAM空间
Ø 设置好栈
Ø 跳转到第二阶段代码入口
(2)第二阶段的功能
Ø 初始化本阶段使用的硬件设备
Ø 检测系统内存映射
Ø 将内核从Flash读取到RAM中
Ø 为内核设置启动参数
Ø 调用内核
第二阶段的C出口函数为:
由于在cpu/arm920t/start.S的最后阶段有如下代码:
ldr pc, _start_armboot
_start_armboot: .word start_armboot 此时pc指针将会跳转到lib_arm/board.c中定义的函数start_armboot().因此start_armboot().函数是c代码的入口代码,分析就从这里起航。
而在函数start_armboot()中有个死循环,
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;)
{
main_loop ();接受用户在uboot命令行输入的命令。
}
在uboot启动的时候,如果在#define CONFIG_BOOTDELAY 1超时之前用户没有输入,uboot就会自动加载linux内核,
其加载时将使用变量“bootcmd”和 “bootargs”在uboot代码所定义的变量值进行启动代码。
变量“bootcmd”和 “bootargs”的值可以在在加载linux内核前,uboot的命令行中进行修改。
我这俩个参数的值如下:
bootcmd=nfs 0x30008000 192.168.1.149:/opt/FriendlyARM/uImage;bootm ------ 需要注意的是再bootcmd变量的最后添加了bootm命令。
bootargs=noinitrd root=/dev/nfs proto=tcp,nolock,nfsvers=3, rw nfsroot=192.168.1.149:/mini2440/rootfs ip=192.168.1.144:192.168.1.149::255.255.255.0 console=ttySAC0,115200 init=/linuxrc mem=64M
首先看看启动内核是俩种比较简单明白的方式,boot,bootd命令的实现。
现实代码如下:
/*******************************************************************/
/* bootd - boot default image */
/*******************************************************************/
#if defined(CONFIG_CMD_BOOTD)
int do_bootd (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
int rcode = 0;
#ifndef CONFIG_SYS_HUSH_PARSER
if (run_command (getenv ("bootcmd"), flag) < 0)
rcode = 1;
#else
if (parse_string_outer (getenv ("bootcmd"),
FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP) != 0)
rcode = 1;
#endif
return rcode;
}
U_BOOT_CMD(
boot, 1, 1, do_bootd,
"boot default, i.e., run 'bootcmd'",
""
);
/* keep old command name "bootd" for backward compatibility */
U_BOOT_CMD(
bootd, 1, 1, do_bootd,
"boot default, i.e., run 'bootcmd'",
""
);
#endif
从上面的对命令boot,bootd命令实现可以知道,其命令的最终执行的是“bootcmd”命令行参数所定义的值
即"nfs 0x30008000 192.168.1.149:/opt/FriendlyARM/uImage;bootm ":
在上面的函数main_loop ()中又如下代码片段
s = getenv ("bootcmd");
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "
if (bootdelay >= 0 && s && !abortboot (bootdelay))
{
# ifdef CONFIG_AUTOBOOT_KEYED
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CONFIG_SYS_HUSH_PARSER
run_command (s, 0);
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef CONFIG_AUTOBOOT_KEYED
disable_ctrlc(prev); /* restore Control C checking */
# endif
}
# ifdef CONFIG_MENUKEY
if (menukey == CONFIG_MENUKEY) {
s = getenv("menucmd");
if (s) {
# ifndef CONFIG_SYS_HUSH_PARSER
run_command (s, 0);
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
}
}
#endif /* CONFIG_MENUKEY */
#endif /* CONFIG_BOOTDELAY */
上的代码中在函数abortboot (bootdelay))
执行的过程中已经超时,则自动的执行"bootcmd" 所定义的命令。
现在看看命令"bootm"命令式如何实现的?
"boot application image from memory",这句话很重要,表明bootm执行是必须确保uImage已经在内从中,
在"bootm addr"中当addr省略的时候bootm加载宏#define CONFIG_SYS_LOAD_ADDR 0x30008000 /* default load address */定义处的内核image。
"bootm"命令的实现函数为do_bootm(),在do_bootm()中完成对linux内核的加载启动。
其加载函数为:do_bootm_linux()函数中获取了"bootargs"环境变量的值。最终将此值传递给linux内核,用来加载文件系统时候使用。
对do_bootm()和do_bootm_linux()函数的分析可以找到看其他朋友的分析。
总结
在uboot中每一个命令都对应一个其实现的函数,在启动linux内核过程中,主要是执行环境变量bootcmd和bootargs所定义的命令。因此,对这俩个变量的定义很重要,如果定义不对就不能正确的加载内核和文件系统。尤其是必须确保文件系统所在分区。