分类: LINUX
2014-06-04 18:36:30
原文地址: Android arm linux kernel启动流程(二) 作者:talkinsky
前面说过解压以后,代码会跳到解压完成以后的vmlinux开始执行,具体从什么地方开始执行我们可以看看生成的vmlinux.lds(arch/arm/kernel/)这个文件:
很明显我们的vmlinx最开头的section是.text.head,这里我们不能看ENTRY的内容,以为这时候我们没有操作系统,根本不知道如何 来解析这里的入口地址,我们只能来分析他的section(不过一般来说这里的ENTRY和我们从seciton分析的结果是一样的),这里 的.text.head section我们很容易就能在arch/arm/kernel/head.S里面找到,而且它里面的第一个符号就是我们的stext:
这里的ENTRY这个宏实际我们可以在include/linux/linkage.h里面找到,可以看到他实际上就是声明一个GLOBAL Symbol,后面的ENDPROC和END唯一的区别是前面的声明了一个函数,可以在c里面被调用。
找到了vmlinux的起始代码我们就来进行分析了,先总体概括一下这部分代码所完成的功能,head.S会首先检查proc和arch以及atag的 有效性,然后会建立初始化页表,并进行CPU必要的处理以后打开MMU,并跳转到start_kernel这个symbol开始执行后面的C代码。这里有 很多变量都是我们进行kernel移植时需要特别注意的,下面会一一讲到。
在这里我们首先看看这段汇编开始跑的时候的寄存器信息,这里的寄存器内容实际上是同bootloader跳转到解压代码是一样的,就是r1=arch r2=atag addr。下面我们就具体来看看这个head.S跑的过程:
首先进入SVC模式并关闭所有中断,并从arm协处理器里面读到CPU ID,这里的CPU主要是指arm架构相关的CPU型号,比如ARM9,ARM11等等。
然后跳转到__lookup_processor_type,这个函数定义在head-common.S里面,这里的bl指令会保存当前的pc在lr里 面,最后__lookup_processor_type会从这个函数返回,我们具体看看这个函数:
他这里的执行过程其实比较简单就是在__proc_info_begin和__proc_info_end这个段里面里面去读取我们注册在里面的 proc_info_list这个结构体,这个结构体的定义在arch/arm/include/asm/procinfo.h,具体实现根据你使用的 cpu的架构在arch/arm/mm/里面找到具体的实现,这里我们使用的ARM11是proc-v6.S,我们可以看看这个结构体:
对着.h我们就知道各个成员变量的含义了,他这里lookup的过程实际上是先求出这个proc_info_list的实际物理地址,并将其内容读出,然 后将其中的mask也就是我们这里的0x007f000与寄存器与之后与0x007b00进行比较,如果一样的话呢就校验成功了,如果不一样呢就会读下一 个proc_info的信息,因为proc一般都是只有一个的,所以这里一般不会循环,如果检测正确寄存器就会将正确的proc_info_list的物 理地址赋给寄存器,如果检测不到就会将寄存器值赋0,然后通过LR返回。
检测完proc_info_list以后就开始检测machine_type了,这个函数的实现也在head-common.S里面,我们看看它具体的实现:
这里的过程基本上是同proc的检查是一样的,这里主要检查芯片的类型,比如我们现在的芯片是MSM7X27FFA,这也是一个结构体,它的头文件在 arch/arm/include/asm/arch/arch.h里面(machine_desc),它具体的实现根据你对芯片类型的选择而不同,这里 我们使用的是高通的7x27,具体实现在arch/arm/mach-msm/board-msm7x27.c里面,这些结构体最后都会注册到 _arch_info_begin和_arch_info_end段里面,具体的大家可以看看vmlinux.lds或者system.map,这里的 lookup会根据bootloader传过来的nr来在__arch_info里面的相匹配的类型,没有的话就寻找下一个machin_desk结构 体,直到找到相应的结构体,并会将结构体的地址赋值给寄存器,如果没有的话就会赋值为0的。一般来说这里的machine_type会有好几个,因为不同 的芯片类型可能使用的都是同一个cpu架构。
对processor和machine的检查完以后就会检查atags parameter的有效性,关于这个atag具体的定义我们可以在./include/asm/setup.h里面看到,它实际是一个结构体和一个联合 体构成的结合体,里面的size都是以字来计算的。这里的atags param是bootloader创建的,里面包含了ramdisk以及其他memory分配的一些信息,存储在boot.img头部结构体定义的地址 中,具体的大家可以看咱以后对bootloader的分析~
这里对atag的检查主要检查其是不是以ATAG_CORE开头,size对不对,基本没什么好分析的,代码也比较好看~ 下面我们来看后面一个重头戏,就是创建初始化页表,说实话这段内容我没弄清楚,它需要对ARM VIRT MMU具有相当的理解,这里我没有太多的时间去分析spec,只是粗略了翻了ARM V7的manu,知道这里建立的页表是arm的secition页表,完成内存开始1m内存的映射,这个页表建立在kernel和atag paramert之间,一般是4000-8000之间~具体的代码和过程我这里就不贴了,大家可以看看参考的链接,看看其他大虾的分析,我还没怎么看明 白,等以后仔细研究ARM MMU的时候再回头来仔细研究了,不过代码虽然不分析,这里有几个重要的地址需要特别分析下~
这几个地址都定义在arch/arm/include/asm/memory.h,我们来稍微分析下这个头文件,首先它包含了arch /memory.h,我们来看看arch/arm/mach-msm/include/mach/memory.h,在这个里面定义了#define PHYS_OFFSET UL(0x00200000) 这个实际上是memory的物理内存初始地址,这个地址和我们以前在boardconfig.h里面定义的是一致的。然后我们再看 asm/memory.h,他里面定义了我们的memory虚拟地址的首地址#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)。
另外我们在head.S里面看到kernel的物理或者虚拟地址的定义都有一个偏移,这个偏移又是从哪来的呢,实际我们可以从arch/arm /Makefile里面找到:textofs-y := 0x00008000 TEXT_OFFSET := $(textofs-y) 这样我们再看kernel启动时候的物理地址和链接地址,实际上它和我们前面在boardconfig.h和 Makefile.boot里面定义的都是一致的~
建立初始化页表以后,会首先将__switch_data这个symbol的链接地址放在sp里面,然后获得__enable_mmu的物理地址,然后 会跳到__proc_info_list里面的INITFUNC执行,这个偏移是定义在arch/arm/kernel/asm-offset.c里面, 实际上就是取得__proc_info_list里面的__cpu_flush这个函数执行。
这个__cpu_flush在这里就是我们proc-v6.S里面的__v6_setup函数了,具体它的实现我就不分析了,都是对arm控制寄存器的操作,这里转一下它对这部分操作的注释,看完之后就基本知道它完成的功能了。
/*
* __v6_setup
*
* Initialise TLB, Caches, and MMU state ready to switch the MMU
* on. Return in r0 the new CP15 C1 control register setting.
*
* We automatically detect if we have a Harvard cache, and use the
* Harvard cache control instructions insead of the unified cache
* control instructions.
*
* This should be able to cover all ARMv6 cores.
*
* It is assumed that:
* - cache type register is implemented
*/
完成这部分关于CPU的操作以后,下面就是打开MMU了,这部分内容也没什么好说的,也是对arm控制寄存器的操作,打开MMU以后我们就可以使用虚拟 地址了,而不需要我们自己来进行地址的重定位,ARM硬件会完成这部分的工作。打开MMU以后,会将SP的值赋给PC,这样代码就会跳到 __switch_data来运行,这个__switch_data是一个定义在head-common.S里面的结构体,我们实际上是跳到它地一个函数 指针__mmap_switched处执行的。
这个switch的执行过程我们只是简单看一下,前面的copy data_loc段以及清空.bss段就不用说了,它后面会将proc的信息和machine的信息保存在__switch_data这个结构体里面,而 这个结构体将来会在start_kernel的setup_arch里面被使用到。这个在后面的对start_kernel的详细分析中会讲到。另外这个 switch还涉及到控制寄存器的一些操作,这里我不没仔细研究spec,不懂也就不说了~
好啦,switch操作完成以后就会b start_kernel了~ 这样就进入了c代码的运行了,下一篇文章仔细研究这个start_kernel的函数~~
Ref:
http://blog.csdn.net/yhmhappy2006/archive/2008/08/06/2775239.aspx
http://blog.csdn.net/sustzombie/archive/2010/06/12/5667607.aspx