*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个QQ群82642304,欢迎加入!
*.目的:整理一下RIotBoard开发板的启动流程,对自己的所学做一个整理总结,本系列Uboot代码基于2009.08版。
*.备注:整个系列只是对我所学进行总结,记录我认为是关键的点,另我能力有限,难免出现疏漏错误,如果读者有发现请多指正,以免我误导他人!
上一篇我们讲到如果用户在启动Uboot过程中没有输入任意键,那么Uboot将执行默认命令bootcmd.
在进入Uboot的命令交互模式后输入printenv:
…
bootargs=console=ttymxc1,115200 nosmp video=mxcfb0:dev=hdmi,1280x720M@60,bpp=32 video=mxcfb1:off
bootargs_mmc=setenv bootargs ${bootargs} root=/dev/mmcblk0p1 rootwait
bootcmd_mmc=run bootargs_mmc; mmc dev 3; mmc read ${loadaddr} 0x800 0x2000; bootm
bootcmd=run bootcmd_mmc
…
可以看出执行bootcmd过程中分别执行:
1. 设置bootargs(启动参数)到环境变量
2. mmc切换到dev 3(即emmc)中.
3. 从mmc dev 3(即emmc)的偏移量为0x800块的位置读取0x2000块长度的数据(一个数据块的大小是512B)到${loadaddr}
4. 执行bootm命令
根据RIotBoard板子上的配置,在emmc上,从0~1M的地址内存放的是Uboot(包括环境变量),从1~9M的地址内存放的是内核镜像,其他的存放文件系统.
所以第2、3步实际上就是将emmc上的1~9M上的数据拷贝到内存上${loadaddr}的位置.
Uboot将内核镜像加载到${loadaddr}的位置上之后,就执行bootm命令,bootm命令的定义在uboot/common/cmd_bootm.c中,它的接口函数是do_bootm,也就是说Uboot调用了do_bootm函数.
/* relocate boot function table */
if (!relocated) {
int i;
for (i = 0; i < ARRAY_SIZE(boot_os); i++)
if (boot_os[i] != NULL)
boot_os[i] += gd->reloc_off;
relocated = 1;
}
启动函数表boot_os里面存放的是一些内核启动函数的函数指针.Uboot是一个通用的内核启动器,不仅支持Linux内核的启动,还支持其他例如Vxworks、netbsd等内核的启动,每一种内核启动函数各不一样,Uboot通过读取内核镜像文件头部的信息来判断该镜像为何种镜像以及在boot_os表中对应的加载函数.
在Uboot汇编入口的那段代码上有个函数是relocate函数,将Uboot的执行镜像重新定位到新地址,如果有重新定位(RIotBoard开发板没有重新定位),需要重新计算内核启动函数表的位置,这样子才能准确的调用.
2. 根据argc的值判断是否有子命令,如果有则执行子命令并返回.
3. 调用bootm_start函数,该函数主要就是读取镜像文件头部信息,然后到填充static bootm_headers_t images;这个数据结构.内核镜像文件就是uImage文件,它是在Image上面增加一个头部信息,后续讲到内核的时候再详细讲它的生成流程,在这边我稍微讲下头部信息,它包含如下信息:
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
其中ih_load指明内核最终被加载的地址,ih_ep指明内核的入口地址,ih_comp指明内核的压缩方式
另外一点就是如果执行bootm时没有带参数,则do_bootm默认从${loadaddr}处读取内核镜像文件,否则,从第一个参数指示的地址读取内核镜像文件.
4. 调用bootm_load_os函数,该函数就是根据已填充好的static bootm_headers_t images;数据结构来加载内核,其中包括:根据内核压缩方式对内核进行解压缩,将内核移动到指定的加载地址.
5. 跳转到对应的启动函数
...
boot_fn = boot_os[images.os.os];
...
boot_fn(0, argc, argv, &images);
...
linux内核对应的就是do_bootm_linux函数
do_bootm_linux函数先保存一下内核参数,然后跳转到内核入口地址.
...
theKernel = (void (*)(int, int, uint))images->ep;
...
cleanup_before_linux ();
theKernel (0, machid, bd->bi_boot_params);
传给内核的有三个参数,第一个参数为0,第二个参数为机器ID,第三个为内核参数的起始地址.
内核的各个参数是一个struct tag params结构体,该结构体的定义如下:
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
struct tag_header hdr;指明里该结构体是什么内核参数以及参数长度,union u;存放参数的数据.
内核参数以ATAG_CORE开头,以ATAG_NONE结尾,依次存放在以bd->bi_boot_params为起始地址的内存中,然后传给内核进行解析.
最后,调用theKernel,进入内核…
Uboot的代码中有许许多多的宏定义给阅读代码造成一些困难,我跳过许多东西,只是陈述一下启动流程.其余的代码读者有兴趣的可以去研究.
暂无