程序员人生 网站导航

Android开机启动过程分析

栏目:综合技术时间:2015-01-18 10:39:57

首先android是基于Linux的内核,只有先加载了kernel才能启动安卓,对Linux来讲android只是其上的1个利用程序。Android的启动大致可以形象的划分为3个进程:

Init->init.rc->zygote从事嵌入式开发的人都知道,Linux加载完内核驱动后会挂载‘/’根文件系统,挂载完成后会履行‘/init’2进制程序,这也是内核启动后履行的第1个用户程序,android里面也是这样。这个程序的main函数位于android/system/core/init/init.c中,作为1个操作系统,初始化1般要完成以下几个工作:

1.创建需要的目录,挂载文件系统,输入和读取文件硬盘数据。

2.装载和设定全局的环境变量,为程序的运行搭建好必要的环境。

3.对android来讲,还需要运行Java虚拟机,这是安卓独有的跨平台特性。

4.加载和运行Framework框架,加载窗口桌面程序,也就是系统的GUI,最后将控制权交给用户后算启动完成。

根文件“/init”程序分析:

源码可以看出,init首先会创建1些必须的目录,如‘/dev’、‘/proc’、‘/sys’等。然后将装备mount到该目录下,mount完成后才可以创建装备节点。例如:

<span style="white-space:pre"> </span>mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); <span style="white-space:pre"> </span>mount("proc", "/proc", "proc", 0, NULL); <span style="white-space:pre"> </span>mount("sysfs", "/sys", "sysfs", 0, NULL);

创建串口输出节点,串口重定向输出:

open_devnull_stdio();

klog_init函数中创建了"/dev/__kmsg__"节点,以后很快unlink掉了,该函数复制了1份串口输出日志,日志包括从kernel启动到android初始化之间的打印信息,通过dmesg命令就能够打印出来。

 

其中还有以下代码:

property_init();该函数主要是加载1些默许的property属性,这些属性存在于build.propdefault.prop文件中。

get_hardware_name(hardware, &revision);该函数用于从"/proc/cpuinfo"节点中获得cpu的硬件和版本信息,将这些信息写入prop属性变量中。

process_kernel_cmdline();从节点“/proc/cmdline”中获得bootargs的环境变量,并设置到全局变量中以便后续使用。

 

在程序中系统会读取和解析init.rc文件,这个文件中更像是1些命令集合,但是程序并没有立即履行这些命令。而是先解析出来,依照1定规则进行整理放入1个链表中,init.rc中的命令其实不是像shell1样是1个直接的履行命令,而是有1个内部的映照,这些映照存在于init/keywords.h文件中,例如:

KEYWORD(mkdir,       COMMAND, 1, do_mkdir)

KEYWORD(mount,       COMMAND, 3, do_mount)

KEYWORD(rm,          COMMAND, 1, do_rm);

KEYWORD(rmdir,       COMMAND, 1, do_rmdir);

mkdirrc中的命令,但do_mkdir才是真实的命令履行实体。

init的后面有1个for循环,命令的履行是在其中完成的:
    for(;;) {

        execute_one_command();

        restart_processes();

        。。。。。。。。。。。。。。。

}其中execute_one_command会将命令提取出来,1条接1条的进行履行。命令中如果注册了1个service,那末它如果履行失败了,也还可以进行重新启动履行。

Service常常是单独的进程进行履行的,这些程序履行成功与否是系统关心的问题,系统需要监管这些进程,所以注册了1个信号处理函数handle_signal。如果子进程出现毛病比如内存溢出等,这时候会触发1个信号量,父进程在接收到该信号量后会到handle_signal中进行处理,父进程的有效管理,使得这些进程避免变成“野进程”或“僵尸进程”。信号处理函数wait_for_one_process中,如果等待的进程程序出错或超时会有“waitpid returned pid %d, status = %08x”的打印,平时如果某个service出错反复履行时就会有该打印。直到所有的service成功启动后,init才会正常退出for循环。

Init.rc初始化文件分析:

分析init.rc文件,通过该文件可以知道系统都做了哪些事情,比如其中的几个关键命令:

on property:ro.debuggable=1

start console

这类语句表示系统会读取prop属性(全局注册表)中的值,如果property:ro.debuggable=1则履行条件以下的语句start console,所以打开android的打印串口是在这地方做的。

sysclktz 0    

设置时区。

loglevel 3

设置log等级。

on property:ro.kernel.qemu=1

    start adbd

根据build.prop中的该属性设置决定是不是启动远程调试adbd

service bootanim /system/bin/bootanimation

启动开机动画。

如果留意,Init.rc中会有1个以on开头后面跟1个字段的关键字,如:

on early-init  这类关键字和上面的prop后面随着1个值的方式不相同。它主要用于表示android的启动阶段,init在加载这些命令时其实不是依照从文件头到文件尾的方式,而是寻觅这些关键字,依照启动顺序顺次加载,履行时也依照这个顺序履行,例如这些阶段还有:

early-initinitearly-fsearly-bootboot等。

init.rc中还有1个很关键的阶段字,代码以下:
on emmc-fs

mount ext4 ext4@system /system ro

 mount ext4 ext4@userdata /data nosuid nodev

mount ext4 ext4@cache /cache nosuid nodev

这几条命令的意思是将@system分区,以ext4的文件系统格式挂载到根目录/system下,ro表示只读的意思。emmc-fs阶段要开始于其它的阶段,由于只有将系统的核心目录挂载了,才能进行后续操作。Init.rc除支持emmc,还支持nand,决定用哪个是系统自动辨认完成的,代码以下:  

       if ( check_flash_type() == NAND_TYPE) {

            action_for_each_trigger("fs", action_add_queue_tail);

        }

        else if ( check_flash_type() == EMMC_TYPE) {

            action_for_each_trigger("emmc-fs", action_add_queue_tail);

        }

通过检查bootargs中的关键字“hinand”或“mmcblk”知道flash的类型。

rc文件中有1个核心的关键字service,使用该关键字就是要把其后的命令扩大为1个服务,这个关键字的规则也是最复杂的,例以下面这条命令:

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

    class main

    socket zygote stream 660 root system

    onrestart write /sys/android_power/request_state wake

    onrestart write /sys/power/state on

    onrestart restart media

    onrestart restart netd

这条命令使用service指令告知系统将zygote加入到系统服务中,service的语法为:

service service_name 可履行程序路径 可履行程序的入口参数

/system/bin/app_process为实际可履行的程序,后面‘-’为履行的参数。socket用于服务所使用到的socket,后面参数顺次为名称、类型、端口、地址。onrestart命令指定该服务重启的条件,即当满足这些条件后,zygote服务就需要重启;固然这些都是1些异常条件,也就是说如果medianetd产生异常重启时,zgote就需要重启。

如果sevices中有oneshot关键字,则表示该service只履行1次,例如:

service bootanim /system/bin/bootanimation

    class main

    user root

    group graphics

    disabled

    oneshot

表示开机动画履行1次,disabled表示如果出错或超时不会再次履行,更多具体的命令可参照init.rc文件。

Zygote卵孵化器分析:

从上面的分析可以看出,到目前为止都没有真正触及到android的东西,如果没有zygote那末安卓顶多算1个linux系统。Zygote进程是所有APK利用的父进程,其它进程都是由该进程孵化产生的,所以将其拟化为1个孵化器。通过android系统的虚拟机可以加载Java的开发环境,从功能上来讲,Java语言更类似于ShellBase语言(属于伪代码),与硬件打交道的任务交给了虚拟机去完成。虚拟机的任务就是屏蔽平台的差异属性,使得同1份Java语言可以同时运行于X86平台和ARM平台。Java语言另外的优势是面向对象,在利用开发方面比C语言更好用,缺点是运行效力比C差。

Zygote的实际履行程序是/system/bin/app_processapp_process的代码位于 frameworks/base/cmds/app_process/app_main.cpp中。

分析app_main.cpp的代码,app_process会在AndroidRuntime.startVm函数中创建第1个虚拟机:

if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {

        ALOGE("JNI_CreateJavaVM failed ");

        goto bail;

    }

同时app_process启动后会装载与framework的相干类和resource资源,检查/system/framework/下的jar包,将其中的jar文件通过dexopt优化变成dex文件,同时安装到/data/dalvik-cache/目录中,这样下1次履行时直接到该目录下寻觅。最后启动两个核心的类ZygoteInit.javaSystemServer.java类,这两个类都是以两个单独的进程启动的。SystemServer进程是Android系统的神经中枢,安卓利用直接交互的大部份系统服务都是在该进程中运行的,最关键的有:WindowManagerServer(Wms)ActivityManagerSystemServiceAmS)、PackageManagerServer(PmS)等,这些服务都是在该进程中以线程的方式启动的。

PackagemanagerService主要用来管理apk,该服务会解析apk中的组件,例如ActivieyService等。系统初始化时会遍历/system/app//data/app/目录,将apk以包名的情势拷贝到/data/data/<pkgName>目录下,将apk中的class文件保存到/data/dalvik-cache/目录下,同时以相应的apk进行命名。服务利用PackageParser类解析apk中的AndroidManifest.xml文件获得包的1些信息,并将这些信息保存到/data/system/packages.xmlpackages.list中,以便系统后续使用。这些信息应当只会读取1次,下次启动后会检查是不是有更新?如果没有就使用上1次的文件。

第1次开机时该服务还会去创建1些目录,如/data/目录下的dataapp-asecapp-libapp-private等。同时解析/system/etc/permissions/platform.xml文件,知道系统都定义了哪些系统权限和该权限对应可履行程序的uid。当用户安装利用时会弹出1个利用要求的权限,这些权限都是在这里定义的。从以上的步骤可以看出,第1次开机会做很多工作,这也就是为何android系统第1次启动都会很慢的缘由。

启动第1个Activity

当所有的线程服务都启动完成后,其中的ActivityManagerService(AmS)服务会去检测其它服务是不是完成,这个是通过调用systemReady()函数来完成的,最后会履行以下代码:

mMainStack.resumeTopActivityLocked(null);

也就是说AmS会履行最顶层的TopActivity,但是第1次开机TopActivity是没有的,这时候候就会有:

if (next == null) {

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

            // Launcher...

            if (mMainStack) {

                ActivityOptions.abort(options);

                return mService.startHomeActivityLocked(mCurrentUser);

            }

        }

也就是去加载HomeActivityAndroid不像其它系统1样,将1个固定的Activity作为主界面程序加载,而是在AmSstarHomeActiviyLocked()中,系统发出1个catagory字段包括CATEGORY_HOMEintent。以下:

intent.setComponent(mTopComponent);

        if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {

            intent.addCategory(Intent.CATEGORY_HOME);

        }

不管是哪一个利用程序,只要声明自己为该类型后,那就能够被认为是Home程序,系统并没有选取任何1个“Home”程序,而是将这个权利交给了用户,用户的选择决定了系统启动的Home,这个就是第1个启动的Acivity(当用户使用安卓系统启动后,会有1个选取界面的操作框,通过按键用户可以选取适合的Activity作为主界面)。当AmS要启动1个activity时,需要根据intent携带的activity名称,调用内部的PackageManager查询该名称对应的具体信息,如果存在就启动activity,否则返回失败。

当系统的Lancher界面启动显现完成后,系统的启动也就算完成了。

【本代码基于android4.2源码进行分析】

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐