android启动

Android启动流程需要处理关键进程的启动,java世界的生成,各种服务的注册加载,还是比较复杂的。在实际应用中,我们常会遇到诸如,开机启动不了,启动时间过长,应用莫名崩溃的问题。分析启动流程,可以帮助我们快速定位这些问题,找到解决问题的思路。

二, Init 进程

1. init简介

Init进程是Android在用户空间的第一个进程,总的来说,它会解析init.rc配置文件,以及和机器相关的配置文件,执行各个阶段的动作,创建一些关键进程,尤其是和zygote进程,并初始化属性相关的资源,启动属性服务。最后进入一个无限循环,通过socket来接受信息,并执行相关动作,比如重启被杀死的zygote进程等。

 

三, Zygote 分析

1.       init.rc说起

先从Zygote  init.rc中的部分说起,如下所示

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 

onestart reastart

 

       这个部分描述了Zygote对应的elf文件是 /system/bin/app_process. 同时还有一些参数。 Onrestart部分,顾名思议,就是定义了Zygote重启时需要执行的动作。但是在系统启动的时候,通过ps查询却没有发现app_process的进程。这是因为app_process使用pctrl系统调用将自己的额名字换成了zygote.  Zygote对应的主程序代码在App_main.cpp中。

 

2.       Zygote诞生 

App_main.cpp中下面这个调用做了很多事情,包括:创建虚拟机,注册JNI函数,并在c层通过虚拟机使用java层的相关类执行相关方法,从而开创了android中的java世界。

 

2.1 Native空间分析

我们先从native空间进行分析。查阅app_main.cppmain函数,可以发现这段代码

if (zygote) {

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

                startSystemServer ? "start-system-server" : "");

}else if{

//…..

}

 

跟踪进入runtime.startsourceinsight会把我们导入AndroidRuntime.cpp文件。在这个文件中有三个重要的地方需要关注。如下:

 

1)      开启虚拟机。代码段如下:

 

/* start the virtual machine */

JNIEnv* env;

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

     return;

}

 

进入startVm函数(仍然在AndroidRuntime.cpp中定义),第一部分遇到的代码段是proerty_get 函数及后面和checkjni相关的代码。 Property_get 是属性服务的接口用来从系统中获取属性值。属性和属性值是一一对应的(key/value)。第一句话表示获取dalvik.vm.checkjni属性的value,并把value写到probBuf中,如果查不到这个属性就将proBuf设置成第三个参数””.  根据这部分代码,可以打开JNI_check 功能。一般情况下JNI_check在调试版本中使用(eng版本),可以帮助检查一些错误。但是这个选项的打开,也会耗费不必要的资源,所以在正式版本(user)中,建议关掉。

在这部分代码之后还有不少根据属性对虚拟机做的一些设置,其中有一个地方值得一说。代码如下:

strcpy(heapstartsizeOptsBuf, "-Xms");

    property_get("dalvik.vm.heapstartsize", heapstartsizeOptsBuf+4, "4m");

    opt.optionString = heapstartsizeOptsBuf;

    mOptions.add(opt);

 

这部分代码是设置虚拟机的heapsize 4.0系统和2.3系统在这一块不太一样。 2.3下只有属性 dalvik.vm.heapsize 是设置一个全局的大小,各个应用都是这个大小,设置大了可能导致浪费资源,设置小了则不利于充分利用系统内存来运行一些较大的程序。 4.0下从一个属性变成了三个属性。在M3单板中,key/value 如下所示:

[dalvik.vm.heapgrowthlimit]: [128m]

[dalvik.vm.heapsize]: [256m]

[dalvik.vm.heapstartsize]: [5m]

 

应用的最大内存被限制在128m, 但是在编译应用时如果编辑AndroidManifest.xml中的Application节点,增加属性largeheap="true, 则可以获得更大的内存。针对这一部分,4.02.3的区别由于时间关系没有深入研究,后续有机会建议深入地看一下,对于编写一些耗费资源较大的应用会很有帮助。

 

2)注册JNI 函数

  if (startReg(env) < 0) {

        LOGE("Unable to register all android natives\n");

        return;

    }

上面是在AndroidRuntime.cpp中的函数调用。跟踪这个函数(仍然在AndroidRuntime.cpp),可以看到在startReg中有一段代码

  if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {

        env->PopLocalFrame(NULL);

        return -1;

    }

其中register_jni_procs就是用来进行jni函数注册的。其注册的函数可以在数组

static const RegJNIRec gRegJNI[] = {

//….

}

中找到。这些函数给那些需要native空间支持的java函数提供了c接口。

 

3)从native空间(c/c++空间)进入java空间。下面这段代码将进程从native空间转到了java空间。

    char* slashClassName = toSlashClassName(className);

    jclass startClass = env->FindClass(slashClassName);

    if (startClass == NULL) {

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

        /* keep going */

    } else {

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

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

        if (startMeth == NULL) {

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

            /* keep going */

        } else {

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

 

#if 0

            if (env->ExceptionCheck())

                threadExitUncaughtException(env);

#endif

        }

    }

    free(slashClassName);

 

其中最关键的函数是 CallStaticVoidMethod。要理解这个需要有jni调用的基本知识,在这里只要把它理解成一个调用java空间main方法的函数就好了。

 

2.2 进入java世界

进入java世界后,可以找到zygoteinit.java. 这个文件。其中在类ZygoteInit中可以找到对应的main方法。在这个方法中做了一些事情

1)   注册了zygote用的socket。见函数registerZygoteSocket, 后续zygote要通过socket和其它进程通信,实现其它工作,比如分化出应用进程等工作。

2)   预加载类和资源.见函数preloadClasses() preloadResources(). preloadClasses中使用了java的反射机制,加载了/framework/base目录下的preloaded-classes文件描述的类。Android开机时,这个预加载机制会耗费不少时间(其它耗费时间的地方包括,apk文件信息的扫描和收集,SystemServer创建的各种Services),如果不做这一步,可以节约开机时间,但是在以后每个应用开启时都会有这一次加载过程(后面会看到,应用本身也是有zygote fork出来的,而fork采用的复制策略是非常快速的,预加载可以保证一次加载多次复制,节约应用的启动时间),所以一般情况下不建议在这里缩短开机时间,而采用别的办法,比如Berkeley Lab Checkpoint/Restart (BLCR) 技术来提升开机速度,即通过对系统做一个快照,将系统信息保存到一个文件中,当系统重启时,直接根据文件中的快照信息来回复重启之前的状态。

 

至此,Zygote的诞生分析完毕。

 

3.       SystemServer的诞生

SystemServer的进程名是system_server, 这是zygote生出来的一个子进程,地位崇高,

zygote可以说生死与共:SystemServer被杀死也会导致Zygote被杀死(而后Zygote重启,并在次重启SystemServer)。我们现在初步分析一下SystemServer的诞生。

 

Zygoteinit.java中下面这段代码开启了SystemServer

if (argv[1].equals("start-system-server")) {

               startSystemServer();

          } else if (!argv[1].equals("")) {

                startSystemServer();

           }else{

               throw new RuntimeException(argv[0] + USAGE_STRING);

}

 

我们进入 startSystemServer 函数,会发现下面这段代码

   pid = Zygote.forkSystemServer(

                    parsedArgs.uid, parsedArgs.gid,

                    parsedArgs.gids,

                    parsedArgs.debugFlags,

                    null,

                    parsedArgs.permittedCapabilities,

                    parsedArgs.effectiveCapabilities);

 

原来SystemServerZygote 通过fork机制生发出来的。这是java空间的函数,通过jni调用,可以进入native空间。我们可以在代码 dalvik_system_Zygote.c中看到函数 Dalvik_dalvik_sytem_Zygote_forkSystemServer并在这个函数中看到 forkAndSpecializeCommon函数的调用。在这个函数中我们能发现熟悉的进程控制流程.

 

pid = fork();

if (pid == 0).{

//…..

}

 

pid =fork() 之前会有一个函数 SetSignalHandler(). 这个函数用于设置信号好处理函

数,在信号处理函数中,会做判断:如果子进程SystemServer死了,Zygote也会把自己干掉。

显然SystemServer fork出来后,复制了Zygote的许多资源,它有着自己的任务和使

命。这里简单说一下,SystemServer会进行一些资源的初始化(比如Skia库的初始化),另外也会采取一些办法进入另外一个java类的main方法中(这里没有使用exec系列的系统调用,而采用抛异常的机制,非常巧妙)。这部分内容留到下一篇设计案例再作介绍了。

 

三、实践情况

本文主要介绍了从进程init到进程SystemServer 的启动流程。更多的内容会在下一篇中讲述,主要包括SystemServer的更多细节,服务的注册,Android图形系统等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值