Android系统启动流程 -- bootloader

Android系统启动流程 -- bootloader
 
BootLoader:在嵌入式操作系统中,BootLoader是在 操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用 操作系统内核准备好正确的环境。在 嵌入式系统中,通常并没有像BIOS那样的 固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。在一个基于ARM7TDMI core的 嵌入式系统中,系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的BootLoader程序。

摘要:本文讲解Android系统在启动过程中的关键动作,摈弃特定平台之间的差异,讨论共性的部分,至于启动更加详细的过程,需要结合代码分析,这里给出流程框架,旨在让大家对开机过程更明了。

 

关键词:U-boot、Linux、Android

 

目录

       第一部分:Bootloader启动

一、Bootloader的定义和种类

二、Arm特定平台的Bootloader

三、U-boot启动流程分析

 

       第二部分:Linux启动

一、zImage是怎样炼成的?

二、linux的c启动阶段

第三部分:Android启动

一、init进程

二、init启动的各种服务

三、android启动图示

             

       对于Android整个启动过程来说,基本可以划分成三个阶段:Bootloader引导、Linux kernel启动、Android启动。下面分别对每个阶段一一展开讨论。

 

第一部分:Bootloader启动

一、             Bootloader的定义和种类

简单地说,BootLoader是在操作系统运行之前运行的一段程序,它可以将系统的软硬件

环境带到一个合适状态,为运行操作系统做好准备。这样描述是比较抽象的,但是它的任务确实不多,终极目标就是把OS拉起来运行。

 

在嵌入式系统世界里存在各种各样的Bootloader,种类划分也有多种方式。除了按照处

理器体系结构不同划分以外,还有功能复杂程度的不同。

先区分一下Bootloader和Monitor[l1] : 严格来说,Bootloader只是引导OS运行起来的代

码;而Monitor另外还提供了很多的命令行接口,可以进行调试、读写内存、烧写Flash、配置环境变量等。在开发过程中Monitor提供了很好地调试功能,不过在开发结束之后,可以完全将其设置成一个Bootloader。所以习惯上将其叫做Bootloader。

 

Bootloader

Monitor?

描述

X86

ARM

PowerPC

U-boot

通用引导程序

RedBoot[l2] 

基于eCos的引导程序

BLOB[l3] 

(StrongARM构架)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系列的系统。

      

二、             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平台上电流程

 

 

三、             U-boot启动流程分析

最常用的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[l6] 的启动过程,大致上可以分成两个阶段:第一阶段,汇编代码;第二阶段,c代码。

      

3.1 第一阶段

       U-boot的第一条指令从cpu/arm920t/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. 检查系统的复位状态,以确定是不是从睡眠唤醒。

8.  ldr r0,_TEXT_BASE

    adr r1,_start

    cmp r0,r1

    blne cpu_init_crit

    根据这几条语句来判断系统是从nand启动的还是直接将程序下载到SDRAM中运行

的,这里涉及到运行时域[l7] 和位置无关代码的概念,ldr r0,_TEXT_BASE的作用是将board/nextdvr2410/config.mk文件中定义的TEXT_BASE值(0x33f80000)装载到r0中,adr r1,_start该指令是条伪指令,在编译的时候会被转换成ADD或SUB指令根据当前pc值计算出_start标号的地址,这样的话就可以知道当前程序在什么地址运行(位置无关代码:做成程序的所有指令都是相对寻址的指令,包括跳转指令等,这样代码就可以不在链接所指定的地址上运行)。在上电之后,系统从nand启动,这里得到r0和r1值是不一样的,r0=0x33f80000,而r1=0x00000000。所以接下来会执行cpu_init_crit函数。

 

9. 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的访问时序。

 

10. Relocate函数,加载nand flash中的uboot到SDRAM中,代码会加载到0x33f80000开始的地址,空间大小是512。

1). ndf2ram函数

a.  设置NFCONF,使能2410的nand 控制器,初始化ECC,disable chip等

b.  enable chip,复位chip,读nand状态,判断是否busy,空闲的话再次disable chip;

c.  为调用c函数准备堆栈空间,这里的堆栈是放在uboot代码在SDRAM空间的最后位置armboot_end开始的128KB地址处(包含3 words for abort-stack,实际的SP位置是128*1024-12B处)。

d.  调用c函数copy_uboot_to_ram():nandll_reset() 设置NFCONF(新增设置了时间参数,其余设置和前面一样),复位nand flash;nandll_read_blocks(),传递了3个参数给它,0x33f80000,0x0, 9*NAND_BLOCK_SIZE.这里在读的过程中检查每个块的坏块标志,如果是坏块,则跳过不读。详情不叙,请看uboot的注释。该部分的c代码在cpu/arm920t/Nand_cp.c文件中

e.  ok_nand_read函数:读取SDRAM的前4k内容和SRAM的4K内容进行比较,只要出现不一样的地方就会进入死循环状态,目的就是为了确保转移代码的正确性。

f.  跳回到调用ndf2ram函数处继续执行

2). ldr pc, _start_armboot

    _start_armboot: .word start_armboot

    这里将会进入第二阶段的c代码部分:start_armboot()函数,/lib_arm/board.c。

 

3.2 第二阶段

    第二阶段从文件/lib_arm/board.c的start_armboot()函数开始。

   

1.     定义一个struct global_data结构体指针gd,struct global_data结构体对象

gd_data,定义一个struct bd_info结构体对象bd_data,定义一个指向函数的二级指针init_fnc_ptr,定义的全局结构体对象都是放在堆栈中的,gd是放在寄存器中的。

2.     gd=&gd_data,gd->bd = &bd_data,并且全部空间清0。

3.     init_fnc_ptr = init_sequence(一个初始化函数指针数组)。将会在接下来的for循环中提取出每一个函数来依次执行完。

init_fnc_t *init_sequence[] = {

    cpu_init,       /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */

    board_init,        

/* 基本的板级相关配置 -- board/nextdvr2410/nextdvr2410.c */

    interrupt_init,/* 初始化中断处理 -- cpu/arm920t/interrupt.c */

    env_init,       /* 初始化环境变量 -- common/env_flash.c */

    init_baudrate,  /* 初始化波特率设置 -- lib_arm/board.c */

    serial_init,    /* 串口通讯设置 -- cpu/arm920t/serial.c */

    console_init_f,/* 控制台初始化阶段1 -- common/console.c */

    display_banner,/* 打印u-boot信息 -- lib_arm/board.c */

    dram_init,  /* 配置可用的RAM -- board/nextdvr2410/nextdvr2410.c */

    display_dram_config,/* 显示RAM的配置大小 -- lib_arm/board.c */

#if defined(CONFIG_VCMA9)

         checkboard,     /* display board info */

#endif

    NULL,

};

cpu_init:根据需要设定IRQ,FIR堆栈。如果使用中断的话,中断堆栈就接在后面。

 

board_init:设置LOCKTIME,配置MPLL,UPLL,配置IO ports,设置gd->bd->bi_arch_number(553),gd->bd->bi_boot_params = 0x30000100设置boot参数地址,使能Icache和Dcache。

 

interrupt_init:使用timer 4来作为系统clock, 即时钟滴答, 10ms一次,到点就产生一个中断,但由于此时中断还没打开所以这个中断不会响应。

 

env_init:该函数主要做关于环境变量的工作,这个环境变量可以不用存放在nor或者nand flash上,直接在内存中生成(default_environment)。不过对于那些掉电需要保存的参数来说,保存在flash上无疑是最可靠的方式。有的uboot还支持冗余存储,也就是存两份做备份。

        在env初始化的时候,是通过env_init—>nandll_read_blocks将位于nand第9

块上的环境变量(16K)全部读入到0x33ef0000这个起始地址中来,在接下来将堆空间分配好之后,在函数env_relocate中,通过在堆中获得一块区域来存放环境变量,env_ptr指向这块区域,接下来所谓的重新获得环境变量无非就是将原来0x33ef0000开始的16K数据拷贝到env_ptr所指的区域中去。这里分第一次uboot启动(泛指只要在第一次运行saveenv指令之前所启动的uboot过程)和保存过环境变量的情况,但实质是一样的,所不同的是,第一次uboot启动,nand第9块区域中的数据肯定不是什么环境变量,所以这是的crc校验肯定出错,所以这时系统使用了默认的环境变量,但是只要这个默认的环境变量没有写到nand中(运行saveenv)的话,uboot的每次启动都被认为是第一次启动。而保存过环境变量之后的话,在执行env_init的时候,就是从nand中读出了实际存在的环境变量参数,至于修不修改环境变量,保不保存,都没有上面的那种情况出现了。

 

    init_baudrate:第一次启动uboot的时候,采用nextdvr2410nand.h中定义的115200默认波特率,后面的启动如果说在参数里设置了新的波特率的话就会用新的波特率来初始化。

   

    display_banner:打印uboot的一些信息,版本信息:NC-Boot 1.5 日期-时间 ,coed范围,bss开始地址,IRQ、FIR堆栈地址。

 

    dram_init: gd->bd->bi_dram[0].start = PHYS_SDRAM_1;

gd->bd->bi_dram[0].size  = PHYS_SDRAM_1_SIZE;设置板级数据中

的SDRAM开始地址和大小

    display_dram_config:打印SDRAM的配置信息,如下:

                    …

RAM Configuration:

Bank#0: 30000000 64 MB

                    Checkboard: NULL

 

4.     配置可用的flash空间,并且打印出相关信息,flash_init()和display_flash_config()。

 

5.     mem_malloc_init()函数,分配堆空间

    CFG_MALLOC_LEN = 16K(CFG_ENV_SIZE)+128K

    mem_malloc_start = _armboot_start(0x33f80000)- CFG_MALLOC_LEN

    mem_malloc_end = _armboot_start(0x33f80000)

 

6.     env_relocate该函数的作用是将0x33ef0000开始16K的环境参数拷贝到堆空间中去。

 

7.    gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr")通过这中方式获得环境变量列表中的ipaddr参数(开发板ip),获得环境变量中的MAC地址,设置到gd->bd->bi_enetaddr[reg]中。

8.     devices_init函数,创建了devlist,但是只有一个串口设备注册在内。

9.     console_init_r函数:控制台完全初始化,此后可以使用函数serial_getc和serial_putc或者putc和getc来输出log。

 

10.  使能中断,如果有网卡设备,设置网卡MAC和IP地址。

 

11.  main_loop ();定义于common/main.c。到此所有的初始化工作已经完成,main_loop在标准输入设备中接受命令,然后分析,查找和执行。

去掉所有无关紧要的宏和代码,main_loop()函数如下:

void main_loop()

{

    static char lastcommand[CFG_CBSIZE] = { 0, };

    int len;

    int rc = 1;

    int flag;

    char *s;

int bootdelay;

 

    s = getenv ("bootdelay");   //自动启动内核等待延时

bootdelay =

s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

    

    s = getenv ("bootcmd");  //取得环境中设置的启动命令行

   

    if (bootdelay >= 0 && s && !abortboot (bootdelay)){

     run_command (s, 0);

//执行启动命令行,smdk2410.h中没有定义CONFIG_BOOTCOMMAND,所以没有命令执行。

    }

   

    for (;;) {

    len = readline(CFG_PROMPT);

//读取键入的命令行到console_buffer

        

         flag = 0;       /* assume no special flags for now */

         if (len > 0)

             strcpy (lastcommand, console_buffer);

//拷贝命令行到lastcommand.

         else if (len == 0)

             flag |= CMD_FLAG_REPEAT;

             if (len == -1)

             puts ("\n");

         else

             rc = run_command (lastcommand, flag); //执行这个命令行。

  

     if (rc <= 0) {

      /* invalid command or not repeatable, forget it */

      lastcommand[0] = 0;

    }

}

12.  在上面的main_loop函数中,通常在开发完成的阶段都会设置一个bootcmd的环境

变量,然后将延时bootdelay设置成0,这样当u-boot跑到这里的时候就不会因为用户按下了任意键就进入了命令行模式,可以直接运行bootcmd的命令来直接加载kernel的Image然后移交控制权。如果进入了命令行模式,我们也可以手动输入命令来启动系统,输入的命令也是基本和bootcmd一样。

 

不过值得一提的是,从这里开始到引导内核的函数do_bootimg_linux()之前,不同

厂商之间做的都和原始的U-boot代码差别挺大,不过万变不离其宗,都是加载各种各样的Image到SDRAM中,不过关于CP部分的Image有的厂商是在这里加载,有的是kernel起来后来有kernel来加载,不过都需要加载的Image就是linux kernel的Image。为了方便,只讨论加载kernel Image的情况。

   

    在继续往下之前,有必要提一下几种不同格式linux kernel编译之后所产生的镜像文件,包括其各种头和ramdisk的混合,容易让人迷糊。

    ramdisk是linux内核启动过程中需要使用的一种临时文件系统,它要么单独编译成ramdisk.img(也有叫initrd或者initramfs),要么编译进内核。

    Linux编译之后最终会产生zImage文件,不过呢,为了迎合U-boot的要求,所以也有专门为U-boot的引导做一个uImage,这个只是加了一个U-boot中定义的一个head而已,用于U-boot中检查,当然前面的ramdisk.img也是需要加这个头的,头里面有这个Image的魔数,大小,类型等信息。现在的android中的u-boot也有要求加头的,他对U-boot进行了改进和优化,做成了自己的一套检查机制,所以现在android编译出来linux部分的Image的名字叫boot.img。

    这个boot.img是zImage和ramdisk.img合成之后的,而且还加了专门的头,这个head和U-boot原始的不一样,具体的源码路径可以参考:system/core/mkbootimg/。

/*

** +-----------------+

** | boot header     | 1 page

** +-----------------+

** | kernel          | n pages 

** +-----------------+

** | ramdisk         | m pages 

** +-----------------+

** | second stage    | o pages

** +-----------------+

**

** n = (kernel_size + page_size - 1) / page_size

** m = (ramdisk_size + page_size - 1) / page_size

** o = (second_size + page_size - 1) / page_size

*/

Android就没有在ramdisk和zImage上单独重复加头了,不过近期做的mtk的平台,他们有点怪,除了上面的额外信息之外,还在这二者上单独加了标志字符串,ROOTFS和KERNEL。

   

    了解了上面这些内容之后,对于从nand上加载uImage或者boot.img,都需要经过分离head进行检查,ok之后才会真正地将数据导入SDRAM。另外别忘了的是,如果ramdisk.img是单独的,那么在加载linux kernel的镜像的时候也需要将其加载进SDRAM,如果是编译到内核了,那就不用了。

   

    通常我们的uboot起来之后,我们会运行下面的命令之一来启动内核

tftp 0x30800000 uImage;bootm (地址可选)

或者

nand read 0x30800000 0x40000 0x200000 ; bootm

   

    例如informax的平台u-boot的bootcmd是:

    #define BOOTCMD

"mcu_clk 260;a7vector_SDRAM;dsp_clk 130;nand read 0x46000000 0x200000 0x400000;boot_from_flash boot"

很明显,原始U-boot中没有boot_from_flash命令,是经过他们改造过的。不过功能基本一样。所以还是以bootm来引导uImage为例来讨论。

 

        bootm命令位于cmd_bootm.c文件中:

        U_BOOT_CMD(

            bootm,  CFG_MAXARGS,    1,  do_bootm,

            "bootm   - boot application image from memory\n",

            "[addr [arg ...]]\n    - boot application image stored in memory\n"

            "        passing arguments 'arg ...'; when booting a Linux kernel,\n"

            "        'arg' can be the address of an initrd image\n"

);

在将nand上0x40000开始的2MB数据拷贝到SDRAM的0x30800000之后,就开始执行bootm命令,其所做的工作大致如下:

12.1如果bootm命令没有带地址参数,将会采用默认地址0x30800000,带地址则保存下这个参数地址。

12.2 从SDRAM的0x30800000开始拷贝64字节到一个dead结构体中进行crc32校验,校验ok之后将会调用调用函数print_image_hdr()打印出如下信息:

Image Name:   Linux-2.6.8-rc2-nc-v1

Created:      2010-05-04   4:14:19 UTC

Image Type:   ARM Linux Kernel Image (uncompressed)

Data Size:    1054712 Bytes =  1 MB

Load Address: 30008000

Entry Point:  30008000

 

12.3 跳过64字节的head,开始校验kernel的Image数据,校验码ok之后会打印:Verifying Checksum ... OK

12.4核对cpu类型

    

12.5 检查Image的类型

    

12.6 禁止中断,检查内核的压缩类型,这里不是指的image和zImage的区别,而是有没有在这基础上进行ZIP或ZIP2的压缩。通常这里是没有这样的压缩的。所以接下来将0x30800000+64B开始的zImage数据搬运到ih_load(0x30008000)处,这个数据就是kernel的Image数据。

 

12.7 根据head中OS的类型,如果是linux,head中类型值就是IH_OS_LINUX,所以接下来会执行u-boot到kernel的过渡程序。

do_bootm_linux (cmdtp, flag, argc, argv, addr, len_ptr, verify);

 

12.8定义thekernel函数指针,获取bootargs参数给commandline指针。

12.9 theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep),将内核的入口地址赋给thekernel函数指针。

12.10将传递给内核的参数放在0x30000100处,以tag的方式存放,主要放置了memery和cmdline的参数。

12.11关中断,关闭IDCache,同时使ID Cache数据失效。

12.12再次获取bi_arch_number参数为553。

12.13 theKernel (0, bd->bi_arch_number, bd->bi_boot_params)进入内核,第一个参数必须为0,第二个参数为机器类型553,第三个参数为传递给内核参数的其实地址0x30000100。

 

总结下,U-Boot调用内核之前,下面的条件必须满足:

a.  R0=0,R1为机器类型ID,参考linux/arch/arm/tools/mach-types,R2为启动参数tag列表在RAM中的基地址。

b.  CPU的工作模式必须为SVC模式,必须禁止中断(IRQS和FIRS)。

c.  数据cache和MMU必须关闭,指令cache可以打开也可以关闭。

 

这里移交控制权之后,u-boot的使命就算是完成了。说起来U-boot命运挺悲惨的,因为它重要而却最不受内核待见。接下来内核的启动更加复杂。

 

 


 [l1]这个词经常在老外的相关博客或者bootloader代码中出现。

 [l2]Redboot支持的处理器构架有ARM,MIPS,MN10300,PowerPC,v850,x86等。

 [l3]Blob提供两种工作模式:启动加载模式和下载模式,会在进入加载模式之前等待数秒,如果用户有任意键按下,那么blob将进入下载模式,否则继续启动引导linux。

 [l4]OEM Boot Module(OBM),bootROM执行完cpu特定初始化后,拷贝部分OBM到ISRAM内将控制权转给OBM,OBM的前面部分主要完成DDR和EMPI的初始化,之后将自己整个拷贝进DDR,然后转到DDR来执行,接着拷贝AP和CP的Image到DDR,此后OBM复位CP,CP和AP开始同时启动,AP侧OBM执行完后将控制权交给BLOB。

 [l5]在ISRAM内执行,barbox也是。

 [l6]注:下面的分析基于s3c2410,arm920t的arm core为例。

 [l7]Image的运行空间

 

参考网址:

1.        http://blog.csdn.net/cuijianzhongswust/article/details/6612624

2.        http://blog.csdn.net/sustzombie/article/details/6659622

3.        http://www.docin.com/p-191202348.html

4.        http://blog.csdn.net/maxleng/article/details/5508372

Android系统启动流程 -- linux kernel

一、zImage是怎样炼成的?

    zImage是linux内核编译之后产生的最终文件,它的生成过程比较复杂,这里不谈编译过程,只聊聊编译的最后阶段:

    1.  arm-linux-gnu-ld用arch/arm/kernel/vmlinux.lds、arch/arm/kernel/head.o、

arch/arm/kernel/init_task.o、各子目录下的built-in.o、lib/lib.a 、arch/arm/lib/lib.a生成顶层目录下的vmlinux (根据arch/arm/kernel/vmlinux.lds来链接 0xc0008000)

 

    2. 生成system.map, 置于顶层目录之下。

    3. arm-linux-gnu-objcopy,去掉顶层vmlinux两个段-R .note -R .comment

的调试信息,减小映像文件的大小,此时大概3M多,生成arch/arm/boot/Image。

 

4. gzip -f -9 < arch/arm/boot/compressed/../Image > arch/arm/boot/compressed/piggy.gz,读入arch/arm/boot/Image的内容,以最大压缩比进行压缩,生成arch/arm/boot/compressed/目录下的piggy.gz。

 

5. arm-linux-gnu-gcc,在arch/arm/boot/compressed/piggy.S文件中是直接引入piggy.gz的内容(piggy.gz其实已经是二进制数据了),然后生成arch/arm/boot/compressed/piggy.o文件。下面是piggy.S的内容

其中所选择的行就是加入了piggy.gz的内容,通过编译生成piggy.o文件,以备后面接下来的ld链接。

 

6. arm-linux-gnu-ld,在arch/arm/boot/compressed/piggy.o的基础上,加入重定位地址和参数地址的同时,加入解压缩的代码(arch/arm/boot/compressed/head.o、misc.o),最后生成arch/arm/boot/compressed目录的vmlinux,此时在解压缩代码中还含有调试信息(根据arch/arm/boot/compressed/vmlinux.lds来链接 0x0)vmlinux.lds开始处。

注意到了27行的吗?*(.piggydata)就表示需要将piggydata这个段放在这个位置,而piggydata这个段放的是什么呢?往后翻翻,看看第五步的图片,呵呵,其实就是将按最大压缩比压缩之后的Image,压缩之后叫piggy.gz中的二进制数据。

 

    7. arm-linux-gnu-objcopy,去掉解压缩代码中的调试信息段,最后生成arch/arm/boot/目录下的zImage。

   

8. /bin/sh

/home/farsight/Resources/kernel/linux-2.6.14/scripts/mkuboot.sh -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n 'Linux-2.6.14' -d arch/arm/boot/zImage arch/arm/boot/uImage

调用mkimage在arch/arm/boot/zImage的基础上加入64字节的uImage头,和入口地址,装载地址, 最终生成arch/arm/boot/目录下的uImage文件。

   

   

    实际上zImage是经过了高压缩之后在和解压缩程序合并在一起生成的。知道了这些之后,我们就可以给linux的启动大致分成3段:zImage解压缩、kernel的汇编启动阶段、kernel的c启动阶段。

    前两个阶段因为都是汇编写成的,代码读起来晦涩难懂,内存分布复杂,涉及MMU、解压缩等众多知识。如果有对这部分感兴趣的,可以自行分析,遇到问题可以上网查资料或者找我,这里就不详细分析了。下面是第二阶段汇编启动的主线,可以了解下:

1. 确定 processor type

    2. 确定 machine type

    3. 手动创建页表 

    4. 调用平台特定的cpu setup函数,设置中断地址,刷新Cache,开启Cache

                         (在struct proc_info_list中,in proc-arm920.S)

    5. 开启mmu I、D cache ,设置cp15的控制寄存器,设置TTB寄存器为0x30004000

    6. 切换数据(根据需要赋值数据段,清bss段,保存processor ID 和 machine type

        和 cp15的控制寄存器值)

7. 最终跳转到start_kernel   

(在__switch_data的结束的时候,调用了 b start_kernel)

 

二、linux的c启动阶段

    经过解压缩和汇编启动两个阶段,将会进入init/Main.c中的start_kernel()函数去继续执行。(2.6.1x、2.6.2x和2.6.3x之间的差异比较大,下面的分析基于2.6.14)

    1. printk(linux_banner)打印内核的一些信息,版本,作者,编译器版本,日期等信

息。

 

    2. 接下来执行是一个及其重要的函数setup_arch(),主要做一些板级初始化,cpu初始

化,tag参数解析,u-boot传递的cmdline解析,建立mmu工作页表(memtable_init),初始化内存布局,调用mmap_io建立GPIO,IRQ,MEMCTRL,UART,及其他外设的静态映射表,对时钟,定时器,uart进行初始化, cpu_init():{打印一些关于cpu的信息,比如cpu id,cache 大小等。另外重要的是设置了IRQ、ABT、UND三种模式的stack空间,分别都是12个字节。最后将系统切换到svc模式}。

 

3. sched_init():初始化每个处理器的可运行队列,设置系统初始化进程即0号进程。

 

4. 建立系统内存页区(zone)链表  build_all_zonelists()。

 

5.printk(KERN_NOTICE "Kernel command line: %s\n", saved_command_line);打印出从uboot传递过来的command_line字符串,在setup_arch函数中获得的。

 

6. parse_early_param(),这里分析的是系统能够辨别的一些早期参数(这个函数甚至可以去掉,__setup的形式的参数),而且在分析的时候并不是以setup_arch(&command_line)传出来的command_line为基础,而是以最原生态的saved_command_line为基础的。

 

7. parse_args("Booting kernel", command_line, __start___param,

                __stop___param - __start___param,

                &unknown_bootoption);

    对于比较新的版本真正起作用的函数,与parse_early_param()相比,此处对解析列表的处理范围加大了,解析列表中除了包括系统以setup定义的启动参数,还包括模块中定义的param参数以及系统不能辨别的参数。

    __start___param是param参数的起始地址,在System.map文件中能看到

    __stop___param - __start___param是参数个数

    unknown_bootoption是对应与启动参数不是param的相应处理函数(查看parse_one()就知道怎么回事)。

 

8. 在前面的setup_arch-àpaging_init-à memtable_init函数中为系统创建页表的时候,中断向量表的虚地址init_maps,是用alloc_bootmem_low_pages分配的,ARM规定中断向量表的地址只能是0或0xFFFF0000,所以该函数里有部分代码的作用就是映射一个物理页到0或0xFFFF0000。

trap_init函数做了以下的工作:把放在.Lcvectors处的系统8个意外入口跳转指令搬到高端中断向量0xffff0000处,再将__stubs_start到__stubs_end之间的各种意外初始化代码搬到0xffff0200处,等。

 

9. init_IRQ()

    初始化系统中所有的中断描述结构数组:irq_desc[NR_IRQS]。接着执行init_arch_irq函数,该函数是在setup_arch函数最后初始化的一个全局函数指针,指向了smdk2410_init_irq函数(in mach-smdk2410.c),实际上是调用了s3c24xx_init_irq函数。在该函数中,首先清除所有的中断未决标志,之后就初始化中断的触发方式和屏蔽位,还有中断句柄初始化,这里不是最终用户的中断函数,而是do_level_IRQ或者do_edge_IRQ函数,在这两个函数中都使用过__do_irq函数来找到真正最终驱动程序注册在系统中的中断处理函数。

 

10. softirq_init():内核的软中断机制初始化函数。

 

12.      console_init():

初始化系统的控制台结构,该函数执行后调用printk函数将log_buf中所有符合打印级别的系统信息打印到控制台上。

 

13. profile_init()函数

/* 对系统剖析做相关初始化, 系统剖析用于系统调用*/

//profile是用来对系统剖析的,在系统调试的时候有用

//需要打开内核选项,并且在bootargs中有profile这一项才能开启这个功能/*

    profile只是内核的一个调试性能的工具,这个可以通过menuconfig中profiling support打开。

   

14.      vfs_caches_init()

该函数主要完成的是文件系统相关的初始化,cache、inode等高速缓存的建

立,在mnt_init()函数中有注册并初始化sysfs、rootfs文件系统,这里只是在内存中建立他们的架构,创建了超级块,并没有真正挂载上去。关于这个rootfs需要说明的是,这个文件系统生命期更加短暂的,为什么?之前说的ramdisk大家是否还记得,ramdisk即将在后面释放到内存空间,来代替这里的rootfs出现在根目录之下,而这个rootfs则退居二线,隐藏在一个二级目录中。本来在非android的系统上,这个ramdisk也是一个暂时的文件系统,之后也会被真正的yaffs2之类的文件系统替换。不过呢,在android上,这个ramdisk还是挂载在根目录下的,只是将system、userdata等真实文件系统挂载了对应的二级目录下。

       

        关于这部分ramdisk内容,有兴趣的下来可以继续探讨。

       

15.      mem_init():

最后内存初始化,释放前边标志为保留的所有页面,这个函数结束之后就不能再使

用alloc_bootmem(),alloc_bootmem_low(),alloc_bootmem_pages()等申请低端内存的函数来申请内存,也就不能申请大块的连续物理内存了。

   

16.     中间还省略了很多内容,涉及到很多东西,这里也没有时间详细讨论,有兴趣的自

己研究代码吧!下面直接跳到start_kernel()函数的最后的一个重要函数:rest_init()。

   

17.     rest_init函数创建了两个线程之后,自己调用cpu_idle()函数隐退了。

创建的第一个线程,习惯上我们将其叫做1号内核线程,第二个线程叫2号内核线程,因为创建它们的父进程叫0号启动进程。

说明一下:2.6.14的内核这里只创建了一个内核线程叫init线程,而上面创建两

个线程的内核版本至少都是2.6.2x了,所以为了后面能和android的启动接上,所以这里开始linux转到2.2.29去。

 

static noinline void __init_refok rest_init(void) __releases(kernel_lock)

{

int pid;

 

kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

cpu_idle();

}

kthreadd这个线程之前的部门交流会上讨论过,新版本的linux将线程创建这个艰巨的工作专门交给了这个叫kthreadd的线程来完成。

接下来既然0号启动进程idle了,那么剩下的工作就都转移到线程kernel_init中去了。

 

18.     kernel_init()

这个线程的任务还是比较艰巨的,第一个重要任务就是调用函数

do_basic_setup(),先调用driver_init()来构建sysfs的目录架构,然后调用do_initcalls()函数来一次执行linux编译时设置的系统函数。

    这里主要工作就是注册系统设备的驱动程序,关于driver和device的注册顺序,是可以互相交换,例如通常的三星平台都有一个struct machine_desc结构体来描述平台相关的启动代码:

    MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch

                    * to SMDK2410 */

    /* Maintainer: Jonas Dietsche */

    .phys_io    = S3C2410_PA_UART,

    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

    .boot_params    = S3C2410_SDRAM_PA + 0x100,

    .map_io     = smdk2410_map_io,

    .init_irq   = s3c24xx_init_irq,

    .init_machine   = smdk2410_init,

    .timer      = &s3c24xx_timer,

MACHINE_END

    所有devices的注册都是在smdk2410_init()函数中调用函数:

    platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));

    来完成,所以drivers的注册就放在后面了。不过这样注册是有一个坏处的,就是不能准确地控制driver代码中probe的执行先后顺序。

    现在mtk平台上的devices和drivers注册顺序想法,也就是先注册上drivers,然后再注册devices,这样的话,就可以控制probe函数的执行先后。

   

include/linux/init.h文件中有这些优先级的定义:

#define pure_initcall(fn)        __define_initcall("0",fn,0)

 

#define core_initcall(fn)        __define_initcall("1",fn,1)

#define core_initcall_sync(fn)       __define_initcall("1s",fn,1s)

#define postcore_initcall(fn)        __define_initcall("2",fn,2)

#define postcore_initcall_sync(fn)   __define_initcall("2s",fn,2s)

#define arch_initcall(fn)        __define_initcall("3",fn,3)

#define arch_initcall_sync(fn)       __define_initcall("3s",fn,3s)

#define subsys_initcall(fn)      __define_initcall("4",fn,4)

#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)

#define fs_initcall(fn)          __define_initcall("5",fn,5)

#define fs_initcall_sync(fn)     __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn)      __define_initcall("rootfs",fn,rootfs)

#define device_initcall(fn)      __define_initcall("6",fn,6)

#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)

#define late_initcall(fn)        __define_initcall("7",fn,7)

#define late_initcall_sync(fn)       __define_initcall("7s",fn,7s)

当然函数的执行属性从1~7,通常我们见到的设备都是6、7级的。另外系统中所有的initcalll函数都是可以从linux根目录下的system.map中查看得到。

 

接下来的一段代码就是来释放前面提到的ramdisk.img的:

if (!ramdisk_execute_command)

     ramdisk_execute_command = "/init";

 

if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {

     ramdisk_execute_command = NULL;

     prepare_namespace();

}

释放出来的ramdisk呈现出来的目录就是android编译出来之后,在out/…/root的目录一样了,这个目录下有一个init可执行程序,下面就准备启动它。

 

接着调用init_post()函数,来打开console设备,这个时候我们的控制台就可以操作了,最后会执行以下代码来寻找和启动init程序:

if (execute_command) {

     run_init_process(execute_command);

     printk(KERN_WARNING "Failed to execute %s.  Attempting "

                 "defaults...\n", execute_command);

}

run_init_process("/sbin/init");

run_init_process("/etc/init");

run_init_process("/bin/init");

run_init_process("/bin/sh");

 

panic("No init found.  Try passing init= option to kernel.");

 

这里执行的init程序需要我们在u-boot传给kernel的cmdline中使用init=/init

来告知kernel,或者kernel启动代码中直接写死。否则在上面的那些目录中找不到init的话,系统就用panic机制将这个警告信息保存在nand的panic分区,在下次启动的时候,会自动将这个分区的信息输出。

 

init进程是linux起来之后启动的第一个用户进程,android系统也就是在这个进

程的基础上启动的。进程号是1。

Android系统启动流程 -- android

    Android的启动过程是从进程init开始的,所以它是后续所有进程的祖先进程。

一、init进程

源码位于system/core/init目录。主要做了以下事情:

1.     重新设置子进程终止时信号SIGCHLD的处理函数。

act.sa_handler = sigchld_handler;  //调用了wait函数等待子进程退出。

act.sa_flags = SA_NOCLDSTOP;

act.sa_mask = 0;

act.sa_restorer = NULL;

sigaction(SIGCHLD, &act, 0);

 

2. 将kernel启动过程中建立好的文件系统框架mount到相应目录。

    mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");

   …

    mount("devpts", "/dev/pts", "devpts", 0, NULL);

    mount("proc", "/proc", "proc", 0, NULL);

mount("sysfs", "/sys", "sysfs", 0, NULL);

   

3.     open_devnull_stdio(),将init进程的标准输入、输出、出错设备设置为新建的设备节点/dev/__null__。

4.     log_init(),创建并打开设备节点/dev/__kmsg__。

5.     读取并解析rc配置文件。

5.1 先从文件/sys/class/BOOT/BOOT/boot/boot_mode读出启动方式:Factory Mode, '4';ATE Factory Mode, '6'。看是否是facatory模式。

5.2 如果是的话,需要读取并解析两个文件:init.factory.rc和init.rc。

5.3 如果是正常启动,则暂时先读取init.rc。

这里在读取解析文件的时候,是以行为最小可执行单位在解析。关于书写init.rc文件的初始化脚本语言的规则,可以上网查找。解析之后并不会马上执行,而是在init进入服务循环之前统一根据其命令本身所带的条件来执行。

   

6.     导入kernel的cmdline,也就是u-boot传递给kernel的参数,查看其中是否具有androidboot.xxx(androidboot.mode、androidboot.hardware等)参数,如果有,将其保存在同名字的xxx(mode、hardware)全局变量中。这里特别说明的是hardware这个参数,从kernel中导出一部分之后,又要从/proc/cpuinfo中导出一部分来组合成完整的hardware参数,因为后面接下来会读取并解析以特定平台的rc文件。

7.     读取特定平台相关的initrc文件,如:init.mt6516.rc。

需要注意的是:对于service,这里会给每个服务建立一个struct service的结构体,全部挂入链表service_list之中,在init最后才启动。

   

8.     检查解析出来的所有命令行当中是否有属于early-init的,如果有,将其提出来加入到链表action_queue之中,马上将其执行掉。

 

9.     device_init()函数将会打开uevent的netlink socket,遍历/sys/class、/sys/block、/sys/devices目录,检查各级目录的uevent文件,处理在vold服务起来之前由kernel所发出来的device add, remove等事件。

 

10.  property_init(), 顾名思义,是属性初始化。首先创建一个名字为system_properties的匿名共享内存区域,对并本init进程做mmap读写映射,其余共享它的进程只有读的权限。然后将这个prop_area结构体通过全局变量__system_property_area__传递给property services。

接着调用函数load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT)从/default.prop文件中加载编译时生成的属性。

       

11.  如果在root目录下有initlogo.rle文件存在,这个是两张android字样的缕空图片,将其读入fb中显示到LCD上。同时也要往串口上输出"             A N D R O I D "。如果图片不存在,就没有这两项的输出。

 

12.  设置相应的属性:

property_set("ro.factorytest", "0")

property_set("ro.serialno", serialno[0] ?serialno : "");

property_set("ro.bootmode", bootmode[0] ?bootmode : "unknown");

property_set("ro.baseband", baseband[0] ?baseband : "unknown");

property_set("ro.carrier", carrier[0] ?carrier : "unknown");

property_set("ro.bootloader", bootloader[0] ?bootloader : "unknown");

 

property_set("ro.hardware", hardware);

snprintf(tmp, PROP_VALUE_MAX, "%d", revision);

property_set("ro.revision", tmp);

 

13.  开始执行以init为trigger的命令行:

action_for_each_trigger("init", action_add_queue_tail);

drain_action_queue();

        前面有执行过eraly-init的。

       

14.  启动属性服务:property_set_fd = start_property_service();

先读取剩余三个文件中的属性:/system/build.prop、/system/default.prop、/system/default.prop,然后用函数load_persistent_properties()加载persist.开始的属性,这种属性都是保存在目录/data/property下的以属性名为文件名的中。

接下来创建一个名为property_service的socket接口(SOCK_STREAM),然后进入监听状态,等待属性事件到来。

   

15.  创建一对socket,用来做信号方面的处理。

socketpair(AF_UNIX, SOCK_STREAM, 0, s),signal_fd = s[0],signal_recv_fd = s[1]。

 

    16.执行eraly-boot和boot为trigger的命令

        action_for_each_trigger("early-boot", action_add_queue_tail);

        action_for_each_trigger("boot", action_add_queue_tail);

        drain_action_queue();

       

17.执行init.rc中以property:开头的属性设置语句,同时使能属性触发方式。

queue_all_property_triggers();

drain_action_queue();

       

        property_triggers_enabled = 1;  //  可以执行那些以属性为条件的init语句。

       

1.     接下来就是利用poll机制监听前面创建的几个fd的动态。

struct pollfd ufds[4];

ufds[0].fd = device_fd;

ufds[0].events = POLLIN;

ufds[1].fd = property_set_fd;

ufds[1].events = POLLIN;

ufds[2].fd = signal_recv_fd;

ufds[2].events = POLLIN;

 

for(;;) {

     int nr, i, timeout = -1;

           

    for (i = 0; i < fd_count; i++)

        ufds[i].revents = 0;

    

     drain_action_queue(); //执行action_queue链表中后来新出现的command。

     restart_processes();  // 第一次启动所有服务,也包括后来restart这些

服务。restart_service_if_needed() à service_start(svc, NULL) à fork()

    

     …

     nr = poll(ufds, fd_count, timeout);

     if (nr <= 0)

            continue;

    

     if (ufds[2].revents == POLLIN) {

            /* we got a SIGCHLD - reap and restart as needed */

            read(signal_recv_fd, tmp, sizeof(tmp));

            while (!wait_for_one_process(0))

                ;

            continue;

     }

    

     if (ufds[0].revents == POLLIN)

          handle_device_fd(device_fd);  // Vold的netlink类型的socket

    

     if (ufds[1].revents == POLLIN)

          handle_property_set_fd(property_set_fd);//属性服务的socket

     if (ufds[3].revents == POLLIN)

            handle_keychord(keychord_fd);

}

到这里init就进入了死循环中一直在监听ufds中的4个文件描述符的动静,如果有POLLIN的事件,就做相应的处理,所以init并没有退出或者进入idle,而是被当做一个服务在运行。第4个文件描述符是keychord_fd,暂时不清楚这个怎么用,不过通过它也可以启动服务,可参考源码。

下面是init.rc的例子,见附件init.rc

       

二、init中启动的各种服务

在init中启动起来的服务按照init.rc中的先后顺序,大致有:

console: start a shell,code path: system/bin/sh,其源码中包含常用的shell命令,如ls,cd等。

adbd: start adb daemon,通常带有disabled的选项,表明需要按名字启动,code path:system/bin/adb。

servicemanager:这个服务管理着系统内所有binder services。code path: frameworks/base/cmds/servicemanager。

Vold: android 的udev,code path: system/vold。

Netd: start ntd daemon, code path: system/netd。

Debuggerd: start debug system, code path: system/core/debuggerd。

zygote: ['zaigəut]这是一个非常重要的服务,稍后详解。start Android  Java Runtime  and start systemserver。code path:frameworks/base/cmds/app_process。

media: add AudioFlinger,AudioPolicyService,MediaPlayerService and CameraService to servicemanager,同时启动管理binder通讯的机制,依靠这两个类来完成binder机制在android中间层所体现的功能:ProcessState 和IPCThreadState。Code path:frameworks/base/media/mediaserver。

bootanim: 开机动画和铃声,code path:frameworks/base/cmds/bootanimation。

 

接下来就是关于modem的服务,如:ccci_fsd、ccci_mdinit、pppd_gprs、pppd、gsm0710muxd、muxtestapp、sockcli、socksrv、muxreport、ril-daemon等,除了前面2个,后面的都带有disabled的参数,需要按名启动。

 

Installd: start install package daemon, code path:

frameworks/base/cmds/installd。

后面还有很多关于其他硬件的服务,比如BT、WIFI等。

 

2.1 servicemanager

    这个服务进程代码比较简单,功能也简单,c实现的,用来管理系统中所有的binder service,不管是本地的c++实现的还是java语言实现的都需要这个进程来统一管理,最主要的管理就是,注册添加服务,获取服务。

    这些binder服务在对外公开之前都必须将自身拥有的binder实体注册到SMgr中,而其他进程如果需要使用binder service的功能,也必须先经过SMgr取得 binder service中的binder实体在SMgr中对应的引用号(binder的引用号和进程中文件描述符概念类似,有效范围只限于本进程)。

 

#define BINDER_SERVICE_MANAGER ((void*) 0)  

int main(int argc, char **argv) 

    struct binder_state *bs; 

    void *svcmgr = BINDER_SERVICE_MANAGER;  

    bs = binder_open(128*1024);   

    // 打开binder设备,mmap映射当前进程的binder接收缓冲区,返回一个binder_state结构体  。

    if (binder_become_context_manager(bs)) {     

    // 通过这个函数将当前进程设置成服务管理进程MSgr,整个系统就这一个。 

       …

    } 

    svcmgr_handle = svcmgr;    

    binder_loop(bs, svcmgr_handler);  

 /*svcmgr_handler作为处理函数,所能完成的操作有:获取service,查看service是否存在,添加service ,列出service清单。其中用的最多的就是获取、添加。*/ 

    return 0; 

}

   

    2.2 zygote

        zygote服务进程也叫做孵化进程,在linux的用户空间,进程app_process会做

一些zygote进程启动的前期工作,如,启动runtime运行时环境(实例),参数分解,设置startSystemServer标志,接着用runtime.start()来执行zygote服务的代码,其实说简单点,就是zygote抢了app_process这个进程的躯壳,改了名字,将后面的代码换成zygote的main函数,这样顺利地过度到了zygote服务进程。这样我们在控制台用ps看系统所有进程,就不会看到app_process,取而代之的是zygote。

       

        而前面runtime.start()这个函数实际上是类函数AndroidRuntime::start(),在

这个函数中,会新建并启动一个虚拟机实例来执行com.android.internal.os.ZygoteInit这个包的main函数。这个main函数中会fork一个子进程来启动systemserver,父进程就作为真正的孵化进程存在了,每当系统要求执行一个 Android应用程序,Zygote就会收到socket消息FORK出一个子进程来执行该应用程序。因为Zygote进程是在系统启动时产生的,它会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等操作,而在系统需要一个新的虚拟机实例时可以快速地制造出一个虚拟机出来。

   

    每一个Android应用都运行在一个Dalvik虚拟机实例里,而每一个虚拟机实例都是一个独立的进程空间。虚拟机的线程机制,内存分配和管理,Mutex等等都是依赖底层linux实现的。所以android应用程序中创建了线程将会实际调用到linux的线程创建接口,和虚拟机所在进程共享一个虚拟机实例对java代码执行。

   

2.2.1 app_process

下面重点讨论zygote服务,源码位于frameworks/base/cmds/app_process。

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

参数:/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

 

int main(int argc, const char* const argv[])

{

    …

    AppRuntime runtime; // 这里启动runtime运行时环境(实例),AppRuntime是AndroidRuntime的子类,在创建这个对象的时候会依次调用基类和子类的构造函数。

    const char *arg;

    const char *argv0;

    …

    argv0 = argv[0];

    // ignore argv[0]

    argc--;

argv++;

/*

    argc = 4;

argv[0] = “-Xzygote”;

argv[1] = “/system/bin”;

argv[2] = “--zygote”;

argv[3] = “–start-system-server”;

*/

    int i = runtime.addVmArguments(argc, argv); // 找到参数中第一个不是以单个-开始的参数,这里很明显是第二个参数:/system/bin

    if (i < argc) {

        runtime.mParentDir = argv[i++];  // 将命令目录保存在mParentDir中,之后i = 2。

    }

    if (i < argc) {

        arg = argv[i++];

        if (0 == strcmp("--zygote", arg)) { // 通常这个分支是成立的

            bool startSystemServer = (i < argc) ?

                    strcmp(argv[i], "--start-system-server") == 0 : false;

// startSystemServer = true ,这个bool变量决定着后面执行runtime.start时是否启动systemserver。

            setArgv0(argv0, "zygote");

            set_process_name("zygote");

            runtime.start("com.android.internal.os.ZygoteInit",

                startSystemServer);

        } else {

            … // 只启动AndroidRuntime

        }

    }else{

        … // 错误处理

    }

} // main()

   

    2.2.2 AndroidRuntime

    下面进一步分析

    runtime.start("com.android.internal.os.ZygoteInit",startSystemServer);

    位于文件frameworks/base/core/jni/ AndroidRuntime.cpp,类定义于:

    frameworks/base/include/android_runtime/AndroidRuntime.h

    void AndroidRuntime::start(const char* className,

const bool startSystemServer){

LOGD("\n>>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<\n");

    JNIEnv* env;

    …

    /* start the virtual machine , mJavaVM是类AndroidRuntime的私有变量,env该函数中局部变量 */

    if (startVm(&mJavaVM, &env) != 0)

        goto bail;

 

    /* Register android functions.向刚刚新建的虚拟机注册JNI本地接口。frameworks/base/core/jni/这个目录下的所有jni接口。*/

    if (startReg(env) < 0) {

        …

goto bail;

}

 

//启动虚拟机之前需要构造java形式的参数数组,如下:

jclass stringClass;

jobjectArray strArray;

    jstring classNameStr;

    jstring startSystemServerStr;

 

stringClass = env->FindClass("java/lang/String");

strArray = env->NewObjectArray(2, stringClass, NULL);

classNameStr = env->NewStringUTF(className);

env->SetObjectArrayElement(strArray, 0, classNameStr);

startSystemServerStr = env->NewStringUTF(startSystemServer ?

                                                 "true" : "false");

env->SetObjectArrayElement(strArray, 1, startSystemServerStr);

/*  strArray[0] = “com.android.internal.os.ZygoteInit”

    strArry[1] = “true”*/

 

/*

 * Start VM.  This thread becomes the main thread of the VM, and will

 * not return until the VM exits.

 */

jclass startClass;

jmethodID startMeth;

 

slashClassName = strdup(className);

for (cp = slashClassName; *cp != '\0'; cp++)

    if (*cp == '.')

        *cp = '/';  // 将包名换成路径

 

startClass = env->FindClass(slashClassName); // 根据路径找到这个包

            // com.android.internal.os.ZygoteInit,这个类位于文件

            // com/ndroid/nternal/os/ZygoteInit.java

if (startClass == NULL) {

     LOGE("JavaVM unable to locate class '%s'\n", slashClassName);

     /* keep going */

 } else {

        /*这个参数决定了我们接下来执行的是zygoteInit.java的main函数。*/

        startMeth = env->GetStaticMethodID(startClass, "main",

            "([Ljava/lang/String;)V");

        if (startMeth == NULL) {

            LOGE("JavaVM unable to find main() in '%s'\n", className);

            /* keep going */

        } else {

            env->CallStaticVoidMethod(startClass, startMeth, strArray);

                // 虚拟机启动,将会调用到com.android.internal.os.ZygoteInit包的main函数。

}

}

 

LOGD("Shutting down VM\n");

    if (mJavaVM->DetachCurrentThread() != JNI_OK)

        LOGW("Warning: unable to detach main thread\n");

    if (mJavaVM->DestroyJavaVM() != 0)

        LOGW("Warning: VM did not shut down cleanly\n");

 

bail:

    free(slashClassName);

}

} // start()

   

    2.2.3 ZygoteInit

    下面进入com.android.internal.os.ZygoteInit 包的main函数:

    frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

    public static void main(String argv[]) {

        try {

            …

            registerZygoteSocket(); // Registers a server socket for zygote command

            // load classs and resources

    preloadClasses();

    preloadResources();

    …

    // Do an initial gc to clean up after startup

gc();/*初始化GC垃圾回收机制*/

 

/* 通过从前面传递过来的第二个参数startsystemserver=”true” 启动systemserver, 在startSystemServer()中会fork一个新的进程命名为system_server, 执行的是com.android.server包中的SystemServer.java文件中的main函数, 源码位置:frameworks/base/services/java/com/android/server/ SystemServer.java。*/

if (argv[1].equals("true")) {

                startSystemServer(); ///*************

} else if(…)

 

if (ZYGOTE_FORK_MODE) {

    runForkMode();      /* ZYGOTE_FORK_MODE 永远为flase */

} else {

    runSelectLoopMode();/* Zygote进程进入无限循环,不再返回。接下来的zygote将会作为一个孵化服务进程来运行。*/

}

 

closeServerSocket();

}

        …

    } // end main()

   

从这里开始android启动分为两条线走,分别是:

startSystemServer(); ---------- Zygote的子进程

runSelectLoopMode(); /* Zygote进程进入无限循环,不再返回,执行孵化工作。*/

 

2.2.4 systemserver

    上面的startSystemServer()函数中,做了如下的调用:

    startSystemServer()

    à Zygote.forkSystemServer()

    à 父进程直接返回true,子进程执行handleSystemServerProcess()

        à RuntimeInit.zygoteInit(parsedArgs.remainingArgs);

        /*

         * Pass the remaining arguments to SystemServer.

         * "--nice-name=system_server com.android.server.SystemServer"

         */

/*上面这个RuntimeInit包定于于文件frameworks\base\core\java\com\android\internal\os\RuntimeInit.java */

à invokeStaticMain(startClass, startArgs)

/* 通过该函数调用执行startClass类的main函数,带参数 system_server 。*/

à ZygoteInit.MethodAndArgsCaller(m, argv)

/* 得到包com.android.server.SystemServer的main()函数。然后执行。*/

   

    下面就开始调用到com.android.server.SystemServer类的main函数,源码位于:

    frameworks/base/services/java/com/android/server/SystemServer.java 

   

This method is called from Zygote to initialize the system. This will cause the nativeservices (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call backup into init2() to start the Android services.

    // main--->init1(system_init)--->init2(systemThread)

native public static void init1(String[] args);

       

public static void main(String[] args) {

        ...

System.loadLibrary("android_servers");// libandroid_servers.so是由目录frameworks/base/services/jni下的源码编译所得

init1(args); // init1实际上是一个jni本地接口,位于文件frameworks\base\services\jni\com_android_server_SystemServer.cpp文件中system_init()函数

}

   

init1接口位于com_android_server_SystemServer.cpp中:

extern "C" int system_init();

static void android_server_SystemServer_init1(JNIEnv* env, jobject clazz){

    system_init();

}

 

static JNINativeMethod gMethods[] = {

    /* name, signature, funcPtr */

{"init1","([Ljava/lang/String;)V",(void*)

android_server_SystemServer_init1 },

};

而函数system_init()位于文件

frameworks\base\cmds\system_server\library\System_init.cpp

extern "C" status_t system_init()

{

    …

    char propBuf[PROPERTY_VALUE_MAX];

    property_get("system_init.startsurfaceflinger", propBuf, "1");

    if (strcmp(propBuf, "1") == 0) {

        // Start the SurfaceFlinger

        SurfaceFlinger::instantiate();

    }

    if (!proc->supportsProcesses()) {

       

        // Start the AudioFlinger

        AudioFlinger::instantiate();

       

        // Start the media playback service

        MediaPlayerService::instantiate();

       

        // Start the camera service

        CameraService::instantiate();

       

        // Start the audio policy service

        AudioPolicyService::instantiate();

       

        //start appc service

        APPCService::instantiate();

    }

    …

    AndroidRuntime* runtime = AndroidRuntime::getRuntime();

    runtime->callStatic("com/android/server/SystemServer", "init2");

    // 执行com.android.server.SystemServer类的init2函数

}

com.android.server.SystemServer包的init2函数开启一个ServerThread线程:

public static final void init2() {

    Thread thr = new ServerThread();

    thr.setName("android.server.ServerThread");

    thr.start();

}

ServerThread线程的run函数会启动系统中绝大部分的android service,并最后进入Loop.loop(),,,,(SystemServer.java)

public void run() {

    …

    // Critical services...

    try {

        …

        Slog.i(TAG, "Power Manager");

        power = new PowerManagerService();

        ServiceManager.addService(Context.POWER_SERVICE, power);

       

        Slog.i(TAG, "Activity Manager");

        context = ActivityManagerService.main(factoryTest);

       

        …

        Slog.i(TAG, "Package Manager");

        pm = PackageManagerService.main(context,

                factoryTest != SystemServer.FACTORY_TEST_OFF);

       

        …

        Slog.i(TAG, "Content Manager");

        ContentService.main(context,

             factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL);

       

        …

        Slog.i(TAG, "Battery Service");

        battery = new BatteryService(context);

        ServiceManager.addService("battery", battery);

        …

        其余addservice的过程类似,只是启动的不同服务罢了,后面还启动了很多

服务,如:Lights、Vibrator、Alarm、Sensor、Bluetooth、Input Method、NetStat、NetworkManagement、Connectivity、Mount、Notification、Audio等。在这些服务都启动完了之后。

        …

        …

        … // run()函数的后半部分

        // It is now time to start up the app processes...

        使用xxx.systemReady()通知各个服务,系统已经就绪。

        …

        ((ActivityManagerService)ActivityManagerNative.getDefault())

                .systemReady(new Runnable() {

         public void run() {

                …

                if (batteryF != null) batteryF.systemReady();

                if (connectivityF != null) connectivityF.systemReady();

                if (dockF != null) dockF.systemReady();

                if (uiModeF != null) uiModeF.systemReady();

                if (recognitionF != null) recognitionF.systemReady();

                Watchdog.getInstance().start();

                …

            }

        });

        …

        Looper.loop(); // Run the message queue in this thread。

        …

}

 

2.2.5 home界面启动

    Home在

((ActivityManagerService)ActivityManagerNative.getDefault()).systemReady(.)

函数调用的过程中启动,其中systemReady()的参数是一段callback代码,如上面灰色显示的部分。

这个函数的实现部分在文件:ActivityManagerService.java中。

public void systemReady(final Runnable goingCallback) {

    …

   

    if (mSystemReady) {

        if (goingCallback != null) goingCallback.run();

        return; // 执行回调

    }

    …

    resumeTopActivityLocked(null);

}

private final boolean resumeTopActivityLocked(HistoryRecord prev) {

    …

    if (next == null) {

        // There are no more activities!  Let's just start up the

        // Launcher...

        return startHomeActivityLocked();

    }

    …

}

private boolean startHomeActivityLocked() {

    …

    if (aInfo != null) {

        …

        if (app == null || app.instrumentationClass == null) {

                intent.setFlags(intent.getFlags() |

                                Intent.FLAG_ACTIVITY_NEW_TASK);

                startActivityLocked(null, intent, null, null, 0, aInfo,

                        null, null, 0, 0, 0, false, false); // 这里启动home

         }

     }

    …

}

三、android启动图示

参考网址:

1.        http://blog.csdn.net/cuijianzhongswust/article/details/6612624

2.        http://blog.csdn.net/sustzombie/article/details/6659622

3.        http://www.docin.com/p-191202348.html

4.        http://blog.csdn.net/maxleng/article/details/5508372

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值