Android系统的内核要加载并运行,其实是经历了千辛万苦的,因为万事开头难。在一个系统刚开始时,并没有什么资源可以使用,CPU只认得0x00000000地址,并从那里运行第一条指令,并且这段代码有大小限制,不可以很大。因此需要开发一个引导程序放在那里运行,在这里的培训课程里,主要使用是S3C6410开发板,并且使用UBoot作为引导程序(Bootloader)。UBoot是一个很通用的引导程序,并且在嵌入式系统的应用里非常广泛,功能也相当强大,设计的架构相当灵活,很方便移植到不同的嵌入式设备里。
从前面知道ARM的CPU是固定从0x00000000开始运行的,那么UBoot的编译出来的大小,是放不到0x00000000的内存空间的,那么UBoot又是怎么样获得控制权呢?其实在S3C6410里可以通过CPU的管脚设置不同的电平,可以选择运行CPU内部的程序,然后让这段小程序加载UBoot到合适地址运行。那么接着下来的问题就是UBoot放到那里才是合适的地址?要理解这个合适地址,就需要看S3C6410的手册了,通过阅读这个手册,就会发现CPU的内存是固定在两个地址空间,如下:
0x50000000--0x5FFFFFFF 大小为256M
0x60000000--0x6FFFFFFF 大小为256M
由此可知,所有内存RAM都必须放到这段地址空间,也就是物理地址空间,因此UBoot就必须加载到这段内存空间里才可以运行,那么UBoot在编译时是否需要知道在那里运行呢?答案是需要的。UBoot运行时,有很多数据是需要找到对内存地址寻址。在UBoot编译时,就作出如下指定:
TEXT_BASE= 0xc7e00000
哗,这里的地址为什么是0xc7e00000的呢?难道是写错了吗?其实这里是大有文章的,与其相关的内容就是MMU了,所谓的MMU就是一个内存映射的硬件,主要作用就是把无限的虚拟内存地址空间映射到有限的物理内存地址空间,作用就是复用物理内存,方便编译所有软件。由此可知,UBoot是编译到虚拟地址0xc7e00000运行,在未有开启MMU之前,它的物理地址是0x57e00000,其实在物理地址上来看来是同一个地方。
从前面的可以知道UBoot真实运行的物理地址是0x57e00000,相对应的虚拟地址是0xc7e00000,这段地址相对于256内存来说是一个高端地址,为什么要放到这个地址运行,而不放到0x50000000的物理地址(虚拟机地址0xC0000000)运行呢?其实这是为了后面的内核linux运行做好准备,否则就会相互打架,整个系统运行就出错。从UBoot编译后的内存映像文件可以看到相关信息:
c7e00000T _start
c7e00020t _undefined_instruction
c7e00024t _software_interrupt
c7e00028t _prefetch_abort
c7e0002ct _data_abort
c7e00030t _not_used
c7e00034t _irq
c7e00038t _fiq
c7e0003ct _pad
c7e00040T _end_vect
c7e00040t _TEXT_BASE
c7e00044t _TEXT_PHY_BASE
c7e00048T _armboot_start
c7e0004cT _bss_start
c7e00050T _bss_end
c7e00054t reset
这段映像文件就说明所有开始代码都是相对0xc7e00000开始的,包括所有数据访问。在UBoot的MMU配置里,用下面的代码来把物理地址0x50000000映射到虚拟地址0xc0000000,如下:
//128MB for SDRAM 0xC0000000 -> 0x50000000
.set__base, 0x500
.rept0xD00 - 0xC00
FL_SECTION_ENTRY__base,3,0,1,1
.set__base,__base+1
.endr
引导程序加载运行,做好充分准备之后就可以加载内核运行了。它加载内核地址也是有讲究的,一般linux内核需要加载在0x50008000的物理地址开始,因此虚拟地址就是0xc0008000了,这样就可以把内核加载在低端运行,高端地址就可以给所有应用程序运行了。
总结一下,CPU从0x00000000开始运行Flash的程序,然后把UBoot重定位到物理地址0x57e00000(虚拟地址0xc7e00000)运行,然后打开MMU,就对应在虚拟地址0xc7e00000运行,然后加载内核到虚拟地址0xc0008000(物理地址0x50008000),然后再跳到这个地址里运行,因此UBoot要指定虚拟地址0xc7e00000作来连接程序的基地址。
下面是UBoot加载内核并运行的代码片段:
voiddo_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, intverify)
{
#ifdefCONFIG_CMDLINE_TAG
char *commandline = getenv("bootargs");//取得内核启动参数
#endif
theKernel = (void (*)(int, int,uint))ntohl(hdr->ih_ep);//在mkimage.c里面指定的内核入口地址
/*
* Check if there is an initrd image
*/
if (argc >= 3){//检测initrd临时要文件系统,包括:magicnumber,头和数据的crc校验,和前面类似
SHOW_BOOT_PROGRESS (9);
addr = simple_strtoul (argv[2], NULL,16);//取得initrd的地址,下面检测过程类似内核
}
if (data) {
initrd_start = data;
initrd_end = initrd_start + len;
}
//下面开始向内核传递参数
#ifdefined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd);
#ifdefCONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdefCONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdefCONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdefCONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdefCONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start,initrd_end);
#endif
#ifdefined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
#endif
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
cleanup_before_linux ();//启动内核之前需要进行的设置
theKernel (0, bd->bi_arch_number,bd->bi_boot_params);//启动内核,uboot使命到此全部完成.
}
//QQ:9073204 EMAIL:[email protected]
//蔡军生 2012-1-8
//培训文档100元一份