您的位置:首页 > 编程语言

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

2017-05-15 15:11 471 查看

深入理解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);

fbf1
//设置第一个元素为“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》,邓凡平著。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android
相关文章推荐