深入理解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; 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》,邓凡平著。
更多相关文章
- C语言函数的递归(上)
- Viewpager显示前后两页部分界面(含5种demo)
- Android(安卓)开发连接 MySQL 数据库
- Android(安卓)4.1.2系统添加重启功能
- Android(安卓)NDK环境搭建和开发入门
- 观摩Android最高权力的Context通用性接口
- Android(安卓)JetPack学习笔记之ViewModel
- 关于Android自定义相机进行拍照(小米手机出现异常的原因)
- [置顶] android中自定义View