Zygote研究报告

一.Zygote的前世今生
1.前世
在init.rc中,zygote作为一个service被启动,其配置为:
service zygote /system/bin/app_process --zygote /system/bin --zygote --start-system-server
其中:
app_process命令是frameworks/base/cmds/app_process/app_main.cpp编译生成的,作为zygote进程的主程序,zygote这个进程名字是进入程序后修改的。
--zygote 指定程序的进程名,nice name
/system/bin 指定程序的parent dir
--zygote 表示启动com.android.internal.os.ZygoteInit类的main函数
--start-system-server 表示启动system_server
2.今生
大体上,zygote完成了如下的使命
-----------------------------                                                                  -------------------------
|     zygote process     |                                                                   |   some process   |
-----------------------------                                                                  -------------------------
                  |             fork                  ----------------------------------                    |
                  +--------------------------->|   system server process  |                       |
                  |                                     ----------------------------------                   |
                  |                                fork command                                            |
                  |<------------------------------------------<------------------------------------|
                  |             fork                   --------------------------------                     |
                  +---------------------------->|           new process       |                      |
                  |                                      --------------------------------                     |
                  |                                   return pid                                               |
                  |-------------------------------------------->---------------------------------->|
1.fork system server进程,并执行com.android.server.SystemServer的main函数
system server进程主要是启动各种service,并进入loop循环等待处理消息
2.等待其他进程的command,在收到command之后进行必要的安全检查之后,fork一个新的进程,并执行指定类的main函数

二.Zygote-受精卵-孕育生命
zygote的中文意思是受精卵,在android中它也的确起到了孕育生命的作用,下面我们将看到其孕育生命的过程
1.调用树
可参考如下的调用树来阅读下面的分析过程:
main[app_main.cpp]
    +--AppRuntime.start[即AndroidRuntime.start][AndroidRuntime.cpp]
              +--AndroidRuntime.startVM[AndroidRuntime.cpp]
                      +--JNI_CreateJavaVM[ dalvik/vm/Jni.cpp ]
              +--AndroidRuntime.startReg[AndroidRuntime.cpp]
              +--env->CallStaticVoidMethod
                       +--ZygoteInit::main[ ZygoteInit.java ]
                             +--ZygoteInit::registerZygoteSocket[ ZygoteInit.java ]
                             +--ZygoteInit::preloadClasses[ ZygoteInit.java ]
                             +--ZygoteInit::startSystemServer[ ZygoteInit.java ]
                                  +--nativeForkSystemServer [ Zygote.java]
                                  +--handleSystemServerProcess [ZygoteInit.java ]
                                          +--zygoteInitNative[ZygoteInit.java ]
                                          +--ApplicationInit[ZygoteInit.java ]
                             +--ZygoteInit::runSelectLoopMode[ ZygoteInit.java ]


2.app_main.cpp
app_main.cpp中的main函数是zygote的主函数,它的流程如下:
1)解析参数
2)调用AppRuntime.start
AppRuntime从AndroidRuntime继承的,在main函数中存在一个AppRuntime的栈变量,在这里会调用AppRuntime的构造函数,也即AndroidRuntime的构造函数,AndroidRuntime类定义于frameworks/base/core/jni/AndroidRuntime.cpp中,具体如下所示。由于这个main函数不会退出,所以这个实例是AndroidRuntime的唯一实例。从构造函数中,我们可以看出skia库就是在这里初始化的,gCurRuntime是一个全局的指针,在后面的调用中将会通过调用它来调用AndroidRuntime的虚函数实现。
AndroidRuntime::AndroidRuntime()
{
    SkGraphics::Init();
    // this sets our preference for 16bit images during decode
    // in case the src is opaque and 24bit
    SkImageDecoder::SetDeviceConfig(SkBitmap::kRGB_565_Config);
    // This cache is shared between browser native images, and java "purgeable"
    // bitmaps. This globalpool is for images that do not either use the java
    // heap, or are not backed by ashmem. See BitmapFactory.cpp for the key
    // java call site.
    SkImageRef_GlobalPool::SetRAMBudget(512 * 1024);
    // There is also a global font cache, but its budget is specified in code
    // see SkFontHost_android.cpp

    // Pre-allocate enough space to hold a fair number of options.
    mOptions.setCapacity(20);

    assert(gCurRuntime == NULL);        // one per process
    gCurRuntime = this;
}

3.AndroidRuntime.start
AppRuntime.start函数实际上是调用了基类的start的函数,
        runtime.start("com.android.internal.os.ZygoteInit",//指定启动java类的类名
                startSystemServer ? "start-system-server" : "");
它的流程如下:
1)AndroidRuntime::startVm
2)AndroidRuntime::startReg
3)env->CallStaticVoidMethod

4.AndroidRuntime::startVm函数的流程:
根据system的property设置传递给虚拟机的参数
1).checkjni
根据dalvik.vm.checkjni和ro.kernel.android.checkjni来确定是否设置checkjni
jni check是在native调用jni函数的时候进行一些检查工作,保证jni调用的正确性
2).dalvik.vm.heapsize
设置虚拟机的heap大小,默认是16M
关于dalvik的说明在Dalvik/Docs/Dexopt.html
3).调用JNI_CreateJavaVM

5.JNI_CreateJavaVM调用流程如下:
1).初始化gDvmJni
gDvmJni最重要的一个字段是一个指向JavaVMExt的指针
2).初始化gDvm
gDvm中的内容相当多,主要是关于虚拟机的各种设定和native函数
调用完成后,具有如下的结构:
                                    __________
gDvmJni  -------------->|    JavaVM   |---->gInvokeInterface
                                   -----------------        ---------------
                                   |      enList    |----> |   JNIEnv   |----->gNativeInterface
                                   -----------------        ---------------
                                                                |      next     |------->JNIEnv
                                                                ----------------
                                                                |     prev      |
                                                                ---------------- 
这里需要说明的是从内存布局看,JavaVM和JavaVMExt具有类似C++的派生关系,JNIEnv和JNIEnvExt也具有这种关系,所以他们可以互相强制转换,上图中使用的结构定义如下所述:
dalvik/vm/init.cpp定义
struct DvmGlobals gDvm;
struct DvmJniGlobals gDvmJni;

dalvik/vm/globals.h
struct DvmJniGlobals {
    bool useCheckJni;
    bool warnOnly;
    bool forceCopy;

    // Provide backwards compatibility for pre-ICS apps on ICS.
    bool workAroundAppJniBugs;

    // Debugging help for third-party developers. Similar to -Xjnitrace.
    bool logThirdPartyJni;

    // We only support a single JavaVM per process.
    JavaVM*     jniVm;
};

dalvik/vm/jniinternal.h定义
struct JavaVMExt {
    const struct JNIInvokeInterface* funcTable;     /* must be first */

    const struct JNIInvokeInterface* baseFuncTable;

    /* head of list of JNIEnvs associated with this VM */
    JNIEnvExt*      envList;
    pthread_mutex_t envListLock;
};
struct _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*/
};

dalvik/vm/jni.cpp
static const struct JNIInvokeInterface gInvokeInterface = {
    NULL,
    NULL,
    NULL,

    DestroyJavaVM,
    AttachCurrentThread,
    DetachCurrentThread,

    GetEnv,

    AttachCurrentThreadAsDaemon,
};

------------------------------------------------------------------------------

struct JNIEnvExt {
    const struct JNINativeInterface* funcTable;     /* must be first */

    const struct JNINativeInterface* baseFuncTable;

    u4      envThreadId;
    Thread* self;

    /* if nonzero, we are in a "critical" JNI call */
    int     critical;

    struct JNIEnvExt* prev;
    struct JNIEnvExt* next;
};
struct _ JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;
....
};
static const struct JNINativeInterface gNativeInterface = {
......
}
如果要研究java虚拟机,可以从此做进一步分析,但是总体上来说,JNIEnv表示JavaVM的运行环境

6.AndroidRuntime::startReg
AndroidRuntime::startReg函数流程
1.设置Thread的线程创建函数
2.在JNIEnv中push一个Frame
3.使用JNI机制,向JNIEnv中注册内置的java类
4.在JNIEnv中pop一个Frame
为什么需要Push和Pop Frame?
在该函数的代码中有这样一段注释:
    /*
     * 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.
     */
大致意思是说,每个reigister函数会调用很多返回local ref的东西,因为现在VM还没有启动,所以这些东西不会自动释放,所以这里需要手动加入Frame
这 里的Frame应该是stack frame,用来构成frame链表的,查看FindClass函数(dalvik/vm/jni.cpp),其中调用了一个 addLocalReference的函数,这个函数就是用来把FindClass返回的local ref加入到当前Frame的一个引用表中,在native函数的栈推出的时候,根据Frame的引用表释放这些local reference的。当然,由于此时VM还没有启动,Frame还没有建立起来,所以需要我们受到的push一个Frame。该函数的注释如下:
/*
 * Add a local reference for an object to the current stack frame.  When
 * the native function returns, the reference will be discarded.
 *
 * We need to allow the same reference to be added multiple times.
 *
 * This will be called on otherwise unreferenced objects.  We cannot do
 * GC allocations here, and it's best if we don't grab a mutex.
 *
 * Returns the local reference (currently just the same pointer that was
 * passed in), or NULL on failure.
 */

7.env->CallStaticVoidMethod
该函数调用com.android.internal.os.ZygoteInit类的main函数,这一步完成了从C++到java的过程,从此一去不复返,调用这个main函数的时候,传入了start-system-server作为参数。其中com.android.internal.os.ZygoteInit这个类名是在app_main.cpp的man函数中调用AndroidRuntime.start的时候指定的

8.ZygoteInit::main
该函数定义于framework/base/core/java/com/android/internal/os/ZygoteInit.java,其流程如下:
1)registerZygoteSocket函数
2)preloadClasses函数
3)startSystemServer函数
4)runSelectLoopMode函数

8.ZygoteInit::registerZygoteSocket函数
通过对init进程的分析,我们知道在启动服务的时候,会在/dev /socket目录下创建一个以服务名字命名的socket,并把这个socket的fd加入到环境变量中,命名为 ANDROID_SOCKET_[service_name],在这个函数中,我们看到的正是这个 socket,ANDROID_SOCKET_zygote,
    private static void registerZygoteSocket() {
        if (sServerSocket == null) {
            int fileDesc;
            try {
                String env = System.getenv(ANDROID_SOCKET_ENV);//从环境变量中获得socket fd
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(
                        ANDROID_SOCKET_ENV + " unset or invalid", ex);
            }

            try {
                sServerSocket = new LocalServerSocket(
                        createFileDescriptor(fileDesc)); //createFileDescriptor 是一个native函数,对应的c函数定义于framework/base/core/jni /com_android_internal_os_zygoteinit.cpp文件中 com_android_internal_os_ZygoteInit_createFileDescriptor ,该函数的主要作用就是在JNIEnv中创建一个与fd对应的jobject,让java环境通过这个jobject来访问fd。
            //LocalServerSocket是一个封装类,它定义于framework/base/core/java/android/net /LocalServerSocket.java,它的功能由LocalSocketImpl类实现,LocalServerSocket的构造函数就直 接调用了LocalSocketImpl的listen方法,该方法调用native函数实现功能,对应的c函数定义于framework/base /core/jni/android_net_LocalSocketImpl.cpp中的socket_listen
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }
由 上面的代码可以知道,该函数就是启动监听/dev/socket/zygote socket,除此之外,关于java层通过jni调用native函数,我们也可以知道一些规则,如果一个java文件中的某个函数是native函 数,那么对应的C++函数实现所在的文件名的命名规则为:java文件的包名与文件名组成的路径,以LocalSocketImpl类的listen方法 为例,该类的全路径为android.net.LocalSocketImpl,则对应的native函数所在的文件的路径为 framework/base/core/jni/android_net_LocalSocketImpl.cpp,其中framework/base /core/jni是一个基准路径

9.ZygoteInit::preloadClasses函数
该函数主要用来预加载类,frameworks/base/preloaded-classes文件中,列出了需要加载的所有类名,该文件是由framworks/base/tools/preload生成的。
该函数有几点需要说明:
1.使用VMRuntime.getTargetHeapUtilization
VMRuntime 是对VM运行期的功能的封装,它封装了很多的native函数,通过getTargetHeapUtilization和 setTargetHeapUtilization方法,用来获取和设置heap的使用率。这里设置为最高0.8的使用率。这样可能控制了gc的次数,加 快加载速度
2.统计加载过程中内存分配的大小,超过阀值的时候,进行GC

10.ZygoteInit::preloadResources函数
该函数使用frameworks/base/core/java/android/content/res/Resources.java中定义的Resources类来加载
com.android.internal.R.array.preloaded_drawables
com.android.internal.R.array.preloaded_color_state_lists

11.ZygoteInit::startSystemServer函数
该函数的流程如下:
11.1.fork一个进程作为system_server
java层的nativeForkSystemServer函数 定义于libcore/dalvik/src/main/java/dalvik/system/Zygote.java,它对应dalvik/vm /native/dalvik_system_Zygote.cpp中 的 Dalvik_dalvik_system_Zygote_forkSystemServer函数,该函数的功能如下:
1).设置sigchild信号的处理为kill(petpid(),SIGKILL),即在收到子进程退出的信号后,kill自己,即zygote进程,这样init进程会重新启动zygote进程,继而由zygote进程重新启动system_server进程
2).fork一个子进程,即system_server进程,pid设置给gDvm.systemServerPid
11.2.调用handleSystemServerProcess作为这个子进程的处理函数
该函数定义于frameworks/base/core/java/com/android/internal/os/ZygoteInit.java,其流程如下:
1)调用RuntimeInit::zygoteInit(frameworks/base/core/java/com/android/internal/os/RuntimeInit.java)
public static final void zygoteInit(int targetSdkVersion, String[] argv)
            throws ZygoteInit.MethodAndArgsCaller {
        if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");

        redirectLogStreams();

        commonInit();
        //该函数是一个native函数,对应frameworks/base/core/jni/AndroidRuntime.cpp中的 com_android_internal_os_RuntimeInit_zygoteInit函数,该函数调用 gCurRuntime->onZygoteInit();其中gCurRuntime是AndroidRuntime构造函数中初始化的时候赋值 的,而AndroidRuntime实例是在app_main.cpp的main函数中作为栈变量声明的,实际上是作为AppRuntime实例存在 的,AppRuntime是AndroidRuntime的派生类。这个main函数是Zygote的主函数,不会退出。onZygoteInit是启动 线程与Binder通信
        zygoteInitNative();
       
        //该函数最 终会调用invokeStaticMain,即调用参数指定类的main函数,即在startSystemServer中指定的类名 com.android.server.SystemServer。比较有意思的是,该函数并非直接调用这个main函数,而是通过抛出异常的方式,抛出 了ZygoteInit.MethodAndArgsCaller异常,这个异常最终是在Zygote的main函数中catch住,并调用了该实例的 run方法。这种方式应该是为了释放多次调用函数的栈变量。需要注意的是,此时,异常的执行已经是在子进程中了。
        applicationInit(targetSdkVersion, argv);
    }

12.runSelectLoopMode函数
该函数的流程如下:
1)把注册的zygote socket加入到select的文件描述符表中
2)selectReadable,等待可读的文件描述符
3)如果zygote socket可读,则accept一个新的ZygoteConnection,并把它加入到文件描述符表中
4)如果是除zygote以外可读,则调用对应的ZygoteConnection.runOnce执行请求的fork命令。
这里有两个问题需要特别说明:
1.ZygoteConnection代表一个connection,在其构造函数中,调用了getPeerCredential函数,该函数对应getPeerCredentials_native函数,最终对应getsockopt函数,通过SO_PEERCRED选项,得到cred信息,对于这个选项和cred结构,我们通过
man 7 unix
man 7 socket
得到以下的信息
getsockopt得到的cred信息是只读的,发送端通过使用socket option SO_PASSCRED来设置是否允许获取cred,接收端通过SO_PEERCRED选项,得到cred信息,cred的结构内容如下:
struct ucred {
                      pid_t pid;    /* process ID of the sending process */
                      uid_t uid;    /* user ID of the sending process */
                      gid_t gid;    /* group ID of the sending process */
                  };
zygote进程通过这个结构的内容来判断是否允许请求fork新的进程。
2.谁发送的fork请求
ZygoteConnection.readArgumentList方法的注释中给出了答案,frameworks/base/core/java/android/os/Process.java的 zygoteSendArgsAndGetResult函数发送参数,并获取执行结果,
    /**
         * See android.os.Process.zygoteSendArgsAndGetPid()
         * Presently the wire format to the zygote process is:
         * a) a count of arguments (argc, in essence)
         * b) a number of newline-separated argument strings equal to count
         *
         * After the zygote process reads these it will write the pid of
         * the child or -1 on failure.
         */
注释中的zygoteSendArgsAndGetPid应该是老版本的代码,在使用新的代码后,没有更新注释
再进一步查看,该函数被Process::startViaZygote调用,在这里添加了各种调用的参数,如果nice-name指定进程的名称,processClass指定执行的类名称,runtime-init说明通过RuntimeInit来运行类。
再进一步查看,Process::startViaZygote又被Process::start调用,也就是说,如果有进程通过android.os.Process.start方法启动进程的时候,实际上就是发送指定的参数给zygote进程

对比zygoteSendArgsAndGetResult方法,再来看ZygoteConnection.runOnce
1)ZygoteConnection.readArgumentList
2)安全性检查
3)Zygote.forkAndSpecialize fork一个进程
4)在子进程中,执行RuntimeInit.zygoteInit执行指定的进程
5)在父进程中,把子进程pid发送回去。

三.背景知识
1.JNI调用
在进行JNI调用的时候,会用到如下的signature定义,例如:
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
"()V"

"(II)V"

"(Ljava/lang/String;Ljava/lang/String;)V"

实际上这些字符是与函数的参数类型一一对应的。

"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具体的每一个字符的对应关系如下

字符 Java类型 C类型

V      void            void
Z       jboolean     boolean
I        jint              int
J       jlong            long
D      jdouble       double
F      jfloat            float
B      jbyte            byte
C      jchar           char
S      jshort          short

数组则以"["开始,用两个字符表示

[I       jintArray      int[]
[F     jfloatArray    float[]
[B     jbyteArray    byte[]
[C    jcharArray    char[]
[S    jshortArray   short[]
[D    jdoubleArray double[]
[J     jlongArray     long[]
[Z    jbooleanArray boolean[]

上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring

2.子进程与父进程
子进程继承了父进程的文件描述符表,环境,运行时,与。子进程复制了父进程的环境,得到了一份拷贝,所以它对于环境的修改,并不影响父进程。但是文件描述符表却是与父进程共享的。
四.待研究的问题
1.Thread
对于Thread需要特别研究一下,在JavaVM的InvokeInterface中,有attachThread的函数,在startReg中有设置 Thread的创建函数,这些都需要详细研究一下。

五.参考文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值