Android系统启动流程分析
2013-10-30 20:24
761 查看
本文讲解Android系统在启动过程中的关键动作,摈弃特定平台之间的差异,讨论共性的部分,至于启动更加详细的过程,需要结合代码分析,这里给出流程框架,旨在让大家对开机过程更明了。各个平台启动流程基本类似,但代码追踪却有较大区别。高通,MTK,Sprd各有不同处理,均有各自的一套源码,本文代码以展讯平台SC7710系列Android4.1源码进行追踪。
源码:bootable/bootloader/* , 说明:加电后,CPU将先执行bootloader程序,此处有三种选择:
a: 开机按Camera+Power启动到fastboot,即命令或SD卡烧写模式,不加载内核及文件系统,此处可以进行工厂模式的烧写
b: 开机按Home+Power启动到recovery模式,加载recovery.img,recovery.img包含内核,基本的文件系统,用于工程模式的烧写
c:开机按Power,正常启动系统,加载boot.img,boot.img包含内核,基本文件系统,用于正常启动手机(以下只分析正常启动的情况)
源码: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以告知已注册的第三方程序在开机时自动启动。
先区分一下Bootloader和Monitor[l1] : 严格来说,Bootloader只是引导OS运行起来的代码;而Monitor另外还提供了很多的命令行接口,可以进行调试、读写内存、烧写Flash、配置环境变量等。在开发过程中Monitor提供了很好地调试功能,不过在开发结束之后,可以完全将其设置成一个Bootloader。所以习惯上将其叫做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方案:
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平台上电流程
从上面第二小节中可以看出,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:
对应的Makefile文件如下:
所以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). 检查系统的复位状态,以确定是不是从睡眠唤醒。
根据这几条语句来判断系统是从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()函数:
该段代码完成了一些设备的初始化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。
接下来分析正常开机的流程也就是normal_mode(),其他几种开机流程与normal_mode()类似,不再一一分析。android共提供了多种mode:
normal_mode()的实现在 u-boot/property/normal_mode.c:
最终将操作交给vlx_nand_boot(),其实现在u-boot/property/normal_nand_mode.c
该函数的重点在开头和结尾的相关操作,开头部分见注释,重点分析vlx_entry()函数,其实现在normal_mode.c:
这里的entry()跳转到VM虚拟机的首地址,start_linux()则是进入kernel的方法,仍在normal_mode.c中实现:
至此,已经到了Kernel\init\main.c的start_kernel(),即来到了linux的世界。
该函数所调用的大部分都是相关的初始化操作,而跟启动关联的是结尾的rest_init() ,该函数是第一个跟init进程相关的函数,看其实现:
该函数启动了kernel_init来进行后续的初始化,进而看kernel_init(),这些函数任然在main.c中实现。
init进程由init_post()创建 即main.c 的init_post():
从以上代码可知,init的工作任务还是很重的,上面的代码已经省略的不少,但任然很多,不过分析两个知识点来看,可将init的工作流程精简为四点:1,解析配置文件重点是init.rc。2,执行各个阶段的动作,创建zygote的工作就在其中的某一个阶段完成。3,调用property_init()初始化属性相关的资源,并且通过property_load_boot_defaults()启动属性服务。4,init进入一个无限循环,并且等待一些事情的发生。接下来重点看下解析配置文件的init.rc。解析函数:
再看init.rc文件:
在init.rc中完成了一系列的重要操作:文件系统权限及挂载,启动zygote,启动系统服务,播放开机动画。当然如何解析对应的代码,并完成对应的操作,如启动zygote、播放开机动画,可以参考相关资料或查看源码,此处不再详述。至此init已经将部分操作交给了zygote。
Zygote进程中完成了java虚拟机的创建及初始化,以及准备了java运行时环境,还有jni的准备工作,所以zygote占据了整个android世界的半壁江山,另半壁江山则是system_server,后续会详细介绍。
Zygote---- >入口文件App_main.cpp ---- >main()
Zygote原意是受精卵的意思。
在linux中指app_process即:frameworks/base/cmds/app_process目录下的App_main.cpp
此处可发现main()
该代码主要完成工作如下:
1,niceName = "zygote";---- >重命名,原进程名称为app_process
,2,setArgv0(argv0, niceName);
,3,set_process_name(niceName); ---- >完成重命名操作
,4,AppRuntime runtime;----- >App_main.cpp的一个内部类,其继承AndroidRuntime.cpp
,5,runtime.start("com.android.internal.os.ZygoteInit",startSystemServer("startsystemserver"));
备注:AppRuntime 作为一个内部类,在main()里调用。其完成:
1, getClassName() ---- >运行时文件类名
2, onVmCreated()---- >java虚拟机创建
3, onStarted()---- >调用时加载
4, onZygoteInit()---- >初始化虚拟机
5, onExit()---- >退出时的操作 --------- > 上述函数基本自动调用
接着,走进runtime.start(com.android.internal.os.ZygoteInit)。runtime来自AndroidRuntime.cpp。AndroidRuntime.cpp------ >AndroidRuntime::start(const char* className, const char* options)。frameworks\base\core\jni\AndroidRuntime.cpp。分析其start()函数:
该函数完成操作:
1, onVmCreated(env);----- >创建虚拟机
2, JNIEnv* env; ---- > JNI环境的初始化
3, env->CallStaticVoidMethod(startClass, startMeth, strArray); ----- >最终函数与上述步骤中的runtime.start(com.android.internal.os.ZygoteInit)对应。
4, 至此走到---- ZygoteInit.java----main()
ZygoteInit.java ---main()----- >java世界准备已经完成,欢迎来到java世界。
该函数重点完成如下3项工作:
1, registerZygoteSocket();
2, startSystemServer();----- > 核心方法,Zygote进程一分为二,此处分裂出一个system_server进程。
3, 至此system_server进程进入SystemServer.java---- >main()
先看下startSystemServer()方法:
com.android.server.SystemServer的创建,预示着SystemServer的的正式启动,自此Zygote一分为二。Zygote将系统服务交给SystemServer统一管理。而zygote则负责java运行时环境和Dalvik虚拟机的管理工作。
Init1(),为system_server的第一阶段SystemServer.java--- >Init1()的本地实现在com_android_server_SystemServer.cpp中。
先看frameworks\base\services\java\com\android\server\SystemServer.java的main()函数。
其中init1()的本地实现在com_android_server_SystemServer.cpp中:
system_init()在frameworks\base\cmds\system_server\library\system_init.cpp中:
再回到SystemServer.java的main()中的init2():init2()将操作交给了内部类ServerThread处理,看起run()函数:
该函数有3个重要功能:
1,ServiceManager.addService("xxx",XXX),将系统服务注册进去。
2,systemReady(),告诉已经实现该接口servers,系统已经启动OK。
3,WakelockMonitor的启动。
至此,systemserver的启动工作已经完成。
1)源码:ActivityManagerService.java为入口,packages/apps/launcher*实现
2)说明:系统启动成功后SystemServer使用xxx.systemReady()通知各个服务,系统已经就绪,桌面程序Home就是在ActivityManagerService.systemReady()通知的过程中建立的,最终调用startHomeActivityLocked()启动launcher。Home在((ActivityManagerService)ActivityManagerNative.getDefault()).systemReady(.)。函数调用的过程中启动,其中systemReady()的参数是一段callback代码,如上面灰色显示的部分。这个函数的实现部分在文件:ActivityManagerService.java中。
先看ActivityManagerService.java的systemReady():
跳转至launcher的操作由resumeTopActivityLocked()完成,其实现在ActivityStack.java里的resumeTopActivityLocked()。
从上述代码可以看出其实是走到了mService.startHomeActivityLocked(0),而这里的mService也就是ActivityManagerService.java,再次回到ActivityManagerService.java的startHomeActivityLocked(0),至此launcher启动完成。
说明:系统启动成功后SystemServer调用wm.systemReady()通知WindowManagerService,进而调用PhoneWindowManager,最终通过LockPatternKeyguardView显示解锁界面,跟踪代码可以看到解锁界面并不是一个Activity,这是只是向特定层上绘图,其代码了存放在特殊的位置。此处不再详细分析。
frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java的systemReady()方法:
第一步,告诉锁屏控制器,系统已经启动完成,接下来有锁屏处理。 frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardViewMediator.java:
再看其doKeyguardLocked()方法:
至此,锁屏启动完成。
至此,android启动完成。
uboot启动:会有一帧 uboot logo。
kernel启动:会有一帧kernel logo。(默认不显示,其控制宏是默认关闭的)
android启动:会有一帧静态图片+一个闪动的图片序列(即开机动画)。
通常情况下,我们在分析android的开机动画效果时,很少去分析uboot logo和kernel logo,因为ubootlogo 属于uboot阶段,kernel logo 属于linux范围。正常情况下,我们在down版本,烧到手机里去时,会吧logo.bmp加进去,这是系统的处理是:uboot logo,kernel logo,android static logo是同一张图片,即我们加的logo.bmp。
双framebuffer显示logo机制分析:本来一直走的是一级logo显示,从uboot logo一直持续到系统动画,但考虑期间时间偏长,欲采用标准三级logo。1、uboot logo 2、kernle logo 3 initlogo.rle 最后动画bootanimation.zip。但是kernel 对framebuffer修改较大,故考虑在uboot开始和结束显示两张logo(第二幅logo显示调用在theKernel()跳入内核函数之前),kernel跳过。uboot 直接刷屏显示第二幅logo
动作过慢,效果不佳,经考虑采用双buffer策略。思路:
1.原来只要显示一张uboot logo :把nand 中boot.logo 拷贝至lcd_base+fbsize处,然后搬至lcd_base显示;
2.现在创建第二个framebuffer于lcd_base+2*fbsize处,在显示第二幅logo前把nand 中第二幅logo 仍然拷贝至lcd_base+fbsize处,然后搬至lcd_base+2*fbsize第二个framebuffer基地址;
3.把第二个framebuffer基地址告诉lcd 控制寄存器,更新framebuffer基地址;
4.但在kernel中,寄存器仍然会指向第一个framebuffer基地址,那么第二幅logo显示犹如昙花一现啊,不过这个问题好解决,既然第二幅logo已经搬进了第二个framebuffer那,那么只要在进入内核前做一个memcpy就好了。
注:logo是bmp格式,在拷贝前需要进行相应的解析,参考uboot给的解析代码,自定义函数。
即由lcd_display_logo()完成相关操作。该函数在normal_mode.c中定义。
相关代码:
/kernel/drivers/video/fbmem.c
/kernel/drivers/video/logo/logo.c
/kernel/drivers/video/logo/Kconfig
/kernel/include/linux/linux_logo.h
N D R I O D”字样。
由此调用logo.c 的load_565rle_image()函数。
该图片格式是565RLE image format格式的,可用工具将bmp格式转化为rle格式。之后会有init.rc 并发开机动画。
相关文件:
/frameworks/base/cmds/bootanimation/BootAnimation.h
/frameworks/base/cmds/bootanimation/BootAnimation.cpp
/frameworks/base/cmds/bootanimation/bootanimation_main.cpp
/system/core/init/init.c
/system/core/rootdir/init.rc
init.c解析init.rc(其中定义服务:“service bootanim /system/bin/bootanimation”),bootanim 服务由SurfaceFlinger.readyToRun()(property_set("ctl.start", "bootanim");)执行开机动画、bootFinished()(property_set("ctl.stop", "bootanim");)执行停止开机动画。 BootAnimation.h和BootAnimation.cpp文件放到了/frameworks/base/cmds
/bootanimation目录下了,增加了一个入口文件bootanimation_main.cpp。Android.mk文件中可以看到,将开机 动画从原来的SurfaceFlinger里提取出来了,生成可执行文件:bootanimation。Android.mk代码如下:
备注:
1,adb shell后,可以直接运行“bootanimation”来重新看开机动画,它会一直处于动画状态,而不会停止。
2,adb shell后,命令“setprop ctl.start bootanim”执行开机动画;命令“getprop ctl.start bootanim”停止开机动画。这两句命令分别对应SurfaceFlinger.cpp的两句语 句:property_set("ctl.start", "bootanim");和property_set("ctl.stop", "bootanim")。
至此android启动动画分析结束。
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可以选用:
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 = getenv ("bootfile")) != NULL) { copy_filename (BootFile, s, sizeof (BootFile)); } #endif boot_pwr_check(); //usb_eth_initialize(NULL); #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);//重点操作函数,此处走至u-boot\property\cmd_cboot.c /* main_loop() can return to retry autoboot, if so just run it again. */ for (;;) { main_loop (); } /* NOTREACHED - no way out of command loop except booting */ }
该段代码完成了一些设备的初始化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(); //DCDC_Cal_All(0); #endif #ifdef CONFIG_AUTOBOOT normal_mode();//如果down的是autopoweron的uboot,这里会直接去正常开机 #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{ //根据sp8810.h里的LOW_BAT_VOL,如果电压低于3.5V,则直接power down 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 int recovery_init(void); int ret =0; ret = recovery_init(); if(ret == 1){ DBG("func: %s line: %d\n", __func__, __LINE__); recovery_mode_without_update(); }else if(ret == 2){ #ifndef CONFIG_SC8830 try_update_modem(); //update img from mmc #endif normal_mode(); } unsigned check_reboot_mode(void); //获取寄存器里HW的rest标志位,得到当前的开机模式 //此处主要是异常重启,恢复出厂设置,关机闹钟等(没有按power键导致的开机) unsigned rst_mode= check_reboot_mode(); //检查是否是recovery模式 if(rst_mode == RECOVERY_MODE){ DBG("func: %s line: %d\n", __func__, __LINE__); recovery_mode(); } else if(rst_mode == FASTBOOT_MODE){ DBG("func: %s line: %d\n", __func__, __LINE__); fastboot_mode(); }else if(rst_mode == NORMAL_MODE){ normal_mode(); }else if(rst_mode == WATCHDOG_REBOOT){ watchdog_mode(); }else if(rst_mode == UNKNOW_REBOOT_MODE){ unknow_reboot_mode(); }else if(rst_mode == PANIC_REBOOT){ panic_reboot_mode(); }else if(rst_mode == ALARM_MODE){ int flag =alarm_flag_check(); if(flag == 1) alarm_mode(); else if(flag == 2) normal_mode(); }else if(rst_mode == SLEEP_MODE){ sleep_mode(); }else if(rst_mode == SPECIAL_MODE){ special_mode(); }else if(rst_mode == CALIBRATION_MODE){ calibration_detect(0); } #ifdef CONFIG_SC8810 // normal_mode(); #endif DBG("func: %s line: %d\n", __func__, __LINE__); if(charger_connected()){ DBG("%s: charger connected\n", __FUNCTION__); #if defined (CONFIG_SP8810W) || defined(CONFIG_SC7710G2) calibration_detect(1); #endif charge_mode(); } //find the power up trigger //如果按power键的“次数”达标了,认为这个是一次长按事件 else if(boot_pwr_check() >= get_pwr_key_cnt()){ DBG("%s: power button press\n", __FUNCTION__); DBG("boot_pwr_check=%d,get_pwr_key_cnt=%d\n",boot_pwr_check(),get_pwr_key_cnt()); //go on to check other keys mdelay(50); for(i=0; i<10;i++){ key_code = board_key_scan();//获取另外一个按键 if(key_code != KEY_RESERVED) break; } DBG("key_code %d\n", key_code); //查找对应的按键码对应的开机模式 key_mode = check_key_boot(key_code); switch(key_mode){ case BOOT_FASTBOOT: fastboot_mode(); break; case BOOT_RECOVERY: recovery_mode(); break; case BOOT_CALIBRATE: engtest_mode(); return 0; //back to normal boot break; case BOOT_DLOADER: dloader_mode(); break; default: break;//如果是正常开机模式,因为没有 } } else if(alarm_triggered() && alarm_flag_check()){ DBG("%s: alarm triggered\n", __FUNCTION__); int flag =alarm_flag_check(); if(flag == 1){ //如果是闹钟触发导致的开机,则进入关机闹钟模式 alarm_mode(); } else if(flag == 2){ normal_mode();//如果只按了power键。 } }else{ #if BOOT_NATIVE_LINUX_MODEM *(volatile u32*)CALIBRATION_FLAG = 0xca; #endif #if !defined (CONFIG_SC8830) && !defined(CONFIG_SC7710G2) calibration_detect(0); #endif //if calibrate success, it will here DBG("%s: power done again\n", __FUNCTION__); power_down_devices(); while(1) ; } if(argc == 1){ DBG("func: %s line: %d\n", __func__, __LINE__); normal_mode(); return 1; } if(argc == 2){ DBG("func: %s line: %d\n", __func__, __LINE__); if(strcmp(argv[1],"normal") == 0){ normal_mode(); return 1; } DBG("func: %s line: %d\n", __func__, __LINE__); if(strcmp(argv[1],"recovery") == 0){ recovery_mode(); return 1; } DBG("func: %s line: %d\n", __func__, __LINE__); if(strcmp(argv[1],"fastboot") == 0){ fastboot_mode(); return 1; } DBG("func: %s line: %d\n", __func__, __LINE__); if(strcmp(argv[1],"dloader") == 0){ dloader_mode(); return 1; } DBG("func: %s line: %d\n", __func__, __LINE__); if(strcmp(argv[1],"charge") == 0){ //如果没有按power键,且插入了充电器,则进入充电模式 charge_mode(); return 1; } DBG("func: %s line: %d\n", __func__, __LINE__); if(strcmp(argv[1],"caliberation") == 0){ calibration_detect(1); return 1; } DBG("func: %s line: %d\n", __func__, __LINE__); } DBG("func: %s line: %d\n", __func__, __LINE__); usage: cmd_usage(cmdtp); return 1; }
接下来分析正常开机的流程也就是normal_mode(),其他几种开机流程与normal_mode()类似,不再一一分析。android共提供了多种mode:
normal_mode()的实现在 u-boot/property/normal_mode.c:
void normal_mode(void) { #if defined (CONFIG_SC8810) || defined (CONFIG_SC8825) || defined (CONFIG_SC8830) //MMU_Init(CONFIG_MMU_TABLE_ADDR); vibrator_hw_init();//初始化马达 #endif set_vibrator(1);//起震,这个就是开机震的那一下 #ifndef UART_CONSOLE_SUPPORT #ifdef CONFIG_SC7710G2 extern int serial1_SwitchToModem(void); serial1_SwitchToModem(); #endif #endif #if BOOT_NATIVE_LINUX vlx_nand_boot(BOOT_PART, CONFIG_BOOTARGS, BACKLIGHT_ON); #else vlx_nand_boot(BOOT_PART, NULL, BACKLIGHT_ON); #endif }
最终将操作交给vlx_nand_boot(),其实现在u-boot/property/normal_nand_mode.c
void vlx_nand_boot(char * kernel_pname, char * cmdline, int backlight_set) { ...... char *fixnvpoint = "/fixnv"; char *fixnvfilename = "/fixnv/fixnv.bin"; char *fixnvfilename2 = "/fixnv/fixnvchange.bin"; char *backupfixnvpoint = "/backupfixnv"; char *backupfixnvfilename = "/backupfixnv/fixnv.bin"; char *runtimenvpoint = "/runtimenv"; char *runtimenvpoint2 = "/runtimenv"; char *runtimenvfilename = "/runtimenv/runtimenv.bin"; char *runtimenvfilename2 = "/runtimenv/runtimenvbkup.bin"; char *productinfopoint = "/productinfo"; char *productinfofilename = "/productinfo/productinfo.bin"; char *productinfofilename2 = "/productinfo/productinfobkup.bin"; int orginal_right, backupfile_right; unsigned long orginal_index, backupfile_index; nand_erase_options_t opts; char * mtdpart_def = NULL; #if (defined CONFIG_SC8810) || (defined CONFIG_SC8825) MMU_Init(CONFIG_MMU_TABLE_ADDR); #endif ret = mtdparts_init(); if (ret != 0){ printf("mtdparts init error %d\n", ret); return; } #ifdef CONFIG_SPLASH_SCREEN #define SPLASH_PART "boot_logo" ret = find_dev_and_part(SPLASH_PART, &dev, &pnum, &part); if(ret){ printf("No partition named %s\n", SPLASH_PART); return; }else if(dev->id->type != MTD_DEV_TYPE_NAND){ printf("Partition %s not a NAND device\n", SPLASH_PART); return; } //读取下载到nand中的boot_logo,就是开机亮的那一屏 off=part->offset; nand = &nand_info[dev->id->num]; //read boot image header size = 1<<19;//where the size come from????//和dowload工具中的地址一致 char * bmp_img = malloc(size); if(!bmp_img){ printf("not enough memory for splash image\n"); return; } ret = nand_read_offset_ret(nand, off, &size, (void *)bmp_img, &off); if(ret != 0){ printf("function: %s nand read error %d\n", __FUNCTION__, ret); return; } //第一次LCD logo lcd_display_logo(backlight_set,(ulong)bmp_img,size); #endif set_vibrator(0);//停止震动,如果发现开机狂震不止,那就是没走到这里。 { nand_block_info(nand, &good_blknum, &bad_blknum); printf("good is %d bad is %d\n", good_blknum, bad_blknum); } ret = load_sector_to_memory(fixnvpoint, fixnvfilename2, fixnvfilename, (unsigned char *)FIXNV_ADR, (unsigned char *)MODEM_ADR, FIXNV_SIZE + 4); ...... #elif defined(CONFIG_CALIBRATION_MODE_NEW) #if defined(CONFIG_SP7702) || defined(CONFIG_SP8810W) /* force dsp sleep in native 8810 verson to reduce power consumption */ extern void DSP_ForceSleep(void); DSP_ForceSleep(); printf("dsp nand read ok1 %d\n", ret); #endif #ifdef CONFIG_SC7710G2 ret = try_update_spl(); if(ret == -1){ printf("try update spl faild!\n"); return -1; } ret = try_load_fixnv(); if(ret == -1){ printf("try load fixnv faild!\n"); return -1; } ret = try_load_runtimenv(); if(ret == -1){ printf("try load runtimenv faild!\n"); } ret = try_load_productinfo(); if(ret == -1){ printf("try load productinfo faild!\n"); } #endif if(poweron_by_calibration()) { #ifndef CONFIG_SC7710G2 // ---------------------fix nv-------------------------------- ...... // ---------------------runtime nv---------------------------- ...... // ---------------------DSP ---------------------------- ...... #endif //////////////////////////////////////////////////////////////// /* KERNEL_PART */ printf("Reading kernel to 0x%08x\n", KERNEL_ADR); ret = find_dev_and_part(kernel_pname, &dev, &pnum, &part); if(ret){ printf("No partition named %s\n", kernel_pname); return; }else if(dev->id->type != MTD_DEV_TYPE_NAND){ printf("Partition %s not a NAND device\n", kernel_pname); return; } off=part->offset; nand = &nand_info[dev->id->num]; //read boot image header #if 0 size = nand->writesize; flash_page_size = nand->writesize; ret = nand_read_offset_ret(nand, off, &size, (void *)hdr, &off); if(ret != 0){ printf("function: %s nand read error %d\n", __FUNCTION__, ret); return; } if(memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)){ printf("bad boot image header, give up read!!!!\n"); return; } else { //read kernel image size = (hdr->kernel_size+(flash_page_size - 1)) & (~(flash_page_size - 1)); if(size <=0){ printf("kernel image should not be zero\n"); return; } ret = nand_read_offset_ret(nand, off, &size, (void *)KERNEL_ADR, &off); if(ret != 0){ printf("kernel nand read error %d\n", ret); return; } //read ramdisk image size = (hdr->ramdisk_size+(flash_page_size - 1)) & (~(flash_page_size - 1)); if(size<0){ printf("ramdisk size error\n"); return; } ret = nand_read_offset_ret(nand, off, &size, (void *)RAMDISK_ADR, &off); if(ret != 0){ printf("ramdisk nand read error %d\n", ret); return; } } #else ret = load_kernel_and_layout(nand, (unsigned int)off, (char *)raw_header, (char *) KERNEL_ADR, (char *) RAMDISK_ADR, 2048, nand->writesize); if (ret != 0) { printf("ramdisk nand read error %d\n", ret); return; } #endif ...... { good_blknum = 0; bad_blknum = 0; nand_block_info(nand, &good_blknum, &bad_blknum); printf("good is %d bad is %d\n", good_blknum, bad_blknum); } creat_cmdline(cmdline,hdr); vlx_entry();//末尾进入entry(),其实现在normal_mode.c }
该函数的重点在开头和结尾的相关操作,开头部分见注释,重点分析vlx_entry()函数,其实现在normal_mode.c:
void vlx_entry() { #if !(defined CONFIG_SC8810 || defined CONFIG_TIGER || defined CONFIG_SC8830) MMU_InvalideICACHEALL(); #endif #if (defined CONFIG_SC8810) || (defined CONFIG_SC8825) || (defined CONFIG_SC8830) MMU_DisableIDCM(); #endif #ifdef REBOOT_FUNCTION_INUBOOT reboot_func(); #endif #if BOOT_NATIVE_LINUX start_linux(); #else void (*entry)(void) = (void*) VMJALUNA_ADR; entry(); #endif }
这里的entry()跳转到VM虚拟机的首地址,start_linux()则是进入kernel的方法,仍在normal_mode.c中实现:
static int start_linux() { void (*theKernel)(int zero, int arch, u32 params); u32 exec_at = (u32)-1; u32 parm_at = (u32)-1; u32 machine_type; machine_type = machine_arch_type; /* get machine type */ //重点根据KERNEL的地址 theKernel = (void (*)(int, int, u32))KERNEL_ADR; /* set the kernel address */ #ifndef CONFIG_SC8830 *(volatile u32*)0x84001000 = 'j'; *(volatile u32*)0x84001000 = 'm'; *(volatile u32*)0x84001000 = 'p'; #endif //根据Kernel的地址走至Kernel\init\main.c的start_kernel() theKernel(0, machine_type, VLX_TAG_ADDR); /* jump to kernel with register set */ while(1); return 0; }
至此,已经到了Kernel\init\main.c的start_kernel(),即来到了linux的世界。
3,linux启动详细分析
Kernel\init\main.c的start_kernel()的kernel的起点,先看这个函数:asmlinkage void __init start_kernel(void) { char * command_line; extern const struct kernel_param __start___param[], __stop___param[]; #ifdef CONFIG_NKERNEL jiffies_64 = INITIAL_JIFFIES; #endif smp_setup_processor_id(); /* * Need to run as early as possible, to initialize the * lockdep hash: */ lockdep_init(); debug_objects_early_init(); /* * Set up the the initial canary ASAP: */ boot_init_stack_canary(); cgroup_init_early(); local_irq_disable(); early_boot_irqs_disabled = true; /* * Interrupts are still disabled. Do necessary setups, then * enable them */ tick_init(); boot_cpu_init(); page_address_init(); printk(KERN_NOTICE "%s", linux_banner); setup_arch(&command_line); mm_init_owner(&init_mm, &init_task); mm_init_cpumask(&init_mm); setup_command_line(command_line); setup_nr_cpu_ids(); setup_per_cpu_areas(); smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */ build_all_zonelists(NULL); page_alloc_init(); printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line); parse_early_param(); parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); /* * These use large bootmem allocations and must precede * kmem_cache_init() */ setup_log_buf(0); pidhash_init(); vfs_caches_init_early(); sort_main_extable(); trap_init(); mm_init(); /* * Set up the scheduler prior starting any interrupts (such as the * timer interrupt). Full topology setup happens at smp_init() * time - but meanwhile we still have a functioning scheduler. */ sched_init(); /* * Disable preemption - early bootup scheduling is extremely * fragile until we cpu_idle() for the first time. */ preempt_disable(); if (!irqs_disabled()) { printk(KERN_WARNING "start_kernel(): bug: interrupts were " "enabled *very* early, fixing it\n"); local_irq_disable(); } idr_init_cache(); perf_event_init(); rcu_init(); radix_tree_init(); /* init some links before init_ISA_irqs() */ early_irq_init(); init_IRQ(); prio_tree_init(); init_timers(); hrtimers_init(); softirq_init(); timekeeping_init(); time_init(); profile_init(); call_function_init(); if (!irqs_disabled()) printk(KERN_CRIT "start_kernel(): bug: interrupts were " "enabled early\n"); early_boot_irqs_disabled = false; local_irq_enable(); /* Interrupts are enabled now so all GFP allocations are safe. */ gfp_allowed_mask = __GFP_BITS_MASK; kmem_cache_init_late(); /* * HACK ALERT! This is early. We're enabling the console before * we've done PCI setups etc, and console_init() must be aware of * this. But we do want output early, in case something goes wrong. */ console_init(); if (panic_later) panic(panic_later, panic_param); lockdep_info(); /* * Need to run this when irqs are enabled, because it wants * to self-test [hard/soft]-irqs on/off lock inversion bugs * too: */ locking_selftest(); #ifdef CONFIG_BLK_DEV_INITRD if (initrd_start && !initrd_below_start_ok && page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) { printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - " "disabling it.\n", page_to_pfn(virt_to_page((void *)initrd_start)), min_low_pfn); initrd_start = 0; } #endif page_cgroup_init(); enable_debug_pagealloc(); debug_objects_mem_init(); kmemleak_init(); setup_per_cpu_pageset(); numa_policy_init(); if (late_time_init) late_time_init(); sched_clock_init(); calibrate_delay(); pidmap_init(); anon_vma_init(); #ifdef CONFIG_X86 if (efi_enabled) efi_enter_virtual_mode(); #endif thread_info_cache_init(); cred_init(); fork_init(totalram_pages); proc_caches_init(); buffer_init(); key_init(); security_init(); dbg_late_init(); vfs_caches_init(totalram_pages); signals_init(); /* rootfs populating might need page-writeback */ page_writeback_init(); #ifdef CONFIG_PROC_FS proc_root_init(); #endif cgroup_init(); cpuset_init(); taskstats_init_early(); delayacct_init(); check_bugs(); acpi_early_init(); /* before LAPIC and SMP init */ sfi_init_late(); ftrace_init(); /* Do the rest non-__init'ed, we're now alive */ //第一个跟init 进程相关的函数 rest_init(); }
该函数所调用的大部分都是相关的初始化操作,而跟启动关联的是结尾的rest_init() ,该函数是第一个跟init进程相关的函数,看其实现:
static noinline void __init_refok rest_init(void) { int pid; rcu_scheduler_starting(); /* * We need to spawn init first so that it obtains pid 1, however * the init task will end up wanting to create kthreads, which, if * we schedule it before we create kthreadd, will OOPS. */ //启动kernel_init来进行接下来的初始化 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); numa_default_policy(); pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); rcu_read_lock(); kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); rcu_read_unlock(); complete(&kthreadd_done); /* * The boot idle thread must execute schedule() * at least once to get things moving: */ init_idle_bootup_task(current); preempt_enable_no_resched(); schedule(); preempt_disable(); /* Call into cpu_idle with preempt disabled */ cpu_idle();//将系统交给调度器处理。 }
该函数启动了kernel_init来进行后续的初始化,进而看kernel_init(),这些函数任然在main.c中实现。
static int __init kernel_init(void * unused) { /* * Wait until kthreadd is all set-up. */ wait_for_completion(&kthreadd_done); /* * init can allocate pages on any node */ set_mems_allowed(node_states[N_HIGH_MEMORY]); /* * init can run on any cpu. */ set_cpus_allowed_ptr(current, cpu_all_mask); cad_pid = task_pid(current); smp_prepare_cpus(setup_max_cpus); do_pre_smp_initcalls(); lockup_detector_init(); smp_init(); sched_init_smp(); //此函数中会调用各个驱动模块的加载函数(静态编译的,非ko)来初始化设备 do_basic_setup(); /* Open the /dev/console on the rootfs, this should never fail */ if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n"); (void) sys_dup(0); (void) sys_dup(0); /* * check if there is an early userspace init. If yes, let it do all * the work */ 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(); } /* * Ok, we have completed the initial bootup, and * we're essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. */ //走至init 进程的相关操作 init_post(); return 0; }
init进程由init_post()创建 即main.c 的init_post():
static noinline int init_post(void) { /* need to finish all async __init code before freeing the memory */ async_synchronize_full(); free_initmem(); mark_rodata_ro(); system_state = SYSTEM_RUNNING; numa_default_policy(); current->signal->flags |= SIGNAL_UNKILLABLE; if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); } /* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ if (execute_command) { //至此init启动完成,接下来的启动就是System\core\init\init.c的main() 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. " "See Linux Documentation/init.txt for guidance."); }
4,android启动详细分析
android部分的启动包括几个部分:init,zygote,systemserver,launcher,lockscreen,othersapps。4.1,init启动
init是一个进程,确切的说,是linux系统用户空间的第一个进程,android是基于linux 的,所以init也是android用户空间的第一个进程,他的进程号是1,作为天字第一号进程,其有很多重要的职责。其最重要的职责是创建了Zygote以及提供了systemserver。system\core\init\init.c的入口函数是main()。int main(int argc, char **argv) { int fd_count = 0; struct pollfd ufds[4]; char *tmpdev; char* debuggable; char tmp[32]; int property_set_fd_init = 0; int signal_fd_init = 0; int keychord_fd_init = 0; bool is_charger = false; if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv); /* clear the umask */ umask(0); /* Get the basic filesystem setup we need put * together in the initramdisk on / and then we'll * let the rc file figure out the rest. */ //创建一些文件夹,并挂载设备,这些是与linux相关的 mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); /* indicate that booting is in progress to background fw loaders, etc */ close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000)); /* We must have some place other than / to create the * device nodes for kmsg and null, otherwise we won't * be able to remount / read-only later on. * Now that tmpfs is mounted on /dev, we can actually * talk to the outside world. */ //重定向标准输入输出 错误输出到/dev/_null_ open_devnull_stdio(); //设置init的日志输出设备为/dev/_kmsg_,不过该文件打开后 //会立刻被unlink,这样其他进程就无法打开这个文件读取日志信息 klog_init(); //prop配置文件的解析与初始化操作,如//设置"/default.prop"属性文件 property_init(); //通过读取proc/cpuinfo得到机器的hardware名 get_hardware_name(hardware, &revision); process_kernel_cmdline(); #ifdef HAVE_SELINUX INFO("loading selinux policy\n"); selinux_load_policy(); #endif is_charger = !strcmp(bootmode, "charger"); INFO("property init\n"); if (!is_charger) property_load_boot_defaults(); INFO("reading config file\n"); //解析init.rc配置文件 非常重要,文件系统的挂载,权限设置 //以及系统server的启动,包括Zygote的创建 init_parse_config_file("/init.rc"); /** **解析完init.rc 会得到一系列的action动作 **keychord_init_action和console_init_action **/ action_for_each_trigger("early-init", action_add_queue_tail); queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); queue_builtin_action(keychord_init_action, "keychord_init"); //console_init_action为控制台初始化,此处会加载一帧boot logo文件为initlogo.rle queue_builtin_action(console_init_action, "console_init"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail); /* skip mounting filesystems in charger mode */ action_for_each_trigger("early-fs", action_add_queue_tail); //action_for_each_trigger("fs", action_add_queue_tail); { bool has_3partions = false; has_3partions = (!access("/sys/block/mmcblk0/mmcblk0p3",R_OK)) && (!access("/sys/block/mmcblk0/mmcblk0p2",R_OK)) && (!access("/sys/block/mmcblk0/mmcblk0p1",R_OK)); if (has_3partions) { action_for_each_trigger("fs-two", action_add_queue_tail); } else { action_for_each_trigger("fs", action_add_queue_tail); } } action_for_each_trigger("post-fs", action_add_queue_tail); if (!is_charger) { //action_for_each_trigger("post-fs", action_add_queue_tail); action_for_each_trigger("post-fs-data", action_add_queue_tail); } queue_builtin_action(property_service_init_action, "property_service_init"); queue_builtin_action(signal_init_action, "signal_init"); queue_builtin_action(check_startup_action, "check_startup"); if (!strcmp(bootmode, "alarm")) { action_for_each_trigger("alarm", action_add_queue_tail); } if (is_charger) { action_for_each_trigger("charger", action_add_queue_tail); } else { action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", action_add_queue_tail); } /* run all property triggers based on current state of the properties */ queue_builtin_action(queue_property_triggers_action, "queue_property_triggers"); #if BOOTCHART queue_builtin_action(bootchart_init_action, "bootchart_init"); #endif for(;;) {//无限循环启动进程 int nr, i, timeout = -1; execute_one_command();//再循环中执行动作 restart_processes();//重启已经死去的进程 if (!property_set_fd_init && get_property_set_fd() > 0) { ufds[fd_count].fd = get_property_set_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; property_set_fd_init = 1; } if (!signal_fd_init && get_signal_fd() > 0) { ufds[fd_count].fd = get_signal_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; signal_fd_init = 1; } if (!keychord_fd_init && get_keychord_fd() > 0) { ufds[fd_count].fd = get_keychord_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; keychord_fd_init = 1; } if (process_needs_restart) { timeout = (process_needs_restart - gettime()) * 1000; if (timeout < 0) timeout = 0; } if (!action_queue_empty() || cur_action) timeout = 0; #if BOOTCHART if (bootchart_count > 0) { if (timeout < 0 || timeout > BOOTCHART_POLLING_MS) timeout = BOOTCHART_POLLING_MS; if (bootchart_step() < 0 || --bootchart_count == 0) { bootchart_finish(); bootchart_count = 0; } } #endif nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i++) { if (ufds[i].revents == POLLIN) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd()) handle_keychord(); else if (ufds[i].fd == get_signal_fd()) handle_signal(); } } } return 0; }
从以上代码可知,init的工作任务还是很重的,上面的代码已经省略的不少,但任然很多,不过分析两个知识点来看,可将init的工作流程精简为四点:1,解析配置文件重点是init.rc。2,执行各个阶段的动作,创建zygote的工作就在其中的某一个阶段完成。3,调用property_init()初始化属性相关的资源,并且通过property_load_boot_defaults()启动属性服务。4,init进入一个无限循环,并且等待一些事情的发生。接下来重点看下解析配置文件的init.rc。解析函数:
int init_parse_config_file(const char *fn) { char *data; data = read_file(fn, 0); if (!data) return -1; parse_config(fn, data); DUMP(); return 0; }
再看init.rc文件:
...... // service管理器 ---- > servicemanager.cpp/ servicemanager.java service servicemanager /system/bin/servicemanager class core user system group system critical onrestart restart zygote ------ > 启动zygote进程 onrestart restart media ------ > 启动media onrestart restart surfaceflinger------ > 启动surfaceflinger onrestart restart drm------ > 启动drm service vold /system/bin/vold class core socket vold stream 0660 root mount ioprio be 2 service netd /system/bin/netd class main socket netd stream 0660 root system socket dnsproxyd stream 0660 root inet service debuggerd /system/bin/debuggerd class main service ril-daemon /system/bin/rild class main socket rild stream 660 root radio socket rild-debug stream 660 radio system user root group radio cache inet misc audio sdcard_rw log // surfaceflinger服务 //对应surfaceflinger.cpp----- >在system_server中具体实现 service surfaceflinger /system/bin/surfaceflinger class main user system group graphics onrestart restart zygote // zygote进程 //后面重点分析 service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main socket zygote stream 666 onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart media onrestart restart netd // drm服务 service drm /system/bin/drmserver class main user drm group system inet drmrpc // mediaserver服务 //在Main_mediaserver.cpp中实现,启动audio camera 等服务 service media /system/bin/mediaserver class main user media group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc ioprio rt 4 //此处引导播放开机动画,并在surfaceflinger中具体实现 service bootanim /system/bin/bootanimation class main user graphics group graphics disabled oneshot ......
在init.rc中完成了一系列的重要操作:文件系统权限及挂载,启动zygote,启动系统服务,播放开机动画。当然如何解析对应的代码,并完成对应的操作,如启动zygote、播放开机动画,可以参考相关资料或查看源码,此处不再详述。至此init已经将部分操作交给了zygote。
4.2,zygote启动
zygote的启动预示着真正的来到了java的世界。zygote这个词的中文意思的受精卵,他和android系统中的java世界有着重要关系。zygote本身是一个native的应用程序,与驱动,内核均无关系。根据对init的了解我们知道,zygote是有init进程根据init.rc文件中的配置项创建的。先分析其来历,zygote最初的名字叫app_process,这个名字是在android.mk文件中指定的。但在运行过程中,app_process通过linux下的pctrl系统调用将自己的名字换成了zygote,所以通过进程看到的名称是zygote。Zygote进程中完成了java虚拟机的创建及初始化,以及准备了java运行时环境,还有jni的准备工作,所以zygote占据了整个android世界的半壁江山,另半壁江山则是system_server,后续会详细介绍。
Zygote---- >入口文件App_main.cpp ---- >main()
Zygote原意是受精卵的意思。
在linux中指app_process即:frameworks/base/cmds/app_process目录下的App_main.cpp
此处可发现main()
int main(int argc, const char* const argv[]) { // These are global variables in ProcessState.cpp mArgC = argc; mArgV = argv; mArgLen = 0; for (int i=0; i<argc; i++) { mArgLen += strlen(argv[i]) + 1; } mArgLen--; AppRuntime runtime; const char* argv0 = argv[0]; // Process command line arguments // ignore argv[0] argc--; argv++; // Everything up to '--' or first non '-' arg goes to the vm int i = runtime.addVmArguments(argc, argv); // Parse runtime arguments. Stop at first unrecognized option. bool zygote = false; bool startSystemServer = false; bool application = false; const char* parentDir = NULL; const char* niceName = NULL; const char* className = NULL; while (i < argc) { const char* arg = argv[i++]; if (!parentDir) { parentDir = arg; } else if (strcmp(arg, "--zygote") == 0) { zygote = true; niceName = "zygote"; } else if (strcmp(arg, "--start-system-server") == 0) { startSystemServer = true; } else if (strcmp(arg, "--application") == 0) { application = true; } else if (strncmp(arg, "--nice-name=", 12) == 0) { niceName = arg + 12; } else { className = arg; break; } } if (niceName && *niceName) { setArgv0(argv0, niceName); set_process_name(niceName); } runtime.mParentDir = parentDir; if (zygote) { // do last shutdown check ALOGV("doLastShutDownCheck"); doLastShutDownCheck(); runtime.start("com.android.internal.os.ZygoteInit", startSystemServer ? "start-system-server" : ""); } else if (className) { // Remainder of args get passed to startup class main() runtime.mClassName = className; runtime.mArgC = argc - i; runtime.mArgV = argv + i; runtime.start("com.android.internal.os.RuntimeInit", application ? "application" : "tool"); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); return 10; } }
该代码主要完成工作如下:
1,niceName = "zygote";---- >重命名,原进程名称为app_process
,2,setArgv0(argv0, niceName);
,3,set_process_name(niceName); ---- >完成重命名操作
,4,AppRuntime runtime;----- >App_main.cpp的一个内部类,其继承AndroidRuntime.cpp
,5,runtime.start("com.android.internal.os.ZygoteInit",startSystemServer("startsystemserver"));
备注:AppRuntime 作为一个内部类,在main()里调用。其完成:
1, getClassName() ---- >运行时文件类名
2, onVmCreated()---- >java虚拟机创建
3, onStarted()---- >调用时加载
4, onZygoteInit()---- >初始化虚拟机
5, onExit()---- >退出时的操作 --------- > 上述函数基本自动调用
接着,走进runtime.start(com.android.internal.os.ZygoteInit)。runtime来自AndroidRuntime.cpp。AndroidRuntime.cpp------ >AndroidRuntime::start(const char* className, const char* options)。frameworks\base\core\jni\AndroidRuntime.cpp。分析其start()函数:
void AndroidRuntime::start(const char* className, const char* options) { ALOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n", className != NULL ? className : "(unknown)"); blockSigpipe(); /* * 'startSystemServer == true' means runtime is obsolete and not run from * init.rc anymore, so we print out the boot start event here. */ if (strcmp(options, "start-system-server") == 0) { /* track our progress through the boot sequence */ const int LOG_BOOT_PROGRESS_START = 3000; LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC))); } const char* rootDir = getenv("ANDROID_ROOT"); if (rootDir == NULL) { rootDir = "/system"; if (!hasDir("/system")) { LOG_FATAL("No root directory specified, and /android does not exist."); return; } setenv("ANDROID_ROOT", rootDir, 1); } //const char* kernelHack = getenv("LD_ASSUME_KERNEL"); //ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack); /* start the virtual machine */ JNIEnv* env; if (startVm(&mJavaVM, &env) != 0) { return; } onVmCreated(env); /* * Register android functions. */ if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); return; } /* * We want to call main() with a String array with arguments in it. * At present we have two arguments, the class name and an option string. * Create an array to hold them. */ jclass stringClass; jobjectArray strArray; jstring classNameStr; jstring optionsStr; stringClass = env->FindClass("java/lang/String"); assert(stringClass != NULL); strArray = env->NewObjectArray(2, stringClass, NULL); assert(strArray != NULL); classNameStr = env->NewStringUTF(className); assert(classNameStr != NULL); env->SetObjectArrayElement(strArray, 0, classNameStr); optionsStr = env->NewStringUTF(options); env->SetObjectArrayElement(strArray, 1, optionsStr); /* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */ char* slashClassName = toSlashClassName(className); jclass startClass = env->FindClass(slashClassName); if (startClass == NULL) { ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in '%s'\n", className); /* keep going */ } else { env->CallStaticVoidMethod(startClass, startMeth, strArray); #if 0 if (env->ExceptionCheck()) threadExitUncaughtException(env); #endif } } free(slashClassName); ALOGD("Shutting down VM\n"); if (mJavaVM->DetachCurrentThread() != JNI_OK) ALOGW("Warning: unable to detach main thread\n"); if (mJavaVM->DestroyJavaVM() != 0) ALOGW("Warning: VM did not shut down cleanly\n"); }
该函数完成操作:
1, onVmCreated(env);----- >创建虚拟机
2, JNIEnv* env; ---- > JNI环境的初始化
3, env->CallStaticVoidMethod(startClass, startMeth, strArray); ----- >最终函数与上述步骤中的runtime.start(com.android.internal.os.ZygoteInit)对应。
4, 至此走到---- ZygoteInit.java----main()
ZygoteInit.java ---main()----- >java世界准备已经完成,欢迎来到java世界。
public static void main(String argv[]) { try { // Start profiling the zygote initialization. SamplingProfilerIntegration.start(); registerZygoteSocket(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis()); preload(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis()); // Finish profiling the zygote initialization. SamplingProfilerIntegration.writeZygoteSnapshot(); // Do an initial gc to clean up after startup gc(); // If requested, start system server directly from Zygote if (argv.length != 2) { throw new RuntimeException(argv[0] + USAGE_STRING); } if (argv[1].equals("start-system-server")) { startSystemServer(); } else if (!argv[1].equals("")) { throw new RuntimeException(argv[0] + USAGE_STRING); } Log.i(TAG, "Accepting command socket connections"); if (ZYGOTE_FORK_MODE) { runForkMode(); } else { runSelectLoopMode(); } closeServerSocket(); } catch (MethodAndArgsCaller caller) { caller.run(); } catch (RuntimeException ex) { Log.e(TAG, "Zygote died with exception", ex); closeServerSocket(); throw ex; } }
该函数重点完成如下3项工作:
1, registerZygoteSocket();
2, startSystemServer();----- > 核心方法,Zygote进程一分为二,此处分裂出一个system_server进程。
3, 至此system_server进程进入SystemServer.java---- >main()
先看下startSystemServer()方法:
private static boolean startSystemServer() throws MethodAndArgsCaller, RuntimeException { /* Hardcoded command line to start the system server */ String args[] = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007", "--capabilities=130104352,130104352", "--runtime-init", "--nice-name=system_server", "com.android.server.SystemServer", }; ZygoteConnection.Arguments parsedArgs = null; int pid; try { parsedArgs = new ZygoteConnection.Arguments(args); ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); /* Request to fork the system server process */ pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, null, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* For child process */ if (pid == 0) { handleSystemServerProcess(parsedArgs); } return true; }
com.android.server.SystemServer的创建,预示着SystemServer的的正式启动,自此Zygote一分为二。Zygote将系统服务交给SystemServer统一管理。而zygote则负责java运行时环境和Dalvik虚拟机的管理工作。
4.3,systemserver启动
system_server进程是android的第二大进程,其余zygote紧密联系,若其中任何一个进程死掉,就会导致系统死掉,其启动过程包含两个阶段Main()----- >init1()和init2()。Init1(),为system_server的第一阶段SystemServer.java--- >Init1()的本地实现在com_android_server_SystemServer.cpp中。
先看frameworks\base\services\java\com\android\server\SystemServer.java的main()函数。
native public static void init1(String[] args); public static void main(String[] args) { if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) { // If a device's clock is before 1970 (before 0), a lot of // APIs crash dealing with negative numbers, notably // java.io.File#setLastModified, so instead we fake it and // hope that time from cell towers or NTP fixes it // shortly. Slog.w(TAG, "System clock is before 1970; setting to 1970."); SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME); } if (SamplingProfilerIntegration.isEnabled()) { SamplingProfilerIntegration.start(); timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { SamplingProfilerIntegration.writeSnapshot("system_server", null); } }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL); } // Mmmmmm... more memory! dalvik.system.VMRuntime.getRuntime().clearGrowthLimit(); // The system server has to run all of the time, so it needs to be // as efficient as possible with its memory usage. VMRuntime.getRuntime().setTargetHeapUtilization(0.8f); System.loadLibrary("android_servers"); init1(args); } public static final void init2() { Slog.i(TAG, "Entered the Android system server!"); Thread thr = new ServerThread(); thr.setName("android.server.ServerThread"); thr.start(); }
其中init1()的本地实现在com_android_server_SystemServer.cpp中:
static void android_server_SystemServer_init1(JNIEnv* env, jobject clazz) { system_init(); }
system_init()在frameworks\base\cmds\system_server\library\system_init.cpp中:
extern "C" status_t system_init() { ALOGI("Entered system_init()"); sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm = defaultServiceManager(); ALOGI("ServiceManager: %p\n", sm.get()); sp<GrimReaper> grim = new GrimReaper(); sm->asBinder()->linkToDeath(grim, grim.get(), 0); char propBuf[PROPERTY_VALUE_MAX]; property_get("system_init.startsurfaceflinger", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { // Start the SurfaceFlinger SurfaceFlinger::instantiate(); } property_get("system_init.startsensorservice", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { // Start the sensor service SensorService::instantiate(); } // And now start the Android runtime. We have to do this bit // of nastiness because the Android runtime initialization requires // some of the core system services to already be started. // All other servers should just start the Android runtime at // the beginning of their processes's main(), before calling // the init function. ALOGI("System server: starting Android runtime.\n"); AndroidRuntime* runtime = AndroidRuntime::getRuntime(); ALOGI("System server: starting Android services.\n"); JNIEnv* env = runtime->getJNIEnv(); if (env == NULL) { return UNKNOWN_ERROR; } jclass clazz = env->FindClass("com/android/server/SystemServer"); if (clazz == NULL) { return UNKNOWN_ERROR; } jmethodID methodId = env->GetStaticMethodID(clazz, "init2", "()V"); if (methodId == NULL) { return UNKNOWN_ERROR; } env->CallStaticVoidMethod(clazz, methodId); ALOGI("System server: entering thread pool.\n"); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); ALOGI("System server: exiting thread pool.\n"); return NO_ERROR; }
再回到SystemServer.java的main()中的init2():init2()将操作交给了内部类ServerThread处理,看起run()函数:
public void run() { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, SystemClock.uptimeMillis()); Looper.prepare(); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_FOREGROUND); ....... AccountManagerService accountManager = null; ContentService contentService = null; LightsService lights = null; PowerManagerService power = null; BatteryService battery = null; VibratorService vibrator = null; AlarmManagerService alarm = null; NetworkManagementService networkManagement = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; ConnectivityService connectivity = null; WifiP2pService wifiP2p = null; WifiService wifi = null; NsdService serviceDiscovery= null; IPackageManager pm = null; Context context = null; WindowManagerService wm = null; BluetoothService bluetooth = null; BluetoothA2dpService bluetoothA2dp = null; DockObserver dock = null; UsbService usb = null; SerialService serial = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; NetworkTimeUpdateService networkTimeUpdater = null; CommonTimeManagementService commonTimeMgmtService = null; InputManagerService inputManager = null; //Bug#185069 fix low storage ,check the space&delete the temp file weather need. DeviceStorageMonitorService.freeSpace(); ....... ServiceManager.addService("xxx",XXX); ....... DevicePolicyManagerService devicePolicy = null; StatusBarManagerService statusBar = null; InputMethodManagerService imm = null; AppWidgetService appWidget = null; NotificationManagerService notification = null; WallpaperManagerService wallpaper = null; LocationManagerService location = null; CountryDetectorService countryDetector = null; TextServicesManagerService tsms = null; LockSettingsService lockSettings = null; DreamManagerService dreamy = null; ...... // These are needed to propagate to the runnable below. final Context contextF = context; final BatteryService batteryF = battery; final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; final NetworkPolicyManagerService networkPolicyF = networkPolicy; final ConnectivityService connectivityF = connectivity; final DockObserver dockF = dock; final UsbService usbF = usb; final ThrottleService throttleF = throttle; final UiModeManagerService uiModeF = uiMode; final AppWidgetService appWidgetF = appWidget; final WallpaperManagerService wallpaperF = wallpaper; final InputMethodManagerService immF = imm; final RecognitionManagerService recognitionF = recognition; final LocationManagerService locationF = location; final CountryDetectorService countryDetectorF = countryDetector; final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater; final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService; final TextServicesManagerService textServiceManagerServiceF = tsms; final StatusBarManagerService statusBarF = statusBar; final DreamManagerService dreamyF = dreamy; final InputManagerService inputManagerF = inputManager; final BluetoothService bluetoothF = bluetooth; ActivityManagerService.self().systemReady(new Runnable() { public void run() { Slog.i(TAG, "Making services ready"); if (!headless) startSystemUi(contextF); try { if (batteryF != null) batteryF.systemReady(); } catch (Throwable e) { reportWtf("making Battery Service ready", e); } try { if (networkManagementF != null) networkManagementF.systemReady(); } catch (Throwable e) { reportWtf("making Network Managment Service ready", e); } try { if (networkStatsF != null) networkStatsF.systemReady(); } catch (Throwable e) { reportWtf("making Network Stats Service ready", e); } try { if (networkPolicyF != null) networkPolicyF.systemReady(); } catch (Throwable e) { reportWtf("making Network Policy Service ready", e); } try { if (connectivityF != null) connectivityF.systemReady(); } catch (Throwable e) { reportWtf("making Connectivity Service ready", e); } try { if (dockF != null) dockF.systemReady(); } catch (Throwable e) { reportWtf("making Dock Service ready", e); } try { if (usbF != null) usbF.systemReady(); } catch (Throwable e) { reportWtf("making USB Service ready", e); } try { if (uiModeF != null) uiModeF.systemReady(); } catch (Throwable e) { reportWtf("making UI Mode Service ready", e); } try { if (recognitionF != null) recognitionF.systemReady(); } catch (Throwable e) { reportWtf("making Recognition Service ready", e); } Watchdog.getInstance().start(); // It is now okay to let the various system services start their // third party code... try { if (appWidgetF != null) appWidgetF.systemReady(safeMode); } catch (Throwable e) { reportWtf("making App Widget Service ready", e); } try { if (wallpaperF != null) wallpaperF.systemReady(); } catch (Throwable e) { reportWtf("making Wallpaper Service ready", e); } try { if (immF != null) immF.systemReady(statusBarF); } catch (Throwable e) { reportWtf("making Input Method Service ready", e); } try { if (locationF != null) locationF.systemReady(); } catch (Throwable e) { reportWtf("making Location Service ready", e); } try { if (countryDetectorF != null) countryDetectorF.systemReady(); } catch (Throwable e) { reportWtf("making Country Detector Service ready", e); } try { if (throttleF != null) throttleF.systemReady(); } catch (Throwable e) { reportWtf("making Throttle Service ready", e); } try { if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady(); } catch (Throwable e) { reportWtf("making Network Time Service ready", e); } try { if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady(); } catch (Throwable e) { reportWtf("making Common time management service ready", e); } try { if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady(); } catch (Throwable e) { reportWtf("making Text Services Manager Service ready", e); } try { if (dreamyF != null) dreamyF.systemReady(); } catch (Throwable e) { reportWtf("making DreamManagerService ready", e); } try { if (inputManagerF != null) inputManagerF.systemReady(bluetoothF); } catch (Throwable e) { reportWtf("making InputManagerService ready", e); } } }); //PowerManagerServer WakeLock dump thread (new Thread(new WakelockMonitor(power))).start(); ...... Looper.loop(); Slog.d(TAG, "System ServerThread is exiting!"); }
该函数有3个重要功能:
1,ServiceManager.addService("xxx",XXX),将系统服务注册进去。
2,systemReady(),告诉已经实现该接口servers,系统已经启动OK。
3,WakelockMonitor的启动。
至此,systemserver的启动工作已经完成。
4.4,launcher启动
桌面launcher即Home:1)源码:ActivityManagerService.java为入口,packages/apps/launcher*实现
2)说明:系统启动成功后SystemServer使用xxx.systemReady()通知各个服务,系统已经就绪,桌面程序Home就是在ActivityManagerService.systemReady()通知的过程中建立的,最终调用startHomeActivityLocked()启动launcher。Home在((ActivityManagerService)ActivityManagerNative.getDefault()).systemReady(.)。函数调用的过程中启动,其中systemReady()的参数是一段callback代码,如上面灰色显示的部分。这个函数的实现部分在文件:ActivityManagerService.java中。
先看ActivityManagerService.java的systemReady():
public void systemReady(final Runnable goingCallback) { ...... retrieveSettings(); if (goingCallback != null) goingCallback.run(); synchronized (this) { if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { List apps = AppGlobals.getPackageManager(). getPersistentApplications(STOCK_PM_FLAGS); if (apps != null) { int N = apps.size(); int i; for (i=0; i<N; i++) { ApplicationInfo info = (ApplicationInfo)apps.get(i); if (info != null && !info.packageName.equals("android")) { addAppLocked(info, false); } } } } catch (RemoteException ex) { // pm is in same process, this will never happen. } } // Start up initial activity. mBooting = true; try { if (AppGlobals.getPackageManager().hasSystemUidErrors()) { Message msg = Message.obtain(); msg.what = SHOW_UID_ERROR_MSG; mHandler.sendMessage(msg); } } catch (RemoteException e) { } mMainStack.resumeTopActivityLocked(null); } }
跳转至launcher的操作由resumeTopActivityLocked()完成,其实现在ActivityStack.java里的resumeTopActivityLocked()。
final ActivityManagerService mService; final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { // Find the first activity that is not finishing. ActivityRecord next = topRunningActivityLocked(null); // Remember how we'll process this pause/resume situation, and ensure // that the state is reset however we wind up proceeding. final boolean userLeaving = mUserLeaving; mUserLeaving = false; if (next == null) { // There are no more activities! Let's just start up the // Launcher... if (mMainStack) { ActivityOptions.abort(options); return mService.startHomeActivityLocked(0); } }
从上述代码可以看出其实是走到了mService.startHomeActivityLocked(0),而这里的mService也就是ActivityManagerService.java,再次回到ActivityManagerService.java的startHomeActivityLocked(0),至此launcher启动完成。
4.5,lockscreen启动
源码:frameworks/policies/base/phone/com/android/internal/policy/impl/*lock*说明:系统启动成功后SystemServer调用wm.systemReady()通知WindowManagerService,进而调用PhoneWindowManager,最终通过LockPatternKeyguardView显示解锁界面,跟踪代码可以看到解锁界面并不是一个Activity,这是只是向特定层上绘图,其代码了存放在特殊的位置。此处不再详细分析。
frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java的systemReady()方法:
/** {@inheritDoc} */ public void systemReady() { if (mKeyguardMediator != null) { // tell the keyguard mKeyguardMediator.onSystemReady(); } synchronized (mLock) { updateOrientationListenerLp(); mSystemReady = true; mHandler.post(new Runnable() { public void run() { updateSettings(); } }); } }
第一步,告诉锁屏控制器,系统已经启动完成,接下来有锁屏处理。 frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardViewMediator.java:
public void onSystemReady() { synchronized (this) { if (DEBUG) Log.d(TAG, "onSystemReady"); mSystemReady = true; doKeyguardLocked(); } }
再看其doKeyguardLocked()方法:
private void doKeyguardLocked() { if(engModeFlag){ Log.d(TAG, "show engmode!"); engModeFlag = false; return ; } // if another app is disabling us, don't show if (!mExternallyEnabled) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes // for an occasional ugly flicker in this situation: // 1) receive a call with the screen on (no keyguard) or make a call // 2) screen times out // 3) user hits key to turn screen back on // instead, we reenable the keyguard when we know the screen is off and the call // ends (see the broadcast receiver below) // TODO: clean this up when we have better support at the window manager level // for apps that wish to be on top of the keyguard return; } // if the keyguard is already showing, don't bother if (mKeyguardViewManager.isShowing()) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); return; } final boolean provisioned = mUpdateMonitor.isDeviceProvisioned(); final boolean lockedOrMissing = isSimLockedOrMissing(); if (!lockedOrMissing && !provisioned) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned" + " and the sim is not locked or missing"); return; } if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); return; } if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen"); showLocked(); }
至此,锁屏启动完成。
4.6,othersapps启动
系统启动完成后,launcher会加载系统已经安装的apk,并显示在launcher上。至此,android启动完成。
5,android启动动画效果剖析
在android启动的过程中我们通常可以看到若干个启动画面,均代表着不同的启动阶段,接下来根据启动阶段分析启动画面。uboot启动:会有一帧 uboot logo。
kernel启动:会有一帧kernel logo。(默认不显示,其控制宏是默认关闭的)
android启动:会有一帧静态图片+一个闪动的图片序列(即开机动画)。
通常情况下,我们在分析android的开机动画效果时,很少去分析uboot logo和kernel logo,因为ubootlogo 属于uboot阶段,kernel logo 属于linux范围。正常情况下,我们在down版本,烧到手机里去时,会吧logo.bmp加进去,这是系统的处理是:uboot logo,kernel logo,android static logo是同一张图片,即我们加的logo.bmp。
双framebuffer显示logo机制分析:本来一直走的是一级logo显示,从uboot logo一直持续到系统动画,但考虑期间时间偏长,欲采用标准三级logo。1、uboot logo 2、kernle logo 3 initlogo.rle 最后动画bootanimation.zip。但是kernel 对framebuffer修改较大,故考虑在uboot开始和结束显示两张logo(第二幅logo显示调用在theKernel()跳入内核函数之前),kernel跳过。uboot 直接刷屏显示第二幅logo
动作过慢,效果不佳,经考虑采用双buffer策略。思路:
1.原来只要显示一张uboot logo :把nand 中boot.logo 拷贝至lcd_base+fbsize处,然后搬至lcd_base显示;
2.现在创建第二个framebuffer于lcd_base+2*fbsize处,在显示第二幅logo前把nand 中第二幅logo 仍然拷贝至lcd_base+fbsize处,然后搬至lcd_base+2*fbsize第二个framebuffer基地址;
3.把第二个framebuffer基地址告诉lcd 控制寄存器,更新framebuffer基地址;
4.但在kernel中,寄存器仍然会指向第一个framebuffer基地址,那么第二幅logo显示犹如昙花一现啊,不过这个问题好解决,既然第二幅logo已经搬进了第二个framebuffer那,那么只要在进入内核前做一个memcpy就好了。
注:logo是bmp格式,在拷贝前需要进行相应的解析,参考uboot给的解析代码,自定义函数。
5.1,uboot logo
以正常模式启动分析uboot logo。即normal_mode.c根据前部分的分析可知,流程会走至normal_nand_mode.c的vlx_nand_boot()函数。//读取下载到nand中的boot_logo,就是开机亮的那一屏 off=part->offset; nand = &nand_info[dev->id->num]; //read boot image header size = 1<<19;//where the size come from????//和dowload工具中的地址一致 char * bmp_img = malloc(size); if(!bmp_img){ printf("not enough memory for splash image\n"); return; } ret = nand_read_offset_ret(nand, off, &size, (void *)bmp_img, &off); if(ret != 0){ printf("function: %s nand read error %d\n", __FUNCTION__, ret); return; } //第一次LCD logo lcd_display_logo(backlight_set,(ulong)bmp_img,size);
即由lcd_display_logo()完成相关操作。该函数在normal_mode.c中定义。
void lcd_display_logo(int backlight_set,ulong bmp_img,size_t size) { #ifdef CONFIG_SPLASH_SCREEN extern int lcd_display_bitmap(ulong bmp_image, int x, int y); extern void lcd_display(void); extern void *lcd_base; extern void Dcache_CleanRegion(unsigned int addr, unsigned int length); extern void set_backlight(uint32_t value); if(backlight_set == BACKLIGHT_ON){ lcd_display_bitmap((ulong)bmp_img, 0, 0); #if defined(CONFIG_SC8810) || defined(CONFIG_SC8825) || defined(CONFIG_SC8830) Dcache_CleanRegion((unsigned int)(lcd_base), size);//Size is to large. #endif lcd_display(); set_backlight(255); }else{ memset((unsigned int)lcd_base, 0, size); #if defined(CONFIG_SC8810) || defined(CONFIG_SC8825) || defined(CONFIG_SC8830) Dcache_CleanRegion((unsigned int)(lcd_base), size);//Size is to large. #endif lcd_display(); } #endif }
5.2,kernel logo
kernel logo 属于linux系统自带的logo机制,由于在android平台其显示默认是关闭的,此处不做多的分析,详细可参考博文:Android系统的开机画面显示过程分析 ,该博文只分析了启动过程的 kernel logo,android logo anim。相关代码:
/kernel/drivers/video/fbmem.c
/kernel/drivers/video/logo/logo.c
/kernel/drivers/video/logo/Kconfig
/kernel/include/linux/linux_logo.h
static int nologo; module_param(nologo, bool, 0); MODULE_PARM_DESC(nologo, "Disables startup logo"); /* logo's are marked __initdata. Use __init_refok to tell * modpost that it is intended that this function uses data * marked __initdata. */ const struct linux_logo * __init_refok fb_find_logo(int depth) { const struct linux_logo *logo = NULL; if (nologo) return NULL; ...... }
5.3,android logo anim
Android 系统启动后,init.c中main()调用queue_builtin_action(console_init_action, "console_init")时会根据console_init_action函数调用load_565rle_image()函数读取/initlogo.rle(一张565 rle压缩的位图),如果读取成功,则在/dev/graphics/fb0显示Logo图片;如果读取失败,则将/dev/tty0设为TEXT模式, 并打开/dev/tty0,输出文本“AN D R I O D”字样。
static int console_init_action(int nargs, char **args) { int fd; char tmp[PROP_VALUE_MAX]; if (console[0]) { snprintf(tmp, sizeof(tmp), "/dev/%s", console); console_name = strdup(tmp); } fd = open(console_name, O_RDWR); if (fd >= 0) have_console = 1; close(fd); if( load_565rle_image(INIT_IMAGE_FILE) ) { fd = open("/dev/tty0", O_WRONLY); if (fd >= 0) { const char *msg; msg = "\n" "\n" "\n" "\n" "\n" "\n" "\n" // console is 40 cols x 30 lines "\n" "\n" "\n" "\n" "\n" "\n" "\n" " A N D R O I D "; write(fd, msg, strlen(msg)); close(fd); } } return 0; }
由此调用logo.c 的load_565rle_image()函数。
int load_565rle_image(char *fn) { struct FB fb; struct stat s; unsigned short *data, *bits, *ptr; unsigned count, max; int fd; if (vt_set_mode(1)) return -1; fd = open(fn, O_RDONLY); if (fd < 0) { ERROR("cannot open '%s'\n", fn); goto fail_restore_text; } if (fstat(fd, &s) < 0) { goto fail_close_file; } data = mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0); if (data == MAP_FAILED) goto fail_close_file; if (fb_open(&fb)) goto fail_unmap_data; max = fb_width(&fb) * fb_height(&fb); ptr = data; count = s.st_size; bits = fb.bits; while (count > 3) { unsigned n = ptr[0]; if (n > max) break; android_memset16(bits, ptr[1], n << 1); bits += n; max -= n; ptr += 2; count -= 4; } munmap(data, s.st_size); fb_update(&fb); fb_close(&fb); close(fd); unlink(fn); return 0; fail_unmap_data: munmap(data, s.st_size); fail_close_file: close(fd); fail_restore_text: vt_set_mode(0); return -1; }
该图片格式是565RLE image format格式的,可用工具将bmp格式转化为rle格式。之后会有init.rc 并发开机动画。
相关文件:
/frameworks/base/cmds/bootanimation/BootAnimation.h
/frameworks/base/cmds/bootanimation/BootAnimation.cpp
/frameworks/base/cmds/bootanimation/bootanimation_main.cpp
/system/core/init/init.c
/system/core/rootdir/init.rc
init.c解析init.rc(其中定义服务:“service bootanim /system/bin/bootanimation”),bootanim 服务由SurfaceFlinger.readyToRun()(property_set("ctl.start", "bootanim");)执行开机动画、bootFinished()(property_set("ctl.stop", "bootanim");)执行停止开机动画。 BootAnimation.h和BootAnimation.cpp文件放到了/frameworks/base/cmds
/bootanimation目录下了,增加了一个入口文件bootanimation_main.cpp。Android.mk文件中可以看到,将开机 动画从原来的SurfaceFlinger里提取出来了,生成可执行文件:bootanimation。Android.mk代码如下:
//=============Android.mk====================== LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ bootanimation_main.cpp \ BootAnimation.cpp # need "-lrt" on Linux simulator to pick up clock_gettime ifeq ($(TARGET_SIMULATOR),true) ifeq ($(HOST_OS),linux) LOCAL_LDLIBS += -lrt endif endif LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libui \ libcorecg \ libsgl \ libEGL \ libGLESv1_CM \ libmedia LOCAL_C_INCLUDES := \ $(call include-path-for, corecg graphics) LOCAL_MODULE:= bootanimation include $(BUILD_EXECUTABLE) //==========================================
备注:
1,adb shell后,可以直接运行“bootanimation”来重新看开机动画,它会一直处于动画状态,而不会停止。
2,adb shell后,命令“setprop ctl.start bootanim”执行开机动画;命令“getprop ctl.start bootanim”停止开机动画。这两句命令分别对应SurfaceFlinger.cpp的两句语 句:property_set("ctl.start", "bootanim");和property_set("ctl.stop", "bootanim")。
至此android启动动画分析结束。
相关文章推荐
- Android OTA升级原理和流程分析(三)---Android系统的三种启动模式
- android系统启动流程之init.rc详细分析笔记
- Android 5.0 Camera系统源码分析(1):CameraService启动流程
- android系统启动流程启动画面学习之init和init.rc分析
- android系统启动流程之init.rc详细分析笔记
- android系统启动流程分析:
- Android OTA升级原理和流程分析(三)---Android系统的三种启动模式
- Android OTA升级原理和流程分析(三)---Android系统的三种启动模式
- 源码级分析Android系统启动流程
- android系统启动流程之init.rc详细分析笔记
- 系统启动Android应用流程分析
- [Android]Android系统启动流程源码分析
- 【源码分析】Android系统启动流程.
- Android 7.0系统启动流程分析
- Android系统启动流程分析
- Android OTA升级原理和流程分析(三)---Android系统的三种启动模式
- [Android]Android系统启动流程源码分析
- ANDROID系统启动流程分析
- Android系统启动流程分析之安装应用
- Android系统启动流程分析之启动应用 - ActivityManagerService