我的手机-我的嵌入式-3

二、安卓系统启动过程剖析

1.完整的安卓系统启动流程


从上图可以看出:安卓的启动流程是

开机加电--------执行BootLoader---------启动Linux内核--------启动安卓系统相关服务-----HomeLauncher启动,到这里,安卓的桌面就显示出来了,启动过程完成。

下面将安卓的启动分成三部分来介绍:

1.  BootLoader 的启动

主要的功能是将操作系统启动的代码拷贝到内存合适的位置,并跳转到特定的位置开始执行操作系统的启动代码。

2. Linux内核的启动

完成linux的启动,建立一个完整的Linux运行环境

3. 安卓init进行的启动

Linux内核提供的服务的基础上,初始化安卓的各种数据结构和服务,对上层的应用程序提供服务。

 

系统启动大致可分为一下几个阶段:

bootloader---初始化、从Flash读取Kernel镜像及一些必须的配置信息,引导kernel启动

linuxkernel启动linux内核

init进程启动

init进程读取init.rc启动必要的daemon程序,如:adbd、vold、netd、等

init进程启动servicemanager---随后详细分析其过程

init进程启动zygote ---随后详细分析其过程

· JAVA部分的Service启动

init进程启动mediaserver---多媒体本地服务启动

 

安卓架构图:



2.Bootloader启动


BootLoader就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。

安卓手机的OEM厂商很多都是没有开放Bootloader的,所以现在很多的刷机包都会列出试用的机型,因为只有允许解锁BootLoader的手机才能刷入第三方的Rom,否则只能刷入官方的Rom. 

BootLoader通常包含处理器厂商开发的一小段上电引导程序,BootLoader通常是先执行一段汇编代码,然后是C代码。汇编代码一般用来直接操作处理器,内存,初始化硬件环境,C代码用来初始化各种需要的数据结构,获得系统的各种信息保存起来等。

 

3.Linux内核的启动

3.1 linux的汇编启动阶段

1. 确定 processor type

    2. 确定 machine type

    3. 手动创建页表 

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

                         (struct proc_info_list,in proc-arm920.S)

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

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

        和 cp15的控制寄存器值)

7. 最终跳转到start_kernel   

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

 

3.2 LinuxC启动阶段

经过前面的过程,将会进入init/Main.c中的start_kernel()函数去继续执行

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 idcache 大小等。另外重要的是设置了IRQABTUND三种模式的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___paramparam参数的起始地址,在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规定中断向量表的地址只能是00xFFFF0000,所以该函数里有部分代码的作用就是映射一个物理页到00xFFFF0000

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只是内核的一个调试性能的工具,这个可以通过menuconfigprofiling support打开。

   

14.      vfs_caches_init()

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

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

       

        关于这部分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);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值