Android开机启动过程分析

时间:2021-02-05 04:45:41

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

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

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

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

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

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

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

源码可以看出,init首先会创建一些必需的目录,如‘/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掉了,该函数复制了一份串口输出日志,日志包括从kernel启动到android初始化之间的打印信息,通过dmesg命令就可以打印出来。

 

其中还有如下代码:

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

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

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

 

在程序中系统会读取和解析init.rc文件,这个文件中更像是一些命令集合,但是程序并没有立即执行这些命令。而是先解析出来,按照一定规则进行整理放入一个链表中,init.rc中的命令并不是像shell一样是一个直接的执行命令,而是有一个内部的映射,这些映射存在于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的后面有一个for循环,命令的执行是在其中完成的:
    for(;;) {

        execute_one_command();

        restart_processes();

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

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

Service往往是单独的进程进行执行的,这些程序执行成功与否是系统关心的问题,系统需要监管这些进程,所以注册了一个信号处理函数handle_signal。如果子进程出现错误比如内存溢出等,这时会触发一个信号量,父进程在接收到该信号量后会到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中会有一个以on开头后面跟一个字段的关键字,如:

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

early-initinitearly-fsearly-bootboot等。

init.rc中还有一个很关键的阶段字,代码如下:
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文件中有一个核心的关键字service,使用该关键字就是要把其后的命令扩展为一个服务,这个关键字的规则也是最复杂的,例如下面这条命令:

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服务就需要重启;当然这些都是一些异常条件,也就是说如果media或者netd发生异常重启时,zgote就需要重启。

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

service bootanim /system/bin/bootanimation

    class main

    user root

    group graphics

    disabled

    oneshot

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

Zygote卵孵化器分析:

从上面的分析可以看出,到目前为止都没有真正涉及到android的东西,如果没有zygote那么安卓顶多算一个linux系统。Zygote进程是所有APK应用的父进程,其它进程都是由该进程孵化产生的,所以将其拟化为一个孵化器。通过android系统的虚拟机可以加载Java的开发环境,从功能上来说,Java语言更类似于ShellBase语言(属于伪代码),与硬件打交道的任务交给了虚拟机去完成。虚拟机的任务就是屏蔽平台的差异属性,使得同一份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函数中创建第一个虚拟机:

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

        ALOGE("JNI_CreateJavaVM failed\n");

        goto bail;

    }

同时app_process启动后会装载与framework的相关类以及resource资源,检查/system/framework/下的jar包,将其中的jar文件通过dexopt优化变为dex文件,同时安装到/data/dalvik-cache/目录中,这样下一次执行时直接到该目录下寻找。最后启动两个核心的类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文件获取包的一些信息,并将这些信息保存到/data/system/packages.xmlpackages.list中,以便系统后续使用。这些信息应该只会读取一次,下次启动后会检查是否有更新?如果没有就使用上一次的文件。

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

启动第一个Activity

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

mMainStack.resumeTopActivityLocked(null);

也就是说AmS会执行最顶层的TopActivity,但是第一次开机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不像其它系统一样,将一个固定的Activity作为主界面程序加载,而是在AmSstarHomeActiviyLocked()中,系统发出一个catagory字段包含CATEGORY_HOMEintent。如下:

intent.setComponent(mTopComponent);

        if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {

            intent.addCategory(Intent.CATEGORY_HOME);

        }

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

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

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