3.2 Kernel启动过程

Android Kernel启动过程与标准Linux Kernel的启动过程基本一致,都是对start_kernel函数的调用和执行。本节将分析Android正常启动流程的第二步:Kernel启动过程。

本节涉及的源码文件如下:

kernel/arch/arm/kernel/head. S

kernel/arch/arm/kernel/head-common. S

kernel/init/main. c

Kernel启动过程分为两个阶段:

1)内核引导阶段。通常使用汇编语言编写,主要检查内核与当前硬件是否匹配。这部分也与硬件体系结构相关。内核引导阶段相关的代码主要位于kernel/arch/arm/kernel/head.S和kernel/arch/arm/kernel/head-common.S。

2)内核启动阶段。引导阶段结束前,将调用start_kernel()进入内核启动阶段。内核启动阶段相关的代码主要位于kernel/init/main.c。

3.2.1 内核引导阶段

head. S是Linux内核启动的汇编程序入口,但head.S中并没有直接调用start_kernel()。可以先找到调用start_kernel()的函数,然后通过回溯法找到其调用的源头。

在head-common.S中可以找到start_kernel()的调用位置,代码如下:

__INIT


@函数__mmap_switched

__mmap_switched:

@取__mmap_switched_data的地址到r3

adr r3,__mmap_switched_data

……@省略部分代码,以下都是ARM汇编指令

ARM(ldmia r3,{r4,r5,r6,r7,sp})

THUMB(ldmia r3,{r4,r5,r6,r7})

THUMB(ldr sp,[r3,#16])

……@省略部分代码

@这里真正调用了start_kernel

b start_kernel

@b指令是跳转指令

@遇到b指令,处理器跳转到给定

@的目标地址,从目标地址继续执行

ENDPROC(__mmap_switched)


从head-common.S代码中可以看到,系统在mmap_switched中通过b汇编指令调用了start_kernel。那么mmap_switched又是在哪里被调用的呢?回到head.S这个汇编入口,代码如下:


@MMU可用后,将跳转到这个地址

ldr r13,=__mmap_switched

adr lr, BSYM(1f)@返回地址(PIC)

@set TTBR1 to swapper_pg_dir

mov r8,r4

ARM(add pc, r10,#PROCINFO_INITFUNC)

THUMB(add r12,r10,#PROCINFO_INITFUNC)

THUMB(mov pc, r12)

1:b__enable_mmu@启动MMU

ENDPROC(stext)


这里将__mmap_switched的地址保存到r13,那pc指针又是什么时候指向r13的呢?在源码中搜索,发现是在turn_mmu_on结束之时将pc指针指向r13的。代码如下:


.align 5

__turn_mmu_on:

……@省略部分代码

mov r3,r13@跳转到r13,即__mmap_switched

mov pc, r3@pc指针,指向当前要运行的指令

__enable_mmu_end:

ENDPROC(__turn_mmu_on)


到这里,可以继续跟踪turn_mmu_on是在哪里调用的。这很容易找到,系统通过b汇编指令跳转到turn_mmu_on。代码如下:


__enable_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))

@load domain access register

mcr p15,0,r5,c3,c0,0

@load page table pointer

mcr p15,0,r4,c2,c0,0

@系统在这里跳转到__turn_mmu_on

b__turn_mmu_on

ENDPROC(__enable_mmu)


到这里,可以看出start_kernel的调用流程:系统在开启MMU的时候,在函数enable_mmu中调用turn_mmu_on,然后turn_mmu_on通过"mov r3,r13"和"mov pc, r3"两条指令调用mmap_switched。而函数__mmap_switched是head-common.S中的函数,在该函数中调用start_kernel。

start_kernel函数是Linux内核通用的启动函数,也是汇编代码执行完毕后的第一个C语言函数,它的实现代码位于init/main.c中。start_kernel的代码比较复杂,其中做了很多软硬件初始化的工作,比如调用了setup_arch()、timer_init()、init_IRQ、console_init()。