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)
- *(.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:
-
-
-
- mrs r0, cpsr
- bic r0, r0, #0x1f
- orr r0, r0, #0xd3
- msr cpsr, r0
-
- bl coloured_LED_init
- bl red_LED_on
-
- ......
-
-
-
-
-
- #ifndef CONFIG_SKIP_LOWLEVEL_INIT
- bl cpu_init_crit
- #endif
-
-
-
- call_board_init_f:
- ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
- bic sp, sp, #7
- ldr r0,=0x00000000
- bl board_init_f
-
-
- .globl relocate_code
- relocate_code:
- mov r4, r0
- mov r5, r1
- mov r6, r2
-
-
- stack_setup:
- mov sp, r4
-
- adr r0, _start
- cmp r0, r6
- beq clear_bss
- mov r1, r6
- ldr r2, _TEXT_BASE
- ldr r3, _bss_start_ofs
- add r2, r0, r3
-
- copy_loop:
- ldmia r0!, {r9-r10}
- stmia r1!, {r9-r10}
- cmp r0, r2
- blo copy_loop
-
- #ifndef CONFIG_PRELOADER
-
-
-
- ldr r0, _TEXT_BASE
- sub r9, r6, r0
- ldr r10, _dynsym_start_ofs
- add r10, r10, r0
- ldr r2, _rel_dyn_start_ofs
- add r2, r2, r0
- ldr r3, _rel_dyn_end_ofs
- add r3, r3, r0
- fixloop:
- ldr r0, [r2]
- add r0, r0, r9
- ldr r1, [r2, #4]
- and r7, r1, #0xff
- cmp r7, #23
- beq fixrel
- cmp r7, #2
- beq fixabs
-
- b fixnext
- fixabs:
-
- mov r1, r1, LSR #4
- add r1, r10, r1
- ldr r1, [r1, #4]
- add r1, r1, r9
- b fixnext
- fixrel:
-
- ldr r1, [r0]
- add r1, r1, r9
- fixnext:
- str r1, [r0]
- add r2, r2, #8
- 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
- mov r4, r6
- add r0, r0, r4
- add r1, r1, r4
- mov r2, #0x00000000
-
- clbss_l:str r2, [r0]
- add r0, r0, #4
- cmp r0, r1
- bne clbss_l
-
- bl coloured_LED_init
- bl red_LED_on
- #endif
-
-
-
-
-
- #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
-
- mov r0, r5
- mov r1, r6
-
- mov pc, lr
-
- _board_init_r_ofs:
- .word board_init_r - _start
- #endif
-
- _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)
- {
- ......
-
- board_init();
-
- 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
-
-
- 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, "");
-
-
-
-
-
- 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();
- 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
-
- env_relocate ();
- boot_pwr_check();
-
- #ifdef CONFIG_VFD
-
- drv_vfd_init();
- #endif /* CONFIG_VFD */
-
-
-
-
- gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
-
- stdio_init ();
- boot_pwr_check();
-
- jumptable_init ();
- boot_pwr_check();
-
- #if defined(CONFIG_API)
-
- api_init ();
- #endif
- char fake[4]="fak";
- setenv("splashimage", fake);
-
- console_init_r ();
- boot_pwr_check();
-
- #if defined(CONFIG_ARCH_MISC_INIT)
-
- arch_misc_init ();
- #endif
- #if defined(CONFIG_MISC_INIT_R)
-
- misc_init_r ();
- #endif
-
-
- interrupt_init ();
-
- enable_interrupts ();
- boot_pwr_check();
-
-
- #if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
-
- if (getenv ("ethaddr")) {
- uchar enetaddr[6];
- eth_getenv_enetaddr("ethaddr", enetaddr);
- smc_set_mac_addr(enetaddr);
- }
- #endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */
-
-
- if ((s = getenv ("loadaddr")) != NULL) {
- load_addr = simple_strtoul (s, NULL, 16);
- }
- #if defined(CONFIG_CMD_NET)
- if ((s = getenv ("bootfile")) != NULL) {
- copy_filename (BootFile, s, sizeof (BootFile));
- }
- #endif
- boot_pwr_check();
-
- #ifdef BOARD_LATE_INIT
- board_late_init ();
- #endif
- ......
-
- #ifdef SPRD_EVM_TAG_ON
- SPRD_EVM_TAG(11);
- #endif
- extern int do_cboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]);
- boot_pwr_check();
-
- do_cboot(NULL, 0, 1, NULL);
-
- for (;;) {
- main_loop ();
- }
-
-
- }
该段代码完成了一些设备的初始化do_cboot(NULL, 0, 1, NULL)和main_loop ()是此处重点函数,其中do_cboot(NULL, 0, 1, NULL)的实现在u-boot/property/cmd_cboot.c而main_loop()则是在u-boot/common/main.c中。先看u-boot/property/cmd_cboot.c。
- int boot_pwr_check(void)
- {
- static int total_cnt = 0;
- if(!power_button_pressed())
- total_cnt ++;
- return total_cnt;
- }
- #define mdelay(_ms) udelay(_ms*1000)
-
-
- int do_cboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
- {
- uint32_t key_mode = 0;
- uint32_t key_code = 0;
- volatile int i;
-
- if(argc > 2)
- goto usage;
-
- #ifdef CONFIG_SC8830
- if(cali_file_check())
- calibration_detect(2);
- #endif
- #ifdef CONFIG_SC7710G2
- {
- extern void set_cp_emc_pad(void);
- set_cp_emc_pad();
- }
- #endif
- CHG_Init();
-
- #ifdef CONFIG_SC8830
- DCDC_Cal_ArmCore();
-
- #endif
-
- #ifdef CONFIG_AUTOBOOT
- normal_mode();
- #endif
-
- #ifdef CONFIG_SC7710G2
- if(!pbint2_connected())
- normal_mode();
- #endif
-
- boot_pwr_check();
-
- #ifdef CONFIG_SC8800G
- CHG_ShutDown();
- if(charger_connected()){
- mdelay(10);
- CHG_TurnOn();
- }else{
-
- if(is_bat_low()){
- printf("shut down again for low battery\n");
- power_down_devices();
- while(1)
- ;
- }
- }
- #else
-
- #ifndef CONFIG_MACH_CORI
- if(is_bat_low()){
- printf("shut down again for low battery\n");
- mdelay(10000);
- power_down_devices();
- while(1)
- ;
- }
- #endif
- #endif
-
- boot_pwr_check();
- board_keypad_init();
- boot_pwr_check();
-
- #ifdef CONFIG_SPRD_SYSDUMP
- write_sysdump_before_boot();
- #endif