随笔之GoldFish Kernel启动过程中arm汇编分析

ARM 111浏览

随笔之GoldFish Kernel启动过程中arm汇编分析

  分析

电子版下载地址:进入我的资源就能找到

http://download.csdn.net/detail/innost/4834459

本节介绍Kernel启动。此时Piggy已经将vimlinux解压,BL将执行权限传给了Kernel

代码在arch/arm/kernel/head.S中。相关代码如下:

//将采用C/C++注释语句

/*

   .section是GNU ASM的语法。格式如下:

    .section name[,"flags"[,@type]]   其中,name是必须的,flags是可选。

    "ax"表示:a为section is allocatable,x为executable。

*/

   .section ".text.head", "ax"

 

//这个ENTRY(stext)有相当的含义。在kernel/vmlinux.ld.S中,也定义了一个ENTRY。在ld

//语法中,ENTRY是一个command,用来定义入口点。所以,这里就是kernel执行的入口点函数。

ENTRY(stext)

   /*

     MSR:是ARM汇编指令,用来将数据copy到status register寄存器中。cpsr_c表示要操作

     CPSR寄存器的Control标志。

   */

  

    msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode

                        @ and irqs disabled

1.1  MSR设置I/FCPU Mode

CPSR全称是Current Process Status Register,用来表示当前CPU的状态,也可用于控制。相关控制位如图1所示:

1 CPSR控制位

由图1可知:

q  N/Z/C/V控制位用来表示负//进位/溢出,属于User Flags,即可在UserMode下操作。A

q  I/F表示InterruptFast Interrupt使能位。

q  Mode用来控制CPU当前的模式。ARM CPU一共有7种模式。

根据上面的代码,首先将禁止I/F中断,并进入Supervisor模式,也就是OS运行的模式。图2ARM CPU支持的CPU模式。

2  ARM CPU支持的运行模式

另外,MSR指令操作的格式如下:

3  MSR指令格式

其中最重要的是fields,目前支持:

q  c:设置control bit。对应位为16

q  x:设置extension bit。对应位为17

q  s:设置status bit。对应位为18

q  f:设置flags field。对应位为19

4 MSR二进制格式

直接看上面的解释,还不是很清楚,因为设置的是MSR指令本身的内容,具体对应到CPSR呢,则可通过下面的伪语句得到:

5 MSR 设置说明

从代码可知:

msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE

//上面代码将设置CPSR的0到第7位,刚好是控制I/F和设置CPU模式的。

1.2  ARM CP15协处理器控制

设置好CPU模式后,下面的工作就是获取CPU的信息。在ARM中,协处理(coprocessor15中用于管理CPU信息和MMU相关的工作。CP15也是ARM中最重要的处理器,以后会经常碰到。

先看下面这条语句:

    mrc    p15, 0, r9, c0, c0        @ get processor id

MRCARM指令,用来从协处理对应的寄存器读取信息到CPU的寄存器,对应写协处理寄存器的指令是MCR。二者的语法格式(注意,是操作CP15的时候)如图6所示:

6  MRC操作CP15的格式说明

q  Rd:本指令用得是R9,也就是协处理的信息会保存到R9中。

q  CRnMRC中协处理器的主要寄存器。此处用得是C0。标准写法是C0C1一直到C15

q  CRm:附属信息。如果没有附属信息,则使用C0

q  opcode2,类似附属信息。根据CRn来决定是否需要。如果不指定,则使用0

CP15有很重要的作用,可通过操作CP15的寄存器来控制它。如图7所示:

7  CP15各个寄存器的作用

先来看此处操作的C0寄存器。

opcode2在指令中默认是0,所以将取出Main ID register的信息。

得到的结果将怎么使用呢?来看下一句指令:

bl    __lookup_processor_type        @ r5=procinfo r9=cpuid

BLARM中的跳转指令,相当于调用函数吧。__lookup_processor_type用来得到CPU信息。注意,这个函数调用的参数是R9R9的值是从CP15 C0寄存器读取出来的,而是是Main ID。下面看看此函数如何处理R9

1.3  __lookup_procesoor_type分析

该函数在head-Common.S中定义。下面逐行分析它,这里会碰到几个重要的指令及其用法。

__lookup_processor_type:

    //adr是一条伪指令,其作用是将3f标签的地址赋给R3。这个伪指令其实是可拆分成多条指令

   //由于后面的3f是相对当前PC位置而言,所以R3实际上存储的是3f的物理地址。

    adr    r3, 3f//f是forward之意。标志3在此代码之后声明

    /*

   ldm是load multiple register的意思,它的作用是将[r3]对应的内存内容存储到r5,r6,

    r7寄存器中。DA是Decrease After的意思。ARM汇编在这里有4种模式,DA,IA,DB,IB等

    此处的ldmda,将把3F所在的内容依序传递给R7,R6,R5。每传递一次,R3递减4个字节。

    */

    ldmda    r3, {r5 - r7}

上面语句执行完后:

q  R5=__proc_info_begin,这个值是虚地址。

q  R6=__proc_info_end

q  R7=.

以上几个值都是虚地址。__proc_info_begin/endld在链接时候指定的信息。

8  arc/arm/kernel/vmlinx.lds.S文件

从中可以看出,__proc_info_begin/end包含了代码中定义在.proc.info.init段的内容。如图9所示。

9  proc-V7.s定义的proc.info.init的内容

为什么是proc -v7.S文件呢,因为goldfish编译的就是这个文件。从图9可以看出,其实也就是定义了一个数据结构罢了。

接着来看代码

    //r3指向3f的物理地址,r7指向虚拟地址,而现在只能访问物理地址,所以需要找到一个offset

    sub    r3, r3, r7        @ get offset between virt&phys

    add    r5, r5, r3        @ convert virt addresses to

    add    r6, r6, r3        @ physical address space

经过上面的换算,r5,r6现在都指向__proc_info_begin/end的物理地址了。

    //ldmia将[r5]的内存信息存储到r3,r4中,每完成一次传输,r5自动加4.

1:  ldmia  r5, {r3, r4}        @ value, mask

    //下面将测试R9和mask之后的值是否是我们想要的r3的值。根据图9。应该是0x000f0000。

   //在Main ID register中,这表明[16-19]位是都是1.

    and    r4, r4, r9        @ mask wanted bits

    teq    r3, r4

    beq    2f   //如果是我们想要的数据,则跳转到2f

    //否则跳过一个PROC_INFO_SIZE,继续找,一般只有一个PROC_INFO结构体。

    add    r5, r5, #PROC_INFO_SZ    @ sizeof(proc_info_list)

    cmp    r5, r6

    blo    1b

   //如果没找到,则设置R5寄存器为0

    mov    r5, #0            @ unknown processor

2:  mov    pc, lr   //从函数返回

ENDPROC(__lookup_processor_type)

 

/*

 * 提供一个C接口的lookup_process_type函数

 */

ENTRY(lookup_processor_type)

    stmfd    sp!, {r4 - r7, r9, lr}

    mov    r9, r0

    bl    __lookup_processor_type

    mov    r0, r5

    ldmfd    sp!, {r4 - r7, r9, pc}

ENDPROC(lookup_processor_type)

 

    .long    __proc_info_begin

    .long    __proc_info_end

3:    .long    .

    .long    __arch_info_begin

    .long    __arch_info_end

lookup_process_type其实比较简单,这里就不再多说。但图9的内容以后还要回过头来继续介绍。那里将初始化CPU MMU相关的内容。

    //如果r5为空,则表示CPU信息获取是否,调用__error_p,退出整个启动

    movs    r10, r5                @ invalid processor (r5=0)?

    beq    __error_p            @ yes, error 'p'

 

否则,将调用__lookup_machine_type获取机器信息。

1.4  __lookup_machine_type分析

该函数也是在head-comm.S中定义的。

__lookup_machine_type:

    adr    r3, 3b  //b是backward的意思。标志3在此代码之前声明。

    //r4,r5,r6分别指向 label 3,__arch_info_begin和__arch_info_end

    ldmia    r3, {r4, r5, r6} 

    sub    r3, r3, r4

    add    r5, r5, r3

    add    r6, r6, r3

   //以上将得到__arch_info_begin/end的物理地址

 

1:  ldr    r3, [r5, #MACHINFO_TYPE]

    //比较r1和MACHINFO_TYPE是不是一致。注意,r1的值是BL传递给它的

    teq    r3, r1                @ matches loader number?

    beq    2f                @ found

    add    r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc

    cmp    r5, r6

    blo    1b

    mov    r5, #0                @ unknown machine

2:  mov    pc, lr

ENDPROC(__lookup_machine_type)

这里涉及到另一个关键数据结构,也就是定义在.arch.info.init段中的。如图10所示:

10  .arch.info.init

从图10可知,这个段其实对应了一个数据结构,即machine_desc.在我们的goldfish平台中,它是这么定义的:

[arch/arm/mach-goldfish/board-goldfish.c]

//还需要加上:

   nr = MACH_TYPE_GOLDFISH

   name = "Goldfish"

MACHINE_START(GOLDFISH, "Goldfish")

    .phys_io    = IO_START,

    .io_pg_offst    = ((IO_BASE) >> 18) & 0xfffc,

    .boot_params    = 0x00000100,

    .map_io        = goldfish_map_io,

    .init_irq    = goldfish_init_irq,

    .init_machine    = goldfish_init,

    .timer        = &goldfish_timer,

MACHINE_END

完整的machine_desc定义如图11所示:

11 machine_desc定义

Goldfish中,nr1441。详情可参考arch/arm/tools/machine-types.h

另外,在BootLoader调用kernel之前,传递参数情况如图12所示:

12  arch/arm/boot/head.S调用kernel前传递参数

从图12可知:

q  r1保存的是machine nr

这部分代码属于BootLoader,相当复杂。以后再细说。

假设__lookup_machine_type一切正常

    bl    __lookup_machine_type        @ r5=machinfo

    movs    r8, r5                @ invalid machine (r5=0)?

    beq    __error_a            @ yes, error 'a'

1.5  __vet_atags分析

接下来的任务就是Kernel校验BL传递的启动参数了。这部分内容和BootLoader有较大关系。

    bl    __vet_atags

此处的核心概念就是ATAG_CORE/END之类的,由BootLoaderKernel传递参数,主要是tag结构体

arch/arm/include/asm/setup.h中。BL传递的是struct tag的链表,该链表以ATAG_CORE开头,以ATAG_NONE结尾。

#define ATAG_CORE    0x54410001

#define ATAG_NONE    0x00000000

struct tag_header {

    __u32 size;

    __u32 tag;

};

struct tag {

  struct tag_header hdr;  //首先是一个头,根据头部的tag来判断下面的union是哪个

  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;

    struct tag_acorn    acorn;

    struct tag_memclk    memclk;

  } u;

};

你可以根据上面的信息自行分析__vet_atags函数。

1.6  __create_page_tables分析

下面的任务就是调用__create_page_tables创建page table

    bl    __create_page_tables  //调用__create_page_tables函数

此函数就在head.S中定义,代码如下:

__create_page_tables:

    /*

       pgtbl是head.S中定义的一个宏,见下面的分析

    */

     pgtbl    r4

pgtbl定义了一个宏,相关代码如下:

//TEXT_OFFSET是kernel镜像在内存中的偏移量。一般定义为0X8000,即32KB处

//PHYS_OFFSET:是内核镜像在内存中的起始物理地址。上面二者之和就是内核镜像在机器上的

//物理地址。Goldfish平台中,PHYS_OFFSET为0。

//PAGE_OFFSET是Kernel镜像在虚拟内存的起始地址,一般是3G处

#define KERNEL_RAM_VADDR    (PAGE_OFFSET + TEXT_OFFSET)

#define KERNEL_RAM_PADDR    (PHYS_OFFSET + TEXT_OFFSET)

.macro    pgtbl, rd //此宏调用完毕后,r4的值就是0x4000,即16KB

    ldr    rd, =(KERNEL_RAM_PADDR - 0x4000)

    .endm

接着看代码。

    mov    r0, r4

    mov    r3, #0

    add    r6, r0, #0x4000

    //STR将寄存器的值往内存中传送。r3为0,故内存的值被设置为0.每调用一次str,r0递增4

    //r0是base address,其值可自动增减。由arm address mode格式控制

1:  str    r3, [r0], #4

    str    r3, [r0], #4

    str    r3, [r0], #4

    str    r3, [r0], #4

    teq    r0, r6

    bne    1b //此循环调用完毕后,0x4000-0x8000的内存都被设置为0。此时r0=32KB

 

    //r10存储的是图9中proc_info的第三个long,也就是mmuflas,用于设置MMU参数

    ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

1.6.1  ARM MMU设置介绍

虽然上面最后一条语句是一个简单的ldr,但背后的内容却相当丰富,不把它搞清楚,后面的内容将解释不清。来看proc-V7mm_mmuflags对应的值是什么

.long   PMD_TYPE_SECT |    // #define PMD_TYPE_SECT (2 << 0)

        PMD_SECT_BUFFERABLE | //#define PMD_SECT_BUFFERABLE (1 << 2)

        PMD_SECT_CACHEABLE | //#define PMD_SECT_CACHEABLE (1 << 3)

        PMD_SECT_AP_WRITE | //#define PMD_SECT_AP_WRITE (1 << 10)

        PMD_SECT_AP_READ //#define PMD_SECT_AP_READ (1 << 11)

上面代码中把对应PMD_SECT_XXX的值显示出来,可知它无非是定义了一个32位的常量,某些位置的值为1,某些位置的值为0。为什么要怎么做呢?先来看ARM MMU所支持的虚实地址转换机制。

13  ARM MMU虚实地址转换

由图13可知:

q  虚地址VA[20-31]位和CP15 CR2[14-31]位共同构成First Level地址。

q  First Level地址将得到一个First Level Descripter,也就是图13中标明memory access的内容。

q  FLD中不同字段表明其内容是段寻址还是页寻址。主要是根据前2位来判断。如果前2位是0b10则是段寻址。

结合图13和前面的代码:

q  PMD_TYPE_SECT = 2<<0,刚好就是0b10

q  C|B控制CachableBuffable的,对应为[2,3]

q  AP对应为Access Point,对应为[10,11]位。

另外,DomainARM CPU的一个重要概念,主要和权限有关。以后碰到再说。

至此,当ldr r7 xx执行完后,r7的值包含了section base address对应的[0-12]位的值。而section base address本身却还没有赋值。

接下来的代码就是为了构造一个FLD的值。根据图13section base address应该是[20-31]

   //r6的值为当前PC值右移20位

    mov    r6, pc, lsr #20

    orr    r3, r7, r6, lsl #20        @ flags + kernel base

    //此时,r3的值就是一个基于段寻址的FLD。把它存起来。位置是r4+r6<<2

    str    r3, [r4, r6, lsl #2]      

现在r6存储的是段寻址的基地址,需要把这个值存储到对应表的位置,由于在表中,每一项是4个字节,所以这里需要乘以4,也就是lsl #2

稍微解释下这里左移4的原因:

1 r4存储的是表的起始地址

2 r6存储的是offset

3 r3存储的是往r4[offset]的值

4 由于1个offset实际上是4个字节,所以真实存储的位置就是r4[4*offset] = r3

1.6.2  设置页表

当理解上面代码后,下面就是把kernel虚拟地址的位置存储到r4表中了

继续看代码

   //立即数的计算比较难理解,网上也没有相关说法。不过,只要知道下面这段代码就是存储kernel

  //虚拟地址到对应页表位置即可

   add    r0, r4,  #(KERNEL_START & 0xff000000) >> 18

   str    r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!

    ldr    r6, =(KERNEL_END - 1)

    add    r0, r0, #4  //r0 = ro+4

    add    r6, r4, r6, lsr #18 //r6=r4+r6>>18

1:    cmp    r0, r6

    add    r3, r3, #1 << 20 //r3 += 1<<20,每次递增1M

    //ls是condition code,表示小于等于,即只要r0<=r6,strls就会执行

    strls    r3, [r0], #4

    bls    1b

 

   //map物理地址前1M到对应位置

   add    r0, r4, #PAGE_OFFSET >> 18

    orr    r6, r7, #(PHYS_OFFSET & 0xff000000)

    .if    (PHYS_OFFSET & 0x00f00000)

    orr    r6, r6, #(PHYS_OFFSET & 0x00f00000)

    .endif

    str    r6, [r0]

 

    mov    pc, lr

ENDPROC(__create_page_tables)

    .ltorg

1.6.3 总结

建议大家仔细体会create_page_tables这段内容。虽然以后不太可能会使用它们,但把这段代码搞清楚还是一个比较有意思的过程。

1.7  剩余工作

回到head.S,最后还剩下几句代码:

//将__switch_data的位置存储到r13

 ldr r13, __switch_data 

 //获取__enable_mmu标签的地址,并保存到lr中

 adr lr, __enable_mmu 

 //r10存储的是__v7_proc_info的地址,#PROCINFO_INITFUNC是一个偏移量

 //执行完下条语句后,pc指向__v7_proc_info的b __v7_setup,故下面这条语句就是

 //执行__v7_setup函数

 add pc, r10, #PROCINFO_INITFUNC

ENDPROC(stext)

1.7.1  __switch_data说明

__switch_data标签如下,主要存储了一些数据。

[head-common.S]

 .type __switch_data, %object

__switch_data:

 .long __mmap_switched

 .long __data_loc   @ r4

 .long _data    @ r5

 .long __bss_start   @ r6

 .long _end    @ r7

 .long processor_id   @ r4

 .long __machine_arch_type  @ r5

 .long __atags_pointer   @ r6

 .long cr_alignment   @ r7

 .long init_thread_union + THREAD_START_SP @ sp

以后再讨论具体作用。

1.7.2  __v7_setup

先来看

add pc, r10, #PROCINFO_INITFUNC

实际上就是执行__v7_setup函数。代码在mm/proc-v7.S中。

adr r12, __v7_setup_stack  @ the local stack

 stmia r12, {r0-r5, r7, r9, r11, lr}

 bl v7_flush_dcache_all

 ldmia r12, {r0-r5, r7, r9, r11, lr}

 mov r10, #0

dsb

#ifdef CONFIG_MMU  //goldfish定义了这个配置项

 mcr p15, 0, r10, c8, c7, 0  @ invalidate I + D TLBs

 mcr p15, 0, r10, c2, c0, 2  @ TTB control register

 orr r4, r4, #TTB_FLAGS

 mcr p15, 0, r4, c2, c0, 1  @ load TTB1

 mov r10, #0x1f   @ domains 0, 1 = manager

 mcr p15, 0, r10, c3, c0, 0  @ load domain access register

#endif

 ldr r5, =0xff0aa1a8

 ldr r6, =0x40e040e0

 mcr p15, 0, r5, c10, c2, 0  @ write PRRR

 mcr p15, 0, r6, c10, c2, 1  @ write NMRR

 adr r5, v7_crval

 ldmia r5, {r5, r6}

    mrc p15, 0, r0, c1, c0, 0  @ read control register

 bic r0, r0, r5   @ clear bits them

 orr r0, r0, r6   @ set them

//最后一句,将lr赋值给pc。执行完后,将跳到__enable_mmu函数。

 mov pc, lr    @ return to head.S:__ret

ENDPROC(__v7_setup)

上面代码大多是执行ARM v7 CPUMMU相关设置的,而其中的汇编语句到比较简单。这也是ARM MMU设置的核心内容。下面我们将结合ARM CPU Rerference简单介绍下这些设置的内容。

请务必从ARM官方网页上下载下面两个文档:

q  DDI0344D_cortex_a8_r2p1_trm.pdf:介绍CORTEX A8相关内容

q  DDI0406B_arm_architecture_reference_manual_errata_markup_10_0:最新的ARM架构参考手册

1.     如何看懂MMU设置并掌握理论知识

以下面这个设置为例:

mcr p15, 0, r10, c8, c7, 0

打开参考文档DDI0344D_cortex_a8_r2p1_trm.pdf的第112页。从这一页开始,C15协处理器的各个寄存器的配置都有详细的说明。如图14所示

14 C8寄存器的设置

上图中,左边空白区域对应的是C8。可知,c8,c7,0的组合对应的是Invalidate unified TLB unlocked entries.详细说明在page3-99

如果在此文档中碰到有不理解的内容,就需要参考DDI0406B_arm_architecture_reference_manual_errata_markup_10_0。该文档会介绍一些理论知识。

篇幅原因,我就不在这里啰嗦。已经告诉大家如何钓鱼了,请大家自己尝试!

1.7.3  __enable_mmu介绍

__v7_setup最后已经的mov pc, lr将使得CPU跳转到__enable_mmu处,其代码如下所示:

__enable_mmu:

#ifdef CONFIG_ALIGNMENT_TRAP

 orr r0, r0, #CR_A

#else

 bic r0, r0, #CR_A

#endif

#ifdef CONFIG_CPU_DCACHE_DISABLE

 bic r0, r0, #CR_C

#endif

#ifdef CONFIG_CPU_BPREDICT_DISABLE

 bic r0, r0, #CR_Z

#endif

#ifdef CONFIG_CPU_ICACHE_DISABLE

 bic r0, r0, #CR_I

#endif

//设置domain的权限,请参考前面的书籍了解DOMAIN在ARM MMU中的意义

mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) |

        domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) |

        domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) |

        domain_val(DOMAIN_IO, DOMAIN_CLIENT))

//请参考前面的方法,了解下面这两条语句的实际作用

 mcr p15, 0, r5, c3, c0, 0  @ load domain access register

 mcr p15, 0, r4, c2, c0, 0  @ load page table pointer

 b __turn_mmu_on //跳转到__turn_mmu_on

ENDPROC(__enable_mmu)

简单看看__turn_mmu_on

__turn_mmu_on:

 mov r0, r0 //类似nop的空指令,浪费一点CPU时间,怕引起race condition发生

//c1,c0这两个控制MMU的设置

 mcr p15, 0, r0, c1, c0, 0  @ write control reg

 mrc p15, 0, r3, c0, c0, 0  @ read id reg

 mov r3, r3

 mov r3, r3

//此时,MMU就正式启动了

 mov pc, r13 //r13指向__switch_data

ENDPROC(__turn_mmu_on)

MMU启动后,我们也无需管什么物理地址还是虚拟地址,直接去看对应地址的代码即可。如果您非对这个转换过程很感兴趣,建议您把那两个参考书好好瞅瞅。

1.7.4  __mmaped_switched介绍

__switch_data第一个定义的就是__mmaped_switchedPC将执行这里的指令:

__mmap_switched:

 adr r3, __switch_data + 4

 

 ldmia r3!, {r4, r5, r6, r7}

 cmp r4, r5    @ Copy data segment if needed

1: cmpne r5, r6

 ldrne fp, [r4], #4

 strne fp, [r5], #4

 bne 1b

 

 mov fp, #0    @ Clear BSS (and zero fp)

1: cmp r6, r7

 strcc fp, [r6],#4

 bcc 1b

 

 ldmia r3, {r4, r5, r6, r7, sp}

 str r9, [r4]   @ Save processor ID

 str r1, [r5]   @ Save machine type

 str r2, [r6]   @ Save atags pointer

 bic r4, r0, #CR_A   @ Clear 'A' bit

 stmia r7, {r0, r4}   @ Save control register values

//上面我就懒得废话了,下面这句代码相信各位都很了解。执行start_kernel函数。

 b start_kernel

ENDPROC(__mmap_switched)

 

二总结

我觉得需要说明下为什么写这篇文章:

早在20107月的时候,我就看了那本鼎鼎大名的《ARM体系结构与编程》,这应该是第一本系统介绍ARM体系结构和编程的书。但是没看懂,全是枯燥的ARM CPU设置,纯教科书。

最近因为工作的原因,想把ARM这块重新捡起来,想起2年的痛苦,觉得应该换个思路。ARM也好,汇编也好,我们应该关注它的目的,而不是具体它是怎么实现的。即了解What to do比了解How to do更重要(仅我个人目的而言,前者重要。不过在某些追求细节的时候,后者重要。需要你自己去判断)。根据这个思路,我选择以Linux Kernel启动为分析对象,大致研究流程如下:

q  先花几天时间了解下ARM汇编的大概语句。

q  直接上代码分析。不过你得对Kernel启动的流程稍有了解。还好我在《深入理解AndroidI》写完后,花了点时间把这块整理了下。请参考http://blog.csdn.net/innost/article/details/6693731

q  碰到不懂的汇编语句,就查参考手册。这些还只是针对一些没有背景知识的语句。

q  当碰到类似CP15操作的语句时,其背后往往包含了较多的CPU相关的知识,这时候就需要查阅前面提到的两本参考书籍,去真真正正了解ARM CPU运行的相关原理。

大概经过2周先痛苦挣扎,到后面豁然开朗的过程,后续的研究就非常非常流畅了。