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作来连接程序的基地址。
Config.mk有下面一段话:
# SMDK6410 has a 128 MB SDR SDRAM
#
# 5000'0000 to 5800'0000
#
#
# Linux-Kernel is expected to be at 5000'8000, entry 5000'8000
# optionally with a ramdisk at 5080'0000
#
# we load ourself to 57e0'0000 without MMU
# with MMU, load address is changed to 0xc7e0_0000
#
# download area is 5000'0000
#
ifndef TEXT_BASE
TEXT_BASE = 0xc7e00000
endif
S3C6410datasheet地址如下:
DRAM Controller of the Memory Port1 :
Address Size(MB)
0x5000_0000 0x5FFF_FFFF 256MB
0x6000_0000 0x6FFF_FFFF 256MB