深入理解zygote

1、概述

 我们已经知道,Android 系统存在着两个完全不同的世界:

java世界,google提供的SDK主要就是针对这个世界的,在这个世界中运行的程序都是基于Dalvik虚拟机的java程序。
Native世界,也就是用Native语言,C语言或C++开发的程序,他们组成了Native世界,初次接触android的人可能会有如下疑问:
Android 是基于linux内核建立的,那么最早存在的肯定是Native世界,可java世界是什么时候创建的呢?
我们都知道程序运行都会有一个进程,但是我们在编写Activity,Service的时候却极少接触到“进程”这一概念,但是这些Activity或service又不能脱离进程而存在。那么这个“进程”是怎样创建和运行的呢?这是一个值得琢磨的问题。
我们经常使用系统的service,那么这些service哪里来的呢。
这些问题的答案和zygote和system_server有关,zygote的意思是受精卵,它和android系统中的java世界有着重要关系,而system_server则人如其名,系统中重要的service都驻留在java世界中。
zygote和system_server这两个进程分别是java的半边天,任何一个进程的死亡都能够导致java世界的崩溃。
zygote分析
zygote本身就是一native的应用软件,与驱动内核等均无关系。zygote是由init.rc文件中的配置项创建的,在分析他们之前我们有必要简单介绍一下“zygote”这个名字的来历。zygote最初的名字叫“app_process”,这个名字是在Android.mk 文件中指定的,但是运行过程中,app_process通过linux下的pctrl系统调用将自己的 名字换成了“zygote”,所以我们通过ps命令看到的进程名是“zygote”。
zygote玩的这一套“换名把戏并不影响我们的分析,它的原型app_process所对应的源文件是App_main.cpp 代码如下:

int main(int argc, const char* const argv[]){     /*        zygote 进程由init进程通过fork 而来,我们看一下init.rc中的设置的启动参数:       -Xzygote  /system/bin  --zygote  --start-system-server     */    // These are global variables in ProcessState.cpp    mArgC = argc;    mArgV = argv;    mArgLen = 0;    for (int i=0; i1;    }    mArgLen--;    AppRuntime runtime;    const char *arg;    const char *argv0;    argv0 = argv[0];    // Process command line arguments    // ignore argv[0]    argc--;    argv++;    // Everything up to '--' or first non '-' arg goes to the vm    //调用Appruntime的addVmArguments    int i = runtime.addVmArguments(argc, argv);    // Next arg is parent directory    if (i < argc) {         //设置runtime的mParentDir为/system/bin        runtime.mParentDir = argv[i++];    }    // Next arg is startup classname or "--zygote"    if (i < argc) {        arg = argv[i++];        if (0 == strcmp("--zygote", arg)) {    //我们传入的参数满足if条件,而且下面的startSystemServer的值为true            bool startSystemServer = (i < argc) ?                     strcmp(argv[i], "--start-system-server") == 0 : false;            setArgv0(argv0, "zygote");    //设置本进程的名称为zygote,这正是前文所讲的“接名把戏”            set_process_name("zygote");         //调用runtime的start,注意第二个参数startSystemServer为true            runtime.start("com.android.internal.os.ZygoteInit",                startSystemServer);        } else {此处代码省略```````````}

zygote的这个函数虽然简单,但是其重要功能确实有AppRuntime的start来完成的,下面我们就来分析AppRuntime。

2.1 AppRuntime分析

AppRuntime类的声明和实现都在App_main.cpp中,它是从AndroidRuntime类派生出来的,图1显示了这两个类的关系和一些重要函数。
由图1我们可知:
AppRuntime重载了onStarted 、onZygoteInit和onExit函数。
前面的代码调用了AndroidRuntime的start函数,由图1可知,这个start函数使用的是基类Android Runtime的start,我们来分析一下它,注意它调用的参数。

                                                                                                                      ![这里写图片描述](https://img-blog.csdn.net/20170515150920476?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMjcwNjEwNDk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)                                                                                                              图一    AppRuntime和AndroidRuntime的关系

AndroidRuntime.cpp

void AndroidRuntime::start(const char* className, const bool startSystemServer){    LOGD("\n>>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<\n");    //className 的值是“com.android.internal.os.zygoteInit”.    //startSystemServer的值是true.    char* slashClassName = NULL;    char* cp;    JNIEnv* env;    blockSigpipe();//处理SIGPIPE信号。    /*      * 'startSystemServer == true' means runtime is obslete and not run from      * init.rc anymore, so we print out the boot start event here.     */    if (startSystemServer) {        /* 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) {     //如果环境变量中没有ANDROID_ROOT,则新增该变量,并设置值为“/system”        rootDir = "/system";        if (!hasDir("/system")) {            LOG_FATAL("No root directory specified, and /android does not exist.");            goto bail;        }        setenv("ANDROID_ROOT", rootDir, 1);    }    //const char* kernelHack = getenv("LD_ASSUME_KERNEL");    //LOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);    /* start the virtual machine */     //创建虚拟机    if (startVm(&mJavaVM, &env) != 0)        goto bail;    /*     * Register android functions.     */    //注册jni函数    if (startReg(env) < 0) {        LOGE("Unable to register all android natives\n");        goto bail;    }    /*     * We want to call main() with a String array with arguments in it.     * At present we only have one argument, the class name.  Create an     * array to hold it.     */    jclass stringClass;    jobjectArray strArray;    jstring classNameStr;    jstring startSystemServerStr;    stringClass = env->FindClass("java/lang/String");    assert(stringClass != NULL);    //创建一个有两个元素的string数组,即java代码String strArray[]=new String[2].    strArray = env->NewObjectArray(2, stringClass, NULL);    assert(strArray != NULL);    classNameStr = env->NewStringUTF(className);    assert(classNameStr != NULL);    //设置第一个元素为“com.android.internal.os.zygoteInit”.    env->SetObjectArrayElement(strArray, 0, classNameStr);    startSystemServerStr = env->NewStringUTF(startSystemServer ?                                                  "true" : "false");    //设置第二个元素为“true”,注意这两个元素都是string类型,即字符串。    env->SetObjectArrayElement(strArray, 1, startSystemServerStr);    /*     * Start VM.  This thread becomes the main thread of the VM, and will     * not return until the VM exits.     */    jclass startClass;    jmethodID startMeth;    slashClassName = strdup(className);    /*      将字符串“com.android.internal.os.zygoteInit”中的“.”"换成“/”,      这样就变成了“com/android/internal/os/zygoteInit”,这个名字符合JNI规范      我们可将其简称为zygoteInit类    */    for (cp = slashClassName; *cp != '\0'; cp++)        if (*cp == '.')            *cp = '/';    startClass = env->FindClass(slashClassName);    if (startClass == NULL) {        LOGE("JavaVM unable to locate class '%s'\n", slashClassName);        /* keep going */    } else {     //找到zygoteInit类的static main函数的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 {      /*
  通过jni调用java函数,注意调用的函数是main,所属的类是com.android.internal.os.zygote ,传递的参数是“com.android.internal.os.zygoteInit true”,也调用了zygoteInit的main函数   后,zygote便进入了java世界,也就是说zygote是开创Android系统中java世界的盘古。
      */            env->CallStaticVoidMethod(startClass, startMeth, strArray);    //zygote 退出,在正常情况下,zygote不需要退出。#if 0            if (env->ExceptionCheck())                threadExitUncaughtException(env);#endif        }    }    LOGD("Shutting down VM\n");    if (mJavaVM->DetachCurrentThread() != JNI_OK)        LOGW("Warning: unable to detach main thread\n");    if (mJavaVM->DestroyJavaVM() != 0)        LOGW("Warning: VM did not shut down cleanly\n");    free(slashClassName);}

通过上面的分析,我们找到了三个关键的点,startVm(),startReg(),env->callStaticVoidMethod(),
1.2.1 、创建虚拟机——startVm
我们先来看三部曲的第一部:startVm,这个函数没有特别之处,就是调用java函数的虚拟机创建函数,但是创建虚拟机时的一些参数却是在startVm中确定的,期代码如下:
AndroidRuntime.cpp

static void readLocale(char* language, char* region){    char propLang[PROPERTY_VALUE_MAX], propRegn[PROPERTY_VALUE_MAX];    property_get("persist.sys.language", propLang, "");    property_get("persist.sys.country", propRegn, "");    if (*propLang == 0 && *propRegn == 0) {        /* Set to ro properties, default is en_US */        property_get("ro.product.locale.language", propLang, "en");        property_get("ro.product.locale.region", propRegn, "US");    }    strncat(language, propLang, 2);    strncat(region, propRegn, 2);    //LOGD("language=%s region=%s\n", language, region);}/* * Start the Dalvik Virtual Machine. * * Various arguments, most determined by system properties, are passed in. * The "mOptions" vector is updated. * * Returns 0 on success. */int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv){    /*
  这个函数绝大部分代码都是设置虚拟机的参数,我们之分析其中两个。  下面的代码是用来设置JNI check选项的,jni check指的是Native层调用JNI函数时,系统所做的一些检查工作,例如,调用NewUTFString函数时,系统会检查传入的字符是不是符合UTF-8的要求,JNI check还能检查资源是否被正确释放。但这个选项也有副作用,比如:

1)因为检查工作比较耗时,所以会影响系统运转速度。
2)有些检查过于严格,例如上面的字符串检查,一旦出错,则会调用进程就会abort。
所以,JNI check选项一般只是在调试的eng版设置,在正式发布的user版中则不设置该选项。下面的几句代码就控制着是否启动JNI check ,这是由系统属性决定的,eng版如经过特殊配置也可以去掉 JNI check

    */    int result = -1;    JavaVMInitArgs initArgs;    JavaVMOption opt;    char propBuf[PROPERTY_VALUE_MAX];    char stackTraceFileBuf[PROPERTY_VALUE_MAX];    char dexoptFlagsBuf[PROPERTY_VALUE_MAX];    char enableAssertBuf[sizeof("-ea:")-1 + PROPERTY_VALUE_MAX];    char jniOptsBuf[sizeof("-Xjniopts:")-1 + PROPERTY_VALUE_MAX];    char heapsizeOptsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];    char* stackTraceFile = NULL;    bool checkJni = false;    bool checkDexSum = false;    bool logStdio = false;    enum {      kEMDefault,      kEMIntPortable,      kEMIntFast,#if defined(WITH_JIT)      kEMJitCompiler,#endif    } executionMode = kEMDefault;    property_get("dalvik.vm.checkjni", propBuf, "");    if (strcmp(propBuf, "true") == 0) {        checkJni = true;    } else if (strcmp(propBuf, "false") != 0) {        /* property is neither true nor false; fall back on kernel parameter */        property_get("ro.kernel.android.checkjni", propBuf, "");        if (propBuf[0] == '1') {            checkJni = true;        }此处代码省略```````/*     设置虚拟机的heapsize,默认大小16MB。绝大多说厂商都会修改这个值,一般是32MB。heapsize不能设置的过小,否则在操作大尺寸的图片时无法分配所需的内存。     这里有一个问题,即headpsize既然是系统级的属性,那么能根据不同的应用程序的需求来进行活动状态调整呢?    */    strcpy(heapsizeOptsBuf, "-Xmx");    property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");    //LOGI("Heap size: %s", heapsizeOptsBuf);    opt.optionString = heapsizeOptsBuf;    mOptions.add(opt);此处代码省略``````    if (checkJni) {        /* extended JNI checking */        opt.optionString = "-Xcheck:jni";        mOptions.add(opt);        /* set a cap on JNI global references */        opt.optionString = "-Xjnigreflimit:2000";        mOptions.add(opt);此处代码省略``````````   //调用JNI_CreateJavaVM 创建虚拟机,pEnv返回当前线程的JNIENV变量    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {        LOGE("JNI_CreateJavaVM failed\n");        goto bail;    }    result = 0;bail:    free(stackTraceFile);    return result;}

关于dalvik虚拟机的详细参数,读者可以参见Dalvik/Docs/Dexopt.html中的说明,这个Docs目录下的内容,或许可以帮助我们更好的了解dalvik虚拟机。

2.2、注册JNI函数——-startReg

前面已经介绍了如何创建虚拟机,下一步则需要个给这个虚拟机注册一些JNI函数,正式因为后续Java世界用到了一些采用native方式实现的,所以才必须提前注册这些函数。
AndroidRuntime.cpp:

/* * Register android native functions with the VM. *//*static*/ int AndroidRuntime::startReg(JNIEnv* env){    /*     * This hook causes all future threads created in this process to be     * attached to the JavaVM.  (This needs to go away in favor of JNI     * Attach calls.)     */     //注意设置Thread类的线程创建函数为javaCreateThreadEtc。    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);    LOGD("--- registering native functions ---\n");    /*     * Every "register" function calls one or more things that return     * a local reference (e.g. FindClass).  Because we haven't really     * started the VM yet, they're all getting stored in the base frame     * and never released.  Use Push/Pop to manage the storage.     */    env->PushLocalFrame(200);    //注册JNI函数,gRegJNI是一个全局数组。    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {        env->PopLocalFrame(NULL);        return -1;    }    env->PopLocalFrame(NULL);    //createJavaThread("fubar", quickTest, (void*) "hello");    return 0;}我们来观察register_jni_process ,代码如下所示:static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env){    for (size_t i = 0; i < count; i++) {        if (array[i].mProc(env) < 0) {   //仅仅是一个封装,调用数组元素的mproc函数#ifndef NDEBUG            LOGD("----------!!! %s failed to load\n", array[i].mName);#endif            return -1;        }    }    return 0;}

上面的函数调用的不过是数组元素的mProc函数,让我们在直接看看这个全局数组gRegJNI变量。

ststic const RegJNIRec gRegJNI[] = {REG_JNI (reggister_android_debug_JNITest),此处代码省略````````//共有100项}

REG_JNI是一个宏,宏里面包括的就是那个mproc函数,这里我们就来分析一下。

android_debug_JNITest.cppint register_android_debug_JNITest(JNIEnv* env){//为android.debug.JNITest 类注册它所需要的函数return jniRegisterNativeMethods(env,”android/debug/JNITest”,gMethods,NELEM(gMethods));}

哦,原来mproc 就是为Java类注册JNI函数。
至此,虚拟机已经创建好了,JNI函数也注册了,下一步就是分析CallStaticVoidMethod了,通过这个函数,我们将进入Android精心打造的JAVA世界,而且最佳的是永远也不回Native世界了。

文献参考:

整理抄录自 — 《深入理解Android卷1》,邓凡平著。

更多相关文章

  1. C语言函数的递归(上)
  2. Viewpager显示前后两页部分界面(含5种demo)
  3. Android(安卓)开发连接 MySQL 数据库
  4. Android(安卓)4.1.2系统添加重启功能
  5. Android(安卓)NDK环境搭建和开发入门
  6. 观摩Android最高权力的Context通用性接口
  7. Android(安卓)JetPack学习笔记之ViewModel
  8. 关于Android自定义相机进行拍照(小米手机出现异常的原因)
  9. [置顶] android中自定义View

随机推荐

  1. android进程间服务通信
  2. android里面intent的简单用法
  3. android接入即时IM(接入亲加通信云)
  4. Android(安卓)Activity与Activity切换动
  5. Android中的时间自动更新
  6. MPlayer往Android上的移植(1)
  7. Android应用程序框架层和系统运行库层日
  8. Android(安卓)获取屏幕尺寸与密度
  9. Android学习日记(一)
  10. 【Android错误解决方案】解决AndroidStud