深入理解zygote——1(代码源于GooGle)

深入理解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; i<argc; i++) {
        mArgLen += strlen(argv[i]) + 1;
    }
    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.cpp
int 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》,邓凡平著。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值