本文讲解Android系统在启动过程中的关键动作,摈弃特定平台之间的差异,讨论共性的部分,至于启动更加详细的过程,需要结合代码分析,这里给出流程框架,旨在让大家对开机过程更明了。各个平台启动流程基本类似,但代码追踪却有较大区别。高通,MTK,Sprd各有不同处理,均有各自的一套源码,本文代码以展讯平台SC7710系列Android4.1源码进行追踪。
1,Android启动概述
Android系统启动基本可分为3个阶段:Bootloader启动,linux启动,Android启动。
1.1,Bootloader启动
系统引导bootloader(bootable/bootloader/* u-boot/*),加电后,CPU先执行bootloader程序,正常启动系统,加载boot.img,中包含内核。
源码:bootable/bootloader/* , 说明:加电后,CPU将先执行bootloader程序,此处有三种选择:
a: 开机按Camera+Power启动到fastboot,即命令或SD卡烧写模式,不加载内核及文件系统,此处可以进行工厂模式的烧写
b: 开机按Home+Power启动到recovery模式,加载recovery.img,recovery.img包含内核,基本的文件系统,用于工程模式的烧写
c:开机按Power,正常启动系统,加载boot.img,boot.img包含内核,基本文件系统,用于正常启动手机(以下只分析正常启动的情况)
1.2,linux启动
由bootloader加载kernel,kernel经自解压,初始化,载入built-in驱动程序,完成启动。kernel启动后会创建若干内核线程,之后装入并执行程序/sbin/init/,载入init process,切换至user-space。
1.3,Android启动
1.3.1,init进程启动
源码:system/core/init/*
配置文件:system/rootdir/init.rc
说明:init是一个由内核启动的用户级进程,它按照init.rc中的设置执行:启动服务(这里的服务指linux底层服务,如adbd提供adb支持,vold提供SD卡挂载等),执行命令和按其中的配置语句执行相应功能。
1.3.2,zygote服务启动
源码:frameworks/base/cmds/app_main.cpp等。
说明:zygote是一个在init.rc中被指定启动的服务,该服务对应的命令是/system/bin/app_process。
作用:建立Java Runtime,建立虚拟机;建立Socket接收ActivityManangerService的请求,用于Fork应用程序;启动System Server。
1.3.3,systemserver服务启动
源码:frameworks/base/services/java/com/android/server/SystemServer.java
说明:被zygote启动,通过System Manager管理android的服务(这里的服务指frameworks/base/services下的服务,如卫星定位服务,剪切板服务等)。
1.3.4,launcher桌面启动
源码:ActivityManagerService.java为入口,packages/apps/launcher*实现。
说明:系统启动成功后SystemServer使用xxx.systemReady()通知各个服务,系统已经就绪,桌面程序Home就是在ActivityManagerService.systemReady()通知的过程中建立的,最终调用startHomeActivityLocked()启launcher。
1.3.5,lockscreen启动
源码:frameworks/policies/base/phone/com/android/internal/policy/impl/*lock*
说明:系统启动成功后SystemServer调用wm.systemReady()通知WindowManagerService,进而调用PhoneWindowManager,最终通过LockPatternKeyguardView显示解锁界面,跟踪代码可以看到解锁界面并不是一个Activity,这是只是向特定层上绘图,其代码了存放在特殊的位置。
1.3.6,othersapp启动
源码:frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
说明:系统启动成功后SystemServer调用ActivityManagerNative.getDefault().systemReady()通知ActivityManager启动成功,ActivityManager会通过置变量mBooting,通知它的另一线程,该线程会发送广播android.intent.action.BOOT_COMPLETED以告知已注册的第三方程序在开机时自动启动。
2,bootloader启动详细分析
2.1,Bootloader的定义和种类
简单地说,BootLoader是在操作系统运行之前运行的一段程序,它可以将系统的软硬件环境带到一个合适状态,为运行操作系统做好准备。这样描述是比较抽象的,但是它的任务确实不多,终极目标就是把OS拉起来运行。在嵌入式系统世界里存在各种各样的Bootloader,种类划分也有多种方式。除了按照处理器体系结构不同划分以外,还有功能复杂程度的不同。
先区分一下Bootloader和Monitor[l1] : 严格来说,Bootloader只是引导OS运行起来的代码;而Monitor另外还提供了很多的命令行接口,可以进行调试、读写内存、烧写Flash、配置环境变量等。在开发过程中Monitor提供了很好地调试功能,不过在开发结束之后,可以完全将其设置成一个Bootloader。所以习惯上将其叫做Bootloader。
Bootloader |
Monitor |
描述 |
X86 |
ARM |
PowerPC |
U-boot |
是 |
通用引导程序 |
是 |
是 |
是 |
RedBoot |
是 |
基于eCos的引导程序 |
是 |
是 |
是 |
BLOB |
否 |
LART(主板)等硬件平台的引导程序 |
否 |
是 |
否 |
LILO |
否 |
Linux磁盘引导程序 |
是 |
否 |
否 |
GRUB |
否 |
GNU的LILO替代程序 |
是 |
否 |
否 |
Loadlin |
否 |
从DOS引导Linux |
是 |
否 |
否 |
Vivi |
是 |
韩国mizi 公司开发的bootloader |
否 |
是 |
否 |
更多bootloader还有:ROLO、Etherboot、ARMboot 、LinuxBIOS等。
对于每种体系结构,都有一系列开放源码Bootloader可以选用:
X86:X86的工作站和服务器上一般使用LILO和GRUB。
ARM:最早有为ARM720处理器开发板所做的固件,又有了armboot,StrongARM平台的blob,还有S3C2410处理器开发板上的vivi等。现在armboot已经并入了U-Boot,所以U-Boot也支持ARM/XSCALE平台。U-Boot已经成为ARM平台事实上的标准Bootloader。
PowerPC:最早使用于ppcboot,不过现在大多数直接使用U-boot。
MIPS:最早都是MIPS开发商自己写的bootloader,不过现在U-boot也支持MIPS架构。
M68K:Redboot能够支持m68k系列的系统。
2.2,Arm特定平台的bootloader
到目前为止,我们公司已经做过多个Arm平台的android方案,包括:marvell(pxa935)、informax(im9815)、mediatek(mt6516/6517)、broadcom(bcm2157)。由于不同处理器芯片厂商对arm core的封装差异比较大,所以不同的arm处理器,对于上电引导都是由特定处理器芯片厂商自己开发的程序,这个上电引导程序通常比较简单,会初始化硬件,提供下载模式等,然后才会加载通常的bootloader。
下面是几个arm平台的bootloader方案:
marvell(pxa935) : bootROM + OBM [l4] + BLOB
informax(im9815) : bootROM + barbox + U-boot
mediatek(mt6516/6517) : bootROM + pre-loader[l5] + U-boot
broadcom(bcm2157) : bootROM + boot1/boot2 + U-boot
为了明确U-boot之前的两个loader的作用,下面以broadcom平台为例,看下在上电之后到U-boot的流程,如图1.2.1:
图1.2.1 broadcom平台上电流程
2.3,uboot启动流程详解
最常用的bootloader还是U-boot,可以引导多种操作系统,支持多种架构的CPU。它支持的操作系统有:Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS等,支持的CPU架构有:ARM、PowerPC、MISP、X86、NIOS、Xscale等。手机系统不像其他的嵌入式系统,它还需要在启动的过程中关心CP的启动,这个时候就涉及到CP的image和唤醒时刻,而一般的嵌入式系统的uboot只负责引导OS内核。所以这里我们也暂不关心CP的启动,而主要关心AP侧。
从上面第二小节中可以看出,bootloader通常都包含有处理器厂商开发的上电引导程序,不过也不是所有的处理都是这样,比如三星的S3C24X0系列,它的bootROM直接跳到U-boot中执行,首先由bootROM将U-boot的前4KB拷贝到处理器ISRAM,接着在U-boot的前4KB中必须保证要完成的两项主要工作:初始化DDR,nand和nand控制器,接着将U-boot剩余的code拷贝到SDRAM中,然后跳到SDRAM的对应地址上去继续跑U-boot。
所以U-boot的启动过程,大致上可以分成两个阶段:第一阶段,汇编代码;第二阶段,c代码。
2.3.1,汇编代码阶段
U-boot的启动由u-boot/arch/arm/cpu/xxx/u-boot.lds开始,其引导调用u-boot/arch/arm/cpu/xxx/start.S。u-boot.lds:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
arch/arm/cpu/arm920t/start.o (.text)//调用对应的start.S,start.o由start.S编译生成
*(.text)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data)
}
. = ALIGN(4);
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
.rel.dyn : {
__rel_dyn_start = .;
*(.rel*)
__rel_dyn_end = .;
}
.dynsym : {
__dynsym_start = .;
*(.dynsym)
}
.bss __rel_dyn_start (OVERLAY) : {
__bss_start = .;
*(.bss)
. = ALIGN(4);
_end = .;
}
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
}
对应的Makefile文件如下:
include $(TOPDIR)/config.mk
LIB = $(obj)lib$(CPU).o
START = start.o
COBJS-y += cpu.o
COBJS-$(CONFIG_USE_IRQ) += interrupts.o
SRCS := $(START:.o=.S) $(SOBJS:.o=.S) $(COBJS-y:.o=.c)
OBJS := $(addprefix $(obj),$(COBJS-y) $(SOBJS))
START := $(addprefix $(obj),$(START))
all: $(obj).depend $(START) $(LIB)
$(LIB): $(OBJS)
$(call cmd_link_o_target, $(OBJS))
#########################################################################
# defines $(obj).depend target
include $(SRCTREE)/rules.mk
sinclude $(obj).depend
所以U-boot的第一条指令从u-boot/arch/arm/cpu/xxx/start.S文件开始,第一阶段主要做了如下事情:
(1). 设置CPU进入SVC模式(系统管理模式),cpsr[4:0]=0xd3。
(2). 关中断,INTMSK=0xFFFFFFFF, INTSUBMSK=0x3FF。
(3). 关看门狗,WTCON=0x0。
(4). 调用s3c2410_cache_flush_all函数,使TLBS,I、D Cache,WB中数据失效。
(5). 时钟设置CLKDIVN=0x3 , FCLK:HCLK:PCLK = 1:2:4。
(6). 读取mp15的c1寄存器,将最高两位改成11,表示选择了异步时钟模型。
(7). 检查系统的复位状态,以确定是不是从睡眠唤醒。
#include <asm-offsets.h>
#include <common.h>
#include <config.h>
.globl _start
_start: b start_code
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
......
//开始的一些初始化操作
start_code:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
bl coloured_LED_init
bl red_LED_on
......
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //重点函数
#endif
/* Set stackpointer in internal RAM to call board_init_f */
/*board.c的board_init_f()函数*/
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f//board初始化
.globl relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
/* Set up the stack */
stack_setup:
mov sp, r4
adr r0, _start
cmp r0, r6
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r2, _TEXT_BASE
ldr r3, _bss_start_ofs
add r2, r0, r3 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop
#ifndef CONFIG_PRELOADER
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop
#endif
clear_bss:
#ifndef CONFIG_PRELOADER
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
ldr r3, _TEXT_BASE /* Text base */
mov r4, r6 /* reloc addr */
add r0, r0, r4
add r1, r1, r4
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
bne clbss_l
bl coloured_LED_init
bl red_LED_on
#endif
/*
* We are done. Do not return, instead branch to second part of board
* initialization, now running from RAM.
*/
#ifdef CONFIG_NAND_SPL
ldr r0, _nand_boot_ofs
mov pc, r0
_nand_boot_ofs:
.word nand_boot
#else
ldr r0, _board_init_r_ofs
adr r1, _start
add lr, r0, r1
add lr, lr, r9
/* setup parameters for board_init_r */
mov r0, r5 /* gd_t */
mov r1, r6 /* dest_addr */
/* jump to it ... */
mov pc, lr
/*board_init_r 此处走至u-boot\arch\arm\lib\board.c的board_init_r()函数 */
_board_init_r_ofs:
.word board_init_r - _start
#endif
/*至此走至C代码的阶段*/
_rel_dyn_start_ofs:
.word __rel_dyn_start - _start
_rel_dyn_end_ofs:
.word __rel_dyn_end - _start
_dynsym_start_ofs:
.word __dynsym_start - _start
......
#endif
根据这几条语句来判断系统是从nand启动的还是直接将程序下载到SDRAM中运行的,这里涉及到运行时域 和位置无关代码的概念,ldr r0,_TEXT_BASE的作用是将config.mk文件中定义的TEXT_BASE值(0x33f80000)装载到r0中,adr r1,_start该指令是条伪指令,在编译的时候会被转换成ADD或SUB指令根据当前pc值计算出_start标号的地址,这样的话就可以知道当前程序在什么地址运行(位置无关代码:做成程序的所有指令都是相对寻址的指令,包括跳转指令等,这样代码就可以不在链接所指定的地址上运行)。在上电之后,系统从nand启动,这里得到r0和r1值是不一样的,r0=0x33f80000,而r1=0x00000000。所以接下来会执行cpu_init_crit函数。
cpu_init_crit函数,主要完成了两个工作:首先使ICache and Dcache,TLBs中早期内容失效,再设置p15 control register c1,关闭MMU,Dcache,但是打开了Icache和Fault checking,(要求mmu和Dcache是必须要关闭的,而Icache可以打开可以关闭);其次调用/board/nextdvr2410/memsetup.S文件中的memsetup函数来建立对SDRAM的访问时序。
Relocate函数,加载nand flash中的uboot到SDRAM中,代码会加载到0x33f80000开始的地址,空间大小是512。
//这里参考的是展讯平台7710的源代码,所以并无start_armboot函数,取而代之的是board_init_r函数。请知悉。
ldr pc, _start_armboot
_start_armboot: .word start_armboot
这里将会进入第二阶段的c代码部分:board_init_r()函数,/u-boot/arch/arm/lib/board.c。
2.3.2,C代码阶段
先看/u-boot/arch/arm/lib/board.c的board_init_r()函数:
void board_init_r (gd_t *id, ulong dest_addr)
{
......
/**一系列初始化操作之后 重点为do_cboot(NULL, 0, 1, NULL)和main_loop ()*/
board_init(); /* Setup chipselects */
boot_pwr_check();
#ifdef CONFIG_SERIAL_MULTI
serial_initialize();
#endif
debug ("Now running in RAM - U-Boot at: %08lx\n", dest_addr);
#ifdef CONFIG_LOGBUFFER
logbuff_init_ptrs ();
#endif
#ifdef CONFIG_POST
post_output_backlog ();
#endif
/* The Malloc area is immediately below the monitor copy in DRAM */
malloc_start = dest_addr - TOTAL_MALLOC_LEN;
#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(4);
#endif
mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);
#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(5);
#endif
boot_pwr_check();
#if !defined(CONFIG_SYS_NO_FLASH)
puts ("FLASH: ");
if ((flash_size = flash_init ()) > 0) {
# ifdef CONFIG_SYS_FLASH_CHECKSUM
print_size (flash_size, "");
/*
* Compute and print flash CRC if flashchecksum is set to 'y'
*
* NOTE: Maybe we should add some WATCHDOG_RESET()? XXX
*/
s = getenv ("flashchecksum");
if (s && (*s == 'y')) {
printf (" CRC: %08X",
crc32 (0, (const unsigned char *) CONFIG_SYS_FLASH_BASE, flash_size)
);
}
putc ('\n');
# else /* !CONFIG_SYS_FLASH_CHECKSUM */
print_size (flash_size, "\n");
# endif /* CONFIG_SYS_FLASH_CHECKSUM */
} else {
puts (failed);
hang ();
}
#endif
boot_pwr_check();
#if !defined(CONFIG_EMMC_BOOT)
#if defined(CONFIG_CMD_NAND)
puts ("NAND: ");
ret = nand_init(); /* go init the NAND */
if (ret) {
puts ("NAND init error ");
while(1);
}
#endif
#endif
boot_pwr_check();
#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(6);
#endif
#if defined(CONFIG_CMD_ONENAND)
#if !(defined CONFIG_TIGER && defined CONFIG_EMMC_BOOT)
onenand_init();
#endif
#endif
#ifdef CONFIG_GENERIC_MMC
puts("MMC: ");
mmc_initialize(bd);
#endif
#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif
#ifdef CONFIG_EMMC_BOOT
mmc_legacy_init(1);
#endif
/* initialize environment */
env_relocate ();
boot_pwr_check();
#ifdef CONFIG_VFD
/* must do this after the framebuffer is allocated */
drv_vfd_init();
#endif /* CONFIG_VFD */
/*tempaily use for tiger to avoid died as refreshing LCD*/
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
stdio_init (); /* get the devices list going. */
boot_pwr_check();
jumptable_init ();
boot_pwr_check();
#if defined(CONFIG_API)
/* Initialize API */
api_init ();
#endif
char fake[4]="fak";
setenv("splashimage", fake);
console_init_r (); /* fully init console as a device */
boot_pwr_check();
#if defined(CONFIG_ARCH_MISC_INIT)
/* miscellaneous arch dependent initialisations */
arch_misc_init ();
#endif
#if defined(CONFIG_MISC_INIT_R)
/* miscellaneous platform dependent initialisations */
misc_init_r ();
#endif
/* set up exceptions */
interrupt_init ();
/* enable exceptions */
enable_interrupts ();
boot_pwr_check();
/* Perform network card initialisation if necessary */
#if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
/* XXX: this needs to be moved to board init */
if (getenv ("ethaddr")) {
uchar enetaddr[6];
eth_getenv_enetaddr("ethaddr", enetaddr);
smc_set_mac_addr(enetaddr);
}
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = ge