自己主机做多个网站,关于网站建设的题目,如何制作企业网页,做网站学哪些语言戳蓝字“牛晓伟”关注我哦#xff01;
用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章#xff0c;技术文章也可以有温度。
本文摘要
本文以访谈的方式来带大家了解zygote进程#xff0c;了解zygote进程是啥#xff1f;它的作用是啥#xff1f;它是如何一步…戳蓝字“牛晓伟”关注我哦
用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章技术文章也可以有温度。
本文摘要
本文以访谈的方式来带大家了解zygote进程了解zygote进程是啥它的作用是啥它是如何一步一步“长大成人”的。(文中代码基于Android13)
Android native系列的文章如下Android系统native进程之我是init进程Android系统native进程之属性能力的设计魅力Android系统native进程之进程杀手–lmkdAndroid系统native进程之日志系统–logd、logcatAndroid系统native进程之我是installd进程Apk安装之谜Android 存储成长记Android vold(卷管理)Android ServiceManager和它的兄弟们 Android 大话binder通信
鼎鼎大名的zygote
主持人“大家好啊我是今天的主持人你们大伙儿可是赚到为啥因为我今天有请到了鼎鼎大名的zygote千万别和我说你不认识她在Android所有的系统native进程中她的名气已经完全超过了vold、installd、lmkd等兄弟们甚至连她的父亲init都自愧不如。”
一位观众提问到“不好意思主持人我确实也听说过她但都是从别人嘴里面得知的完全不知道zygote名气大的原因是啥”
“谢谢这位观众这确实是我的疏忽首先我认为zygote名气大的原因是她有很多的子进程而这些子进程是可以直接跟用户打交道的比如微信、抖音而像init它的很多直接子进程都是demon类型的只是在后台默默无闻的工作用户对他们完全没感知。其次她是所有系统native进程中唯一可以运行Java/Kotlin代码的进程。那就有请我们今天的主角zygote吧。” zygote“大家好主持人完全过奖了我可不敢当。我的真名是zygote64是一个系统native进程。我其实还有个妹妹她的真名字是zygote她也和我一样也是一个系统native进程。我和我妹妹的主要工作职责是fork (孵化)可运行java代码的进程我和她分工明确我是孵化64位进程而她是孵化32位进程这也就是为啥我的真名后面有64的原因。别看我的工作职责很单一可是有非常多的重量级的进程如systemserver进程可都是我孵化出来的而我妹妹是没有孵化systemserver进程的。”
又一位观众提问到“您好zygote我这有问题请教您提到的fork是啥意思”
zygote“fork是一个孵化子进程的方法该方法的一个非常突出的特点就是快能以快到惊人的速度把子进程创建好fork做到如此之快的原因是会创建一个与父进程几乎完全相同的副本子进程从父进程继承了所有的内存布局、环境变量、打开的文件描述符等也就是子进程会与父进程共享非常多的数据在内核层只需要为子进程创建很少的数据即可。还有一个概念写时复制 (Copy-on-Write)子进程与父进程共享的数据比如子进程或者父进程改变了一些数据的话则这些数据不会再是共享状态了而是会拷贝出一份来这就是写时复制。”
这位观众继续提问到“听了您的解释还是有些懵圈能具体点吗”
zygote“那这样吧我们来看段代码看了代码您肯定就明白了。”
#include stdio.h
#include stdlib.h
#include unistd.h int globvar 6;
char globbuf[] 我是全局字符串;int main() { pid_t pid; int localvar 88;// 调用fork() pid fork(); //小于0则代表fork失败if (pid 0) { fprintf(stderr, Fork failed\n); return 1; } else if (pid 0) { //pid 0则代表是fork成功的子进程子进程会执行下面代码printf(I am the child process, my PID is %d\n, getpid()); printf(My parents PID is %d\n, getppid()); } else { // fork()返回非零值表示这是父进程返回值是子进程的PID父进程执行下面代码printf(I am the parent process, my PID is %d\n, getpid()); printf(My childs PID is %d\n, pid); } return 0;
}如上面的代码其中globvar、globbuf都是全局变量而localvar是局部变量当fork成功后子进程会把父进程的globvar、globbuf、localvar继承过来其实也就是共享过来它们的值也和父进程的值一样。
这就是fork后子进程与父进程共享数据其实也就是有点懒加载的味道你想啊如果孵化子进程的时候一上来就要把各种数据都复制一份首先会出现浪费的情况因为这些数据有可能很多是子进程和父进程都不会去修改的其次孵化子进程的速度肯定慢。
同样还是使用上面的例子来介绍下写时复制如下代码 } else if (pid 0) { //pid 0则代表是fork成功的子进程子进程会执行下面代码localvar 100;globvar 888;省略其他代码......}上面代码当子进程fork成功后只是把localvar和globvar修改为100和888则这时候localvar和globvar在子进程复制出自己单独的一份数据与父进程的localvar和globvar已经不是共享了而globbuf变量还是共享状态。
这位观众又说到“谢谢您的解答我还有个大胆的想法感觉您只是简单的fork了子进程如果孵化子进程的工作不由您来做而是让init进程启动systemserver进程systemserver进程来fork子进程这样做的话会节省内存开销 (因为zygote进程不需要启动)同时加快孵化子进程的速度 (因为由您孵化子进程需要跨进程通信而直接由systemserver就不用跨进程了)。不知道我这粗略的想法对不对。”
zygote“哈哈你这想法非常大胆但是会有一个严重的问题上面也提过fork机制子进程会与父进程共享非常多的数据而systemserver中的很多的数据首先是非常重要及对安全性要求非常高的这些数据如果被子进程共享的话想想后果都非常可怕。还有systemserver中的非常多的数据对于子进程是完全无用的 (比如binder等相关数据binder驱动描述符了等)因为子进程需要自己的binder数据。因此fork子进程需要一个干净纯粹的环境。”
zygote拖了拖腮帮子继续说到“而我可以提供这个非常干净的环境别以为这样就完事了还没有你想啊一个可运行Java代码的进程肯定是需要加载JVM (Java虚拟机)的如果每个子进程都加载一次JVM那它们的启动速度可想而知了。作为母亲的我怎么可能让这种事情发生呢我本着能让自己多吃苦多受罪也不能让孩子们吃一丁点苦的原则我会加载JVM以及一些公共的资源这样当子进程fork成功后它们一“出生”就有了JVM它们的启动速度可是杠杠硬啊。”
这位观众有些羞愧的说到“不好意思啊我这大胆的想法太过于鲁莽了您说的我都明白了谢谢。”
zygote“没事技术探讨不分对错我也像我的父亲init进程一样拥有很多的孩子下图是我和我妹及我的部分家族成员。” 如上图zygote64是我我的pid是1221可以看出所有的子进程都基本是由我fork出来的。
主持人“像您这么有名气您能分享下您的成名过程吗供年轻人参考参考还有您是基于什么样的机缘巧合要立志成名的。”
zygote“其实也没啥机缘巧合无非是榜样的力量当我刚出生不久的时候我父亲init就是我的榜样当我看到他有那么多的孩子 (子进程)我就立志也要像他一样能拥有很多的孩子 (子进程)我特别特别喜欢孩子。为了成名我可是吃了不少苦、做了非常多的努力。那就从我的出生说起吧。”
我的出生
我的出生要从init脚本文件说起。
主持人“为啥从脚本文件说起”
zygote“是这样的大家都知道我的父亲是init进程因为它的子进程是非常非常多的这么多子进程何时创建、创建之前需要执行哪些命令又更是多上加多这么多的信息它完全是无招架之力为了解决这个问题它创建了init脚本语言哪个子进程需要创建则配置自己的init脚本语言即可。”
主持人“了解了解那您继续说吧。”
下面是我和我妹的init脚本语言
//文件路径system/core/rootdir/init.zygote64_32.rc//名为zygote的service在fork成功后会执行 /system/bin/app_process64 可执行文件它是64为的后面是跟的参数--start-system-server是代表要fork systemserver进程--socket-name代表启动的server socket的名字
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-namezygote//该service属于main类别class mainpriority -20//user是root级别的user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root system//在重启的时候会重新启动下面这些服务onrestart exec_background - system system -- /system/bin/vdc volume abort_fuseonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart media.tuneronrestart restart netdonrestart restart wificondtask_profiles ProcessCapacityHigh MaxPerformancecritical window${zygote.critical_window.minute:-off} targetzygote-fatal//名为zygote_secondary的servicefork成功后会执行/system/bin/app_process32可执行文件它是32位的后面是可执行文件跟随的参数--socket-name代表启动的server socket的名字--enable-lazy-preload代表不需要预加载各种资源
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-namezygote_secondary --enable-lazy-preloadclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote_secondary stream 660 root systemsocket usap_pool_secondary stream 660 root systemonrestart restart zygotetask_profiles ProcessCapacityHigh MaxPerformance上面脚本文件配置了两个服务zygote和zygote_secondary下面简单列下它们的区别吧
zygote服务它的可执行文件是/system/bin/app_process64参数分别为–zygote --start-system-server --socket-namezygotezygote_secondary服务它的可执行文件是/system/bin/app_process32参数分别为–zygote --socket-namezygote_secondary --enable-lazy-preload
先记住上面的几个参数在用到它们的时候在解释。
配置了上面的init脚本文件还需要在init.rc脚本文件中配置何时启动zygote和zygote_secondary服务如下
//文件路径system/core/rootdir/init.rc
on zygote-start property:ro.crypto.stateunencryptedwait_for_prop odsign.verification.done 1# A/B update verifier that marks a successful boot.exec_start update_verifier_nonencryptedstart statsdstart netdstart zygotestart zygote_secondary
init进程会在对应的时机分别启动zygote、zygote_secondary服务启动这俩服务会fork (孵化)zygote64和zygote进程进而会分别执行/system/bin/app_process64和/system/bin/app_process32可执行文件这俩可执行文件最终都会执行到app_main.cpp的main方法如下代码
//文件路径cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{省略代码......
}
执行到app_main.cpp的main方法我和我妹妹就出生了。
主持人“我记得要想做成一件事情得需要列一些计划您作为成功人士应该也不例外吧若有计划的话能否讲讲您的计划”
zygote“是的我确实列了很多的计划那我就来分享给大家。”
我的计划
我的计划主要分为解析参数、启动JVM、拦截native线程创建、注册所有JNI方法、进入Java世界、解析参数、预加载资源、启动systemserver进程、可孵化App进程。计划的前一步都是在为计划的后一步做铺垫。
主持人“那就依次来介绍下我的计划把您的成名之路分享给大家吧。”
解析参数
zygote“解析参数作为计划的第一步主要是把脚本文件中的参数进行解析。”
还记得在我的出生介绍过我和我妹妹的init脚本信息吗在脚本信息中会传递一些参数会在app_main.cpp的main方法中解析这些参数这些参数可是非常的重要。有非常重要的一点再次提醒下我和我妹可是两个不同进程是分别执行app_main.cpp的main方法。
传递给我的参数有–zygote、–start-system-server、–socket-namezygote其中start-system-server则代表我会启动systemserver进程socket-name则用来建立socket通信也就是systemserver进程想要孵化子进程的话则会通过socket发信息给我
传递给我妹的参数有–zygote、–socket-namezygote_secondary --enable-lazy-preload这里只解释下enable-lazy-preload,它代表不需要预加载公共资源。
这些参数会被重新放置在类型为Vector的args变量中该变量会被传递给上层。
产物
会生成一个Vector的args变量它装载了init脚本传递过来的参数。
启动JVM
zygote“启动JVM作为第二步JVM大家肯定都非常熟悉了Java/Kotlin代码要想运行肯定是离不开它的为了不让每个fork出来的子进程在重复的启动JVM也为了让子进程启动速度更快。因此我会把JVM启动这样子进程再fork成功后就能直接使用我启动的JVM实例了。同时启动JVM也是最关键最核心的一步没有它则后面的计划根本执行不了。启动JVM的工作我是交给了AppRuntime而它继承了AndroidRuntime那就有请它俩来给大家介绍下吧。”
//文件路径cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{//构造AppRuntime实例AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));省略代码......//zygote值为trueif (zygote) {//调用start方法开始启动JVM并做一些其他的初始化工作runtime.start(com.android.internal.os.ZygoteInit, args, zygote);}省略代码......}AppRuntime“可以看下上面的代码使用init脚本文件传递过来的参数初始化一个AppRuntime的实例进而调用我的start方法会把com.android.internal.os.ZygoteInit、args、zygote为true这几个参数传递给我。”
主持人“方便解释下这几个参数吗”
AppRuntime“好的com.android.internal.os.ZygoteInit聪明的你一定能看出它是一个Java类对的它就是zygote进程进入Java世界的入口类args是解析参数阶段的产物。进入start方法就交给AndroidRuntime了。”
启动何种JVM
AndroidRuntime“在启动JVM之前我会对一些目录进行检查比如/apex/com.android.art等检查通过后就准备启动JVM了我采用解耦式来启动JVM。”
主持人“解耦式这个名字很新鲜啊给解释下呗。”
AndroidRuntime“解耦式就是指把启动JVM与启动何种JVM分离开我AndroidRuntime只定义启动JVM的动作和返回信息具体是启动Dalvik JVM还是ART JVM甚至是别的类型的JVM我通通都不关心。”
主持人“明白也就是您只定义一些规范和接口不关心实现者的具体实现。”
AndroidRuntime“对的我定义了如下接口请看如下代码。”
struct JniInvocationImpl {省略代码......// Function pointers to methods in JNI provider.jint (*JNI_GetDefaultJavaVMInitArgs)(void*);//创建虚拟机的方法jint (*JNI_CreateJavaVM)(JavaVM**, JNIEnv**, void*);//创建或者获取JavaVM实例jint (*JNI_GetCreatedJavaVMs)(JavaVM**, jsize, jsize*);
};
如上JniInvocationImpl结构体咱们主要看创建JVM的接口JNI_CreateJavaVM它的第一个参数类型是JavaVM类型的第二个参数是JNIEnv类型的这两个参数都是out类型的也就是具体实现者创建JVM成功后会把自己的实现的JavaVM和JNIEnv实例赋值给这两个参数而第三个参数是启动JVM需要传递的参数。
JavaVM和JNIEnv它们到底有啥作用呢
咱们声明了创建JVM的JNI_CreateJavaVM方法指针既然创建了JVM那肯定还需要一些与JVM进行交流的机制比如销毁JVM那JavaVM的作用就是与JVM进行交流的比如声明DestroyJavaVM方法来销毁JVM而具体如何销毁JVM是由创建JVM的具体实现者提供的。一个进程只存在一个JavaVM实例。
JNIEnv大家肯定在jni方法中经常看到它的主要作用是让native代码 (c/c)可以操控Java层的类、对象、类的方法、对象的方法。比如CallStaticVoidMethodV方法的作用就是让native代码可以调用Java的某个类的静态方法。一个线程也只存在一个JNIEnv实例。
不管是JavaVM还是JNIEnv只是定义了一些接口不管是创建JVM的接口JNI_CreateJavaVM还是该接口返回的JavaVM和JNIEnv到底是什么类型的实例都是由创建JVM的真正实现者来实现的。
下面先来看下它们的声明请自行取阅
//文件路径libnativehelper/include_jni/jni.hstruct _JavaVM {const struct JNIInvokeInterface* functions;#if defined(__cplusplus)jint DestroyJavaVM(){ return functions-DestroyJavaVM(this); }jint AttachCurrentThread(JNIEnv** p_env, void* thr_args){ return functions-AttachCurrentThread(this, p_env, thr_args); }jint DetachCurrentThread(){ return functions-DetachCurrentThread(this); }jint GetEnv(void** env, jint version){ return functions-GetEnv(this, env, version); }jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args){ return functions-AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
typedef _JavaVM JavaVM;//文件路径libnativehelper/include_jni/jni.hstruct _JNIEnv {/* do not rename this; it does not seem to be entirely opaque *///初始化了它所有的方法的实现都是调用它const struct JNINativeInterface* functions;#if defined(__cplusplus)jint GetVersion(){ return functions-GetVersion(this); }jclass DefineClass(const char *name, jobject loader, const jbyte* buf,jsize bufLen){ return functions-DefineClass(this, name, loader, buf, bufLen); }jclass FindClass(const char* name){ return functions-FindClass(this, name); }void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...){va_list args;va_start(args, methodID);functions-CallStaticVoidMethodV(this, clazz, methodID, args);va_end(args);}void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args){ functions-CallStaticVoidMethodV(this, clazz, methodID, args); }void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, const jvalue* args){ functions-CallStaticVoidMethodA(this, clazz, methodID, args); }省略代码......
}typedef _JNIEnv JNIEnv;
主持人“接口我看到了那到底启动的是何种类型的JVM呢”
AndroidRuntime“启动JVM之前是需要传递各种参数的在Android13上启动的是ART JVM具体实现是在libart.so库中。JavaVM的真正实例是JavaVMExt类型JNIEnv的真正实例是JNIEnvExt类型。”
如下查找创建JVM的真正实现者相关代码请自行取阅
//文件路径libnativehelper/JniInvocation.c
//该方法主要是查找创建JVM的真正实现者
bool JniInvocationInit(struct JniInvocationImpl* instance, const char* library_name) {#ifdef __ANDROID__char buffer[PROP_VALUE_MAX];#elsechar* buffer NULL;#endif//获取library_name这时候它的值是 libart.solibrary_name JniInvocationGetLibrary(library_name, buffer);DlLibrary library DlOpenLibrary(library_name);省略代码......DlSymbol JNI_GetDefaultJavaVMInitArgs_ FindSymbol(library, JNI_GetDefaultJavaVMInitArgs);if (JNI_GetDefaultJavaVMInitArgs_ NULL) {return false;}DlSymbol JNI_CreateJavaVM_ FindSymbol(library, JNI_CreateJavaVM);if (JNI_CreateJavaVM_ NULL) {return false;}DlSymbol JNI_GetCreatedJavaVMs_ FindSymbol(library, JNI_GetCreatedJavaVMs);if (JNI_GetCreatedJavaVMs_ NULL) {return false;}//下面代码把libart.so的相关方法赋值给instanceinstance-jni_provider_library_name library_name;instance-jni_provider_library library;instance-JNI_GetDefaultJavaVMInitArgs (jint (*)(void *)) JNI_GetDefaultJavaVMInitArgs_;instance-JNI_CreateJavaVM (jint (*)(JavaVM**, JNIEnv**, void*)) JNI_CreateJavaVM_;instance-JNI_GetCreatedJavaVMs (jint (*)(JavaVM**, jsize, jsize*)) JNI_GetCreatedJavaVMs_;return true;
}
创建JVM相关代码请自行取阅 //文件路径core/jni/AndroidRuntime.cppvoid AndroidRuntime::start(const char* className, const VectorString8 options, bool zygote)
{省略代码....../* start the virtual machine */JniInvocation jni_invocation;//调用jni_invocation的Init方法会去找创建JVM的真正实现者是谁jni_invocation.Init(NULL);JNIEnv* env;//会调用下面的startVm方法开始创建JVMmJavaVM是JavaVM类型env是JNIEnv类型if (startVm(mJavaVM, env, zygote, primary_zygote) ! 0) { //zygote为true primary_zygote为truereturn;}省略代码......
}int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote)
{JavaVMInitArgs initArgs;省略各种创建JVM参数的代码...../** Initialize the VM.** The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.* If this call succeeds, the VM is ready, and we can start issuing* JNI calls.*///调用JNI_CreateJavaVM创建JVMif (JNI_CreateJavaVM(pJavaVM, pEnv, initArgs) 0) {ALOGE(JNI_CreateJavaVM failed\n);return -1;}return 0;
}产物
该阶段会启动ART JVM启动成功后会创建JavaVMExt的对象赋值给pJavaVM同时也会创建JNIEnvExt的对象赋值给pEnv它们可都是在后面计划中起着非常重要的作用。
启动ART JVM后预示着可以运行Java代码了但是先别急还有一些事情要做。
拦截native线程创建 主持人“拦截native线程创建这个计划着实有些让人摸不着头脑。”
AndroidRuntime“哈哈确实有些晕圈在介绍该计划之前我先介绍下Android中的线程吧。”
Android中的线程分为两大类Java线程和native线程。而native线程又可分为纯native线程和可调用Java代码的native线程。
可调用Java代码的native线程是指该native线程的native代码是可以调用Java层的类、对象、类的方法、对象方法等反之纯native线程就是指native线程的native代码不能调用Java层的任何代码。
对于Java线程的Java代码是可以通过jni调用native层的代码而native层的代码如果要想调用Java层代码就需要用到JNIEnv对象而该对象是在每个Java线程创建成功后会自动创建的。
而对于可调用Java代码的native线程也需要使用到JNIEnv对象才能调用Java层的代码因此拦截native线程所做的事情就是针对 可调用Java代码的native线程 拦截它的创建过程这样就可以把JNIEnv对象交给它进而可以调用Java层代码。
主持人“创建的JNIEnv对象从何而来呢”
还记得在启动JVM阶段libart.so库创建的JavaVMExt对象吗它可是每个进程都拥有的调用它的AttachCurrentThread方法就可以创建JNIEnvExt类型的对象 (JNIEnvExt是JNIEnv的子类)
如下是部分代码请自行取阅
void AndroidRuntime::start(const char* className, const VectorString8 options, bool zygote)
{省略代码....../* start the virtual machine */JniInvocation jni_invocation;//调用jni_invocation的Init方法会去找创建JVM的真正实现者是谁jni_invocation.Init(NULL);JNIEnv* env;//会调用下面的startVm方法开始创建JVMmJavaVM是JavaVM类型env是JNIEnv类型if (startVm(mJavaVM, env, zygote, primary_zygote) ! 0) { //zygote为true primary_zygote为truereturn;}/** Register android functions.*/if (startReg(env) 0 ) {ALOGE(Unable to register all android natives\n);return;}省略代码......
}int AndroidRuntime::startReg(JNIEnv* env)
{省略代码......androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);省略代码......return 0;
}
产物
该阶段可调用Java代码的native线程的native代码是可以调用Java层的类、对象、类的方法、对象方法等Java层的代码。
注册所有JNI方法
JNI它是Java Native Interface的缩写它定义了Java层与native层之间的接口这些接口约定了Java层与native层方法调用的规则。
JNI到底长啥样子看下面代码
//文件路径/libnativehelper/include_jni/jni.htypedef struct {//name对应Java层的方法在Java类中使用native关键字来标注该方法const char* name;//方法签名会表明方法的参数、返回值等信息const char* signature;//native方法指针也就是在Java层调用名称为name的方法最终会调用到fnPtrvoid* fnPtr;
} JNINativeMethod;是不是有些抽象是吧那举些例子吧如下代码 public class ZygoteInit{省略代码......private static native void nativeZygoteInit();省略代码......
}//文件路径core/jni/AndroidRuntime.cppconst JNINativeMethod methods[] {{ nativeZygoteInit, ()V,(void*) com_android_internal_os_ZygoteInit_nativeZygoteInit },};如上例子ZygoteInit类的nativeZygoteInit方法是一个native方法该方法没有参数也没有返回值因此它的签名是 ()V 而它在native层的真正实现者是com_android_internal_os_ZygoteInit_nativeZygoteInit这个方法。
在Android系统中像ZygoteInit这样具有native方法的类很多比如Parcel、Binder等注册所有的JNI方法这里的所有就是指这些具有native方法的Java类。而注册就是调用JNIEnv对象的RegisterNatives方法进行注册注册后才可以在Java层调用native层的方法。
如下是注册所有JNI方法的代码有兴趣自行取阅
int AndroidRuntime::startReg(JNIEnv* env)
{省略代码......//调用register_jni_procs方法注册所有的JNI方法gRegJNI保存了所有的JNI方法if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) 0) { //注册各种jni方法env-PopLocalFrame(NULL);return -1;}省略代码......return 0;
}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) {
#ifndef NDEBUGALOGD(----------!!! %s failed to load\n, array[i].mName);
#endifreturn -1;}}return 0;
}static const RegJNIRec gRegJNI[] {REG_JNI(register_com_android_internal_os_RuntimeInit),REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),省略代码......REG_JNI(register_android_os_Process),REG_JNI(register_android_os_SystemProperties),REG_JNI(register_android_os_Binder),REG_JNI(register_android_os_Parcel),省略代码......
}
注意如上注册所有JNI方法的代码其中用到的JNIEnv对象是在启动JVM那阶段的产生的JNIEnv对象。
产物
该阶段注册了所有的JNI方法这样当fork出来的子进程就不需要再次注册所有的JNI方法了因为它们已经从zygote进程“继承”了这样做可以加快子进程的启动速度。
进入Java世界
以上的计划都是在native世界那我们总得来到Java世界吧init脚本携带的参数也解析了、ART JVM也启动了、所有的JNI方法也都注册了有了这些基础就已经具备了进入Java世界的条件进入Java世界唯一缺少的就是进入哪个类的哪个方法了
还记得在启动JVM的时候调用AppRuntime的start方法传递的第一个参数 com.android.internal.os.ZygoteInit 它就是要进入Java世界的入口类而要进入的方法是它的main方法。会调用JNIEnv的CallStaticVoidMethod方法进入ZygoteInit类的main方法当然也会把一些参数传递给main方法 (如init脚本携带的参数)
下面是对应代码请自行取阅
void AndroidRuntime::start(const char* className, const VectorString8 options, bool zygote)
{省略代码....../** We want to call main() with a String array with arguments in it.* At present we have two arguments, the class name and an option string.* Create an array to hold them.*/jclass stringClass;jobjectArray strArray;jstring classNameStr;//使用JNIEnv来查找java/lang/String的classstringClass env-FindClass(java/lang/String);assert(stringClass ! NULL);strArray env-NewObjectArray(options.size() 1, stringClass, NULL);assert(strArray ! NULL);//className就是 com.android.internal.os.ZygoteInitclassNameStr env-NewStringUTF(className);assert(classNameStr ! NULL);env-SetObjectArrayElement(strArray, 0, classNameStr);for (size_t i 0; i options.size(); i) {jstring optionsStr env-NewStringUTF(options.itemAt(i).string());assert(optionsStr ! NULL);env-SetObjectArrayElement(strArray, i 1, optionsStr);}/** Start VM. This thread becomes the main thread of the VM, and will* not return until the VM exits.*/char* slashClassName toSlashClassName(className ! NULL ? className : );jclass startClass env-FindClass(slashClassName);if (startClass NULL) {没找到ZygoteInit类} else {//查找main方法jmethodID startMeth env-GetStaticMethodID(startClass, main,([Ljava/lang/String;)V);if (startMeth NULL) {没找到main方法} else {//调用ZygoteInit的main方法env-CallStaticVoidMethod(startClass, startMeth, strArray);#if 0if (env-ExceptionCheck())threadExitUncaughtException(env);
#endif}}//退出Java世界后需要释放slashClassName并且把JVM销毁掉free(slashClassName);ALOGD(Shutting down VM\n);if (mJavaVM-DetachCurrentThread() ! JNI_OK)ALOGW(Warning: unable to detach main thread\n);if (mJavaVM-DestroyJavaVM() ! 0)ALOGW(Warning: VM did not shut down cleanly\n);
}如上代码进入Java世界后会停留在Java世界除非由于各种原因退出Java世界后会销毁JVM等操作。
产物
该阶段进入了ZygoteInit类的main方法也代表着完全的进入了Java世界。
解析参数
从native层是传递了多个参数过来的因此要在ZygoteInit类的main方法把这些参数解析出来而这些参数大部分来自init脚本定义的参数那我们再次把init脚本的部分信息请出来供大家看下 (如下代码)
//文件路径system/core/rootdir/init.zygote64_32.rc//名为zygote的service在fork成功后会执行 /system/bin/app_process64 可执行文件它是64为的后面是跟的参数--start-system-server是代表要fork systemserver进程--socket-name代表启动的server socket的名字
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-namezygote省略其他配置......service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-namezygote_secondary --enable-lazy-preload省略其他配置......同时结合ZygoteInit类的main方法的解析参数代码 (如下) public static void main(String[] argv) {省略代码......try {省略代码......//是否要启动systemserver进程boolean startSystemServer false;String zygoteSocketName zygote;String abiList null;//是否是推迟预加载资源boolean enableLazyPreload false; for (int i 1; i argv.length; i) {//zygote64进程需要启动systemserver进程if (start-system-server.equals(argv[i])) {startSystemServer true;} else if (--enable-lazy-preload.equals(argv[i])) {//zygote进程该值为true代表不需要预加载资源enableLazyPreload true;} else if (argv[i].startsWith(ABI_LIST_ARG)) { //这个值从native层传递过来的是从 adb shell getprop ro.product.cpu.abilist64获取的 值是arm64-v8aabiList argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {//app_process64为zygote app_process32为zygote_secondaryzygoteSocketName argv[i].substring(SOCKET_NAME_ARG.length());} else {throw new RuntimeException(Unknown command line argument: argv[i]);}}省略代码......} catch (Throwable ex) {Log.e(TAG, System zygote died with fatal exception, ex);throw ex;} finally {if (zygoteServer ! null) {zygoteServer.closeServerSocket();}}// Were in the child process and have exited the select loop. Proceed to execute the// command.if (caller ! null) {caller.run();}}zygote“如上代码我zygote64进程startSystemServer为true代表需要启动systemserver进程enableLazyPreload为false代表需要预加载资源zygoteSocketName值为zygote。”
“而我妹zygote进程startSystemServer为false代表不需要启动systemserver进程enableLazyPreload为true代表不需要预加载资源zygoteSocketName值为zygote_secondary。”
预加载资源
预加载资源就是把每个子进程都会用到的通用的、静态的资源提前在我zygote内加载这样当子进程被fork出来后就可以不需要在执行这些资源的加载流程了完全加快了子进程的启动速度。
只有我zygote才会预加载资源而我妹是不会的其主要原因是因为我是作为fork子进程的主力基本所有的子进程都是由我孵化的。而我妹只是一个辅助而已有可能在Android设备打开的这段时间内我妹基本上不会孵化进程或者说她孵化的子进程非常少而为了这么少的子进程来提前预加载资源这不是大大的浪费珍贵的内存吗这是大大的“犯罪”。
而通用的、静态的资源这里的通用指所有子进程都会用到的而静态指的是这些资源加载到内存后基本是不会发生变化的。
通用的、静态的资源有各种Java类这些类提前加载到JVM内后在子进程中就不需要再次加载它们了。还有Resource资源比如基础图片、文字等这些Resource资源提前加载进来子进程也不需要再次加载它们了。当然还有别的资源比如共享库、字体资源等。
下面是相关代码请自行取阅
public static void main(String[] argv) {省略代码......// In such cases, we will preload things prior to our first fork.//zygote64进程该值为falseif (!enableLazyPreload) {bootTimingsTraceLog.traceBegin(ZygotePreload);EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,SystemClock.uptimeMillis());//预加载资源preload(bootTimingsTraceLog); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis());bootTimingsTraceLog.traceEnd(); // ZygotePreload}省略代码......}static void preload(TimingsTraceLog bootTimingsTraceLog) {Log.d(TAG, begin preload);bootTimingsTraceLog.traceBegin(BeginPreload);beginPreload();bootTimingsTraceLog.traceEnd(); // BeginPreloadbootTimingsTraceLog.traceBegin(PreloadClasses);//虚拟机预加载各种类preloadClasses();bootTimingsTraceLog.traceEnd(); // PreloadClassesbootTimingsTraceLog.traceBegin(CacheNonBootClasspathClassLoaders);cacheNonBootClasspathClassLoaders();bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoadersbootTimingsTraceLog.traceBegin(PreloadResources);//预加载ResourcepreloadResources();bootTimingsTraceLog.traceEnd(); // PreloadResourcesTrace.traceBegin(Trace.TRACE_TAG_DALVIK, PreloadAppProcessHALs);nativePreloadAppProcessHALs();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);Trace.traceBegin(Trace.TRACE_TAG_DALVIK, PreloadGraphicsDriver);maybePreloadGraphicsDriver();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);//预加载共享libpreloadSharedLibraries();//预加载字体资源preloadTextResources(); // Ask the WebViewFactory to do any initialization that must run in the zygote process,// for memory sharing purposes.WebViewFactory.prepareWebViewInZygote();endPreload();warmUpJcaProviders();Log.d(TAG, end preload);sPreloadComplete true;}产物
该阶段预加载了各种资源这样在子进程fork成功后就不需要再次加载了大大的提高了子进程的启动速度。
启动systemserver进程
经过前面的几个阶段是完全已经具备了fork子进程的能力了但是作为所有子进程的“长子”systemserver进程它是需要最先被启动的只有它完全启动成功后哪个子进程需要被fork都是由systemserver进程内发出的命令。
关于启动systemserver进程在此节我们不做过多的讨论会在systemserver进程那节咱们再来分析。
可孵化App进程
启动完毕systemserver进程后前面的所有的铺垫工作启动ART JVM、拦截native线程创建、注册所有的JNI方法、预加载资源、启动systemserver进程都已经结束那最后的工作内容就是孵化App进程当systemserver进程发出孵化App进程的请求时候zygote就开始孵化App进程。
那这就有个需要解决的问题了systemserver进程和zygote64、zygote进程完全不是一个进程那它们之间应该采用何种通信方式呢
到底是选用binder or socket呢 ?
我觉得需要从以下几方面考虑。
binder通信就一定比socket通信快吗
大家都非常清楚binder通信要比socket通信快但是这个快我觉得得有一个前提条件那就是传递的数据量是不是比较大比较大的话我觉得快非常小的话就不至于快了为啥这样说呢还要从binder通信说起。
在binder通信中调用的方法的参数在通信过程中是只有一次拷贝而像方法对应的code值 (int类型),它其实在通信过程中是需要两次拷贝的。而socket通信数据是需要两次拷贝的。
若采用binder通信调用了一个无参的方法像code值在整个binder通信过程中是需要进行两次拷贝的若采用socket通信实现同样功能的话只需要传递一个类似于code值一样的简单类型这时候的code值也是进行了两次拷贝。那在以上的情况下就不见得binder通信比socket通信快了甚至有可能比socket通信还慢因为binder通信还要涉及到一些转换处理等流程。
若还是上面的情况换成调用的方法的参数是简单类型这时候也不见得binder通信比socket通信快了。
若采用binder通信遇到哪些问题需要做哪些处理
采用binder通信的话在fork子进程成功后需要做以下事情
子进程是需要把从父进程继承过来的binder fd (文件描述符)关闭掉的binder使用mmap打开的匿名共享内存也需要ummap掉因为这块内存是与zygote进程共享的而其他子进程是完全不需要的。子进程会继承父进程的线程若线程存在锁的话也需要对锁进行特殊处理否则子进程的继承过来的线程出现死锁情况。当然除了上面还需要子进程重新把自己的binder驱动打开在重新使用mmap打开自己的匿名共享内存。
上面想到的只是其中一部分还有需要做的事情因此如果采用binder通信的话子进程需要做的事情真的非常多这无疑增加了复杂度。
结合实际使用场景
systemserver进程是孵化App进程的发起方 (它是client端)而zygote是孵化App进程的执行方 (它是server端)非常的明确的一点client端只有一个并且还有个很关键的一点它们之间传递的数据量不大。
在基于传递的数据量不大情况下binder通信不至于比socket通信快并且若采用binder通信需要处理的问题确实比较多。基于以上分析反而binder通信的优势不明显了而采用socket通信的话首先是实现简单其次是子进程fork成功后只需要把继承过来的socket关闭掉即可这时候在启动binder通信。
如上systemserver进程和zygote64、zygote进程之间采用socket通信方式zygote64、zygote是作为server端而systemserver是作为client端zygote64、zygote若没有孵化App进程的请求则进入阻塞状态如若有则开始孵化App进程当孵化成功后再次去查找是否有孵化APp进程的请有孵化没有则进入阻塞状态。zygote64、zygote就是这样周而复始的重复的执行着上面的流程。
关于如何孵化App进程的分析会在App启动流程章节再次进行探讨。
结束
主持人“今天我们认识了zygote (真名是zygote64)和她的妹妹 (真名是zygote)zygote是孵化App进程的主力而她的妹妹是作为辅助存在。zygote她是一个伟大的母亲她为了让她的孩子们 (子进程)少受些‘累’它把启动JVM、注册所有的JNI方法、预加载资源等这些事情提前做了这样当她的孩子们在启动时候就不需要做这些重复的事情了进而提升了它们的启动速度。并且她还启动了systemserver进程。关于zygote如何启动systemserver进程和孵化App进程的内容会在后面节目中继续邀请她为大家分享。谢谢大家。”