Android系统启动流程分析

         本文讲解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
  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值