• Zygote原理学习


      

    1 zygote分析

    1.1 简介

    Zygote本身是一个NATIVE层的应用程序,与驱动、内核无关。前面已经介绍过了,zygote由init进程根据init.rc配置文件创建。其实本质上来说,zygote就是app_process,这个名字在android.mk中指定,但是在运行的时候,app_process通过LINUX下的pctrl系统调用将自己的名字换成了“zygote”,所以通过ps命令就可以看到进程的名字为zygote。Pctrl函数相关信息见:

    http://blog.csdn.net/zuokong/article/details/7318154

    zygote的原型app_process在App_main.cpp中:

    int main(int argc, char* const argv[])

    {

        // 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* argv0 = argv[0];

    ……

    /*将参数加入到JAVAVM中作为其参数选项*/

        int i = runtime.addVmArguments(argc, argv);

        // Parse runtime arguments.  Stop at first unrecognized option.

        bool zygote = false;

        bool startSystemServer = false;

        bool application = false;

        const char* parentDir = NULL;

        const char* niceName = NULL;

        const char* className = NULL;

        while (i < argc) {

            const char* arg = argv[i++];

            if (!parentDir) {

                parentDir = arg;

            } else if (strcmp(arg, "--zygote") == 0) {

                zygote = true;

                niceName = "zygote";

            } else if (strcmp(arg, "--start-system-server") == 0) {

                startSystemServer = true;

            } else if (strcmp(arg, "--application") == 0) {

                application = true;

            } else if (strncmp(arg, "--nice-name=", 12) == 0) {

                niceName = arg + 12;

            } else {

                className = arg;

                break;

            }

        }

    //这就是前面说的换名把戏的实现函数

    if (niceName && *niceName) {

            setArgv0(argv0, niceName);

            set_process_name(niceName);

        }

    //设置runtime的mParentDir为/sysem/bin

        runtime.mParentDir = parentDir;

    if (zygote) {

    //★这是关键函数,怎个zygote的主要功能都由此函数启动!

            runtime.start("com.android.internal.os.ZygoteInit",

                    startSystemServer ? "start-system-server" : "");

        } else if (className) {

            // Remainder of args get passed to startup class main()

            runtime.mClassName = className;

            runtime.mArgC = argc - i;

            runtime.mArgV = argv + i;

            runtime.start("com.android.internal.os.RuntimeInit",

                    application ? "application" : "tool");

        } else {

            fprintf(stderr, "Error: no class name or --zygote supplied. ");

            app_usage();

            LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");

            return 10;

        }

    }

    1.2 AppRuntime分析

    AppRuntime是一个类,该类的声明和实现都在APP_main.cpp中,该类派生至AndroidRuntime类。

    AppRuntime重载了onStarted, onZygoteInit, onExit函数。

    1.1节中说道zygote的main函数中调用了AppRuntime.start函数,下面来详细分析下这个函数的功能:

    1) 获取环境变量ANDROID_ROOT的值,赋给rootDir,如果该值为null,就新增该变量,并将其值设置为”/system”。

    2) ★创建虚拟机:

    if (startVm(&mJavaVM, &env) != 0) {

            return;

    }

    3) ★注册JNI函数:

    startReg(env);

    4) 构建一个String数组,这个数组含有两个元素:classname, option。这个数组用于后面第五步的main函数的参数!

    stringClass = env->FindClass("java/lang/String");

        assert(stringClass != NULL);

    //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);

        optionsStr = env->NewStringUTF(options);

      //设置第二个元素值为“start-system-server”!

        env->SetObjectArrayElement(strArray, 1, optionsStr);

    5) ★开始虚拟机,此线程就变为虚拟机的主线程,除非虚拟机退出,否则这个线程是不会返回的:

    ①将前面的com.android.internal.os.zygoteInit转换为斜线的形式,这样就符合jni函数的规范了,以后我们均简称其为zygoteInit类:

    char* slashClassName = toSlashClassName(className);

    ②调用该类的main(strings)函数,此函数的参数就是前面说的string数组。

    /*在调用zygoteInit的main函数后,zygote便进入了java世界!所以说zygote是android系统中JAVA世界的鼻祖!*/

    char* slashClassName = toSlashClassName(className);

        jclass startClass = env->FindClass(slashClassName);

        if (startClass == NULL) {

            ALOGE("JavaVM unable to locate class '%s' ", slashClassName);

            /* keep going */

        } else {

            jmethodID startMeth = env->GetStaticMethodID(startClass, "main",

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

            if (startMeth == NULL) {

                ALOGE("JavaVM unable to find main() in '%s' ", className);

                /* keep going */

            } else {

                env->CallStaticVoidMethod(startClass, startMeth, strArray);

    …….

    通过上面对app_runtime的start函数的分析,我们发现了三个关键点:2、3、5。它们共同创建了整个Android的JAVA世界,下面分别对它们三个进行详细分析。

    1.3 创建虚拟机——startVM

    该函数就是调用JNI的虚拟机创建函数,但是创建虚拟机时的一些参数却是在此函数中确定的,详细代码在base/core/jni/Android_Runtime.cpp中:

    /*此函数的绝大部分代码都是设置虚拟机的参数,这里只分析其中的两个。*/

    /*下面的代码是用来设置JNI check选项的,JNIcheck就是NATIVE层调用JNI函数时,系统所做的一些检查工作。如,调用NEWUTFString函数时,系统会检测传入的字符串是否满足UTF-8编码格式。它还能检测资源是否被正确释放,但这个选项比较耗时,只有在eng版的系统里面才有,正式发布的user版是关闭了此功能的。下面几句代码就是有系统属性来控制是否使用JNI check。*/

       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;

            }

    }

    ……

    /*开始设置虚拟机的堆栈起始大小和最大大小,默认起始大小是4MB最大大小是16MB,不过绝大多数厂商都会更改这个值*/

    strcpy(heapstartsizeOptsBuf, "-Xms");

        property_get("dalvik.vm.heapstartsize", heapstartsizeOptsBuf+4, "4m");

        opt.optionString = heapstartsizeOptsBuf;

        mOptions.add(opt);

        strcpy(heapsizeOptsBuf, "-Xmx");

        property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");

        opt.optionString = heapsizeOptsBuf;

        mOptions.add(opt);

    ……

    /*最后调用JNI_CreateJavaVM 函数创建虚拟机,pEnv返回当前线程的JNIEnv,从这里就可以了解到VM是对进程而言的,JNIEnv是对线程而言的*/

    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {

            ALOGE("JNI_CreateJavaVM failed ");

            goto bail;

        }

    1.4 注册JNI函数——startReg

    上一节介绍了如何创建虚拟机,本节就介绍如何给这个虚拟机注册JNI函数。因为后续的JAVA世界用到的一些函数是采用NATIVE方式实现的,所以才必须提前注册这些JNI函数。详细代码同样在AndroidRuntime.cpp中:

    /*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(这其实是一个hook函数),此函数的具体作用将在后面进行详细分析 */

        androidSetCreateThreadFunc((android_create_thread_fn)javaCreateThreadEtc);

        ALOGV("--- registering native functions --- ");

        /*

         * 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是一个全局大数组,每个元素对应了一个JNI注册方法。

        if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {

            env->PopLocalFrame(NULL);

            return -1;

        }

        env->PopLocalFrame(NULL);

    //这句话听说是码农休闲时的小把戏,可以称为IT界的文物~

        //createJavaThread("fubar", quickTest, (void*) "hello");

    return 0;

    }

    下面再来看看register_jni_procs函数的详细代码:

    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函数,完成对应的JNI注册

    #ifndef NDEBUG

                ALOGD("----------!!! %s failed to load ", array[i].mName);

    #endif

                return -1;

            }

        }

        return 0;

    }

    上面提到了gRegJNI全局数组,该数组元素调用mProc函数,那么它们到底是什么呢?

    static const RegJNIRec gRegJNI[] = {

        REG_JNI(register_com_android_internal_os_RuntimeInit),

        REG_JNI(register_android_os_SystemClock),

        REG_JNI(register_android_util_EventLog),

        REG_JNI(register_android_util_Log),

        REG_JNI(register_android_util_FloatMath),

        REG_JNI(register_android_text_format_Time),

    ….

    约有100项,每一项的括号里面的参数name对应一个函数name(env)

    }

    REG_JNI是一个宏,宏里面包含的就是那个mProc函数。

    #ifdef NDEBUG

        #define REG_JNI(name)      { name }

        struct RegJNIRec {

            int (*mProc)(JNIEnv*);

        };

    #else

        #define REG_JNI(name)      { name, #name }

        struct RegJNIRec {

            int (*mProc)(JNIEnv*);

            const char* mName;

        };

    #endif

    这个宏的意思就是,当我们调用gRegJNI[i].mProc(env)时,真正执行的函数就是name_i (env),这里为了方便就这样定义了,这个小技巧值得学习啊。

    至此,我们已经分析了startVM, StartReg,了解了虚拟机的创建,学习了JNI函数的注册,下面就调用zygoteInit的main函数,进入java世界了!

    1.5 Welcom to Java World

    Java世界的入口函数就是zygoteInit的main函数,下面看看这个main函数的具体功能(ps:后面的代码都是java代码了~)。

    public static void main(String argv[]) {

            try {

                // Start profiling the zygote initialization.

                SamplingProfilerIntegration.start();

               //★key1注册zygote用的socket

                registerZygoteSocket();

                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,

                    SystemClock.uptimeMillis());

                preload(); //★key2预加载类资源等

    /*

    static void preload() {

            preloadClasses();

            preloadResources();

            preloadOpenGL();

        }

    */

                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,

                    SystemClock.uptimeMillis());

                // Finish profiling the zygote initialization.

                SamplingProfilerIntegration.writeZygoteSnapshot();

                //强制执行一次垃圾回收

                gc();

                // If requested, start system server directly from Zygote

                if (argv.length != 2) {

                    throw new RuntimeException(argv[0] + USAGE_STRING);

                }

                if (argv[1].equals("start-system-server")) {

                    startSystemServer(); //★key3我们传入的参数是满足if的,所以调用此函数启动system_server进程

                } else if (!argv[1].equals("")) {

                    throw new RuntimeException(argv[0] + USAGE_STRING);

                }

                Log.i(TAG, "Accepting command socket connections");

                runSelectLoop();//★key4 zygote调用这个函数

                closeServerSocket();

            } catch (MethodAndArgsCaller caller) {

                caller.run(); //★key5 很重要的caller run函数,以后分析

            } catch (RuntimeException ex) {

                Log.e(TAG, "Zygote died with exception", ex);

                closeServerSocket();

                throw ex;

            }

    }

    在这个main函数中,列举了5大关键点,下面一一进行分析。

    1、建立IPC通信服务端:registerZygoteSocket

    Zygote及系统中其他程序之间的通讯并没有使用Binder,而是采用了基于AF_UNIX类型的Socket。registerZygoteSocket的工作就是建立这个socket。

    private static void registerZygoteSocket() {

            if (sServerSocket == null) {

                int fileDesc;

                try {

            /*从环境变量中获取socket的fd,回想一下zygote是如何启动的:在init进程的main函数中有一个execve函数,该函数的第三个参数就是环境变量*/

                    String env = System.getenv(ANDROID_SOCKET_ENV);

                    fileDesc = Integer.parseInt(env);

                } catch (RuntimeException ex) {

                    throw new RuntimeException(

                            ANDROID_SOCKET_ENV + " unset or invalid", ex);

                }

                try {

    //创建服务端socket,这个socket将listen并accept Client

                    sServerSocket = new LocalServerSocket(

                            createFileDescriptor(fileDesc));

                } catch (IOException ex) {

                    throw new RuntimeException(

                            "Error binding to local socket '" + fileDesc + "'", ex);

                }

            }

    }

    registerZygoteSocket很简单,就是创建一个socket服务器。不过我们应当思考一下:

    ①谁是客户端?

    ②服务端如何处理来自客户端的请求?

    2、预加载类和资源

    这里主要分析preloadClasses和preloadResources函数。

    首先来看preloadClasses函数:

    ①预加载类的信息存储在PRELOADED_CLASSES变量中,它的值为”preloaded_classes”。这是一个文件的名字。意思就是预加载类的信息存储在这个文件中!

    ②读取该文件的每一行,再通过JAVA反射来加载类,每一行中存储的就是预加载类的类名。

    Class.forName(line);

       ③一些扫尾工作。

    Preloadclasses看起来很简单,但其实它要加载1268个类!!!这是相当耗时的,这也是Android系统启动慢的原因之一。Preload-class文件由frame/base/tools/preload工具生成,它会判断每个类的加载时间是否大于1250微秒,超过这个时间的话就会被写到这个文件中,再由zygote预加载。详细信息可以参考preload工具的说明。

    至于preloadResources,它与preloadClasses类似,它主要加载frameword-res.apk中的资源——在UI编程中常用到的com.android.R.xxx是系统默认资源,它们就是由zygote在这里加载的。

    3、启动system_server

    现在开始分析第三个关键函数startSystemServer。这个函数会创建JAVA世界中系统server所驻留的进程system_server,该进程是framework的核心

    private static boolean startSystemServer()

                throws MethodAndArgsCaller, RuntimeException {

            /** Hardcoded command line to start the system server */

            String args[] = {

                "--setuid=1000",Class.forName(line);

                "--setgid=1000",       "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003",

                "--capabilities=130104352,130104352",

                "--runtime-init",

                "--nice-name=system_server",

                "com.android.server.SystemServer",

            };

            ZygoteConnection.Arguments parsedArgs = null;

            int pid;

            try {

            //将args转换成Arguments类对象

                parsedArgs = new ZygoteConnection.Arguments(args);

                int debugFlags = parsedArgs.debugFlags;

                if ("1".equals(SystemProperties.get("ro.debuggable")))

                    debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;

                /*fork一个子进程,这个子进程就是system_server进程 */

                pid = Zygote.forkSystemServer(

                        parsedArgs.uid, parsedArgs.gid,

                        parsedArgs.gids, debugFlags, null);

            } catch (IllegalArgumentException ex) {

                throw new RuntimeException(ex);

            }

            /** For child process */

            if (pid == 0) {

                handleSystemServerProcess(parsedArgs); //system_server进程的工作。

            }

            return true;

        }

    handleSystemServerProcess的详细代码如下:

    private static void handleSystemServerProcess(

                ZygoteConnection.Arguments parsedArgs)

                throws ZygoteInit.MethodAndArgsCaller {

            /*首先设置兼容性*/

            if (parsedArgs.uid != 0) {

                try {

                    setCapabilities(parsedArgs.permittedCapabilities,

                                    parsedArgs.effectiveCapabilities);

                } catch (IOException ex) {

                    Log.e(TAG, "Error setting capabilities", ex);

                }

            } 

           //然后关闭该服务的socket

            closeServerSocket();

            /**

             * 将主要的参数传递给SystemServer.

             * "--nice-name=system_server com.android.server.SystemServer"

             */

            RuntimeInit.zygoteInit(parsedArgs.remainingArgs);

        }

    从上面的代码可以看出,zygote进行了一次fork,创建了一个子进程system_server进程(代码中的zygote.forkSystemServer),该进程的具体作用后面再讲,下面继续分析zygote进程。

    4、runSelectLoop分析

    当zygote从startSystemServer返回后,就进入第四个关键函数:runSelectLoop。

    回想在前面的第一个关键点registerZygoteSocket中注册了一个用于IPC的socket,并提出了两个问题,第二个问题就是:服务端如何处理请求。答案就在这个函数中:

    private static void runSelectLoop() throws MethodAndArgsCaller {

            ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();

            ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

            FileDescriptor[] fdArray = new FileDescriptor[4];

       /* sServerSocket 是我们在前面的registerZygoteSocket中建立的socket*/

            fds.add(sServerSocket.getFileDescriptor());

            peers.add(null);

            int loopCount = GC_LOOP_COUNT;

            while (true) {

                int index;

                if (loopCount <= 0) {

                    gc();

                    loopCount = GC_LOOP_COUNT;

                } else {

                    loopCount--;

                }

                try {

                    fdArray = fds.toArray(fdArray);

           /* selectReadable 是一个NATIVE函数,使用多路复用I/O模型。

    当有客户端连接或有数据时,该函数就返回。新的客户端连接上返回0,已连接上的客户端发送数据则返回>0,错误返回-1*/

                    index = selectReadable(fdArray);

                } catch (IOException ex) {

                    throw new RuntimeException("Error in select()", ex);

                }

                if (index < 0) {

                    throw new RuntimeException("Error in select()");

                } else if (index == 0) {

    /*有一个客户端连接上。注意,客户端在zygote的代表是zygoteConnection*/

                    ZygoteConnection newPeer = acceptCommandPeer();

                    peers.add(newPeer);

                    fds.add(newPeer.getFileDesciptor());

                } else {

                    boolean done;

    /*客户端发送了请求,peers.get返回zygoteConnection类对象,然后调用该类的runOnce函数来处理客户端请求*/

                    done = peers.get(index).runOnce();

                    if (done) {

    /*表示客户端的请求已经得到解决,服务器就删除该连接*/

                        peers.remove(index);

                        fds.remove(index);

                    }

                }

            }

        }

    总结runSelectLoop:

    ①处理客户端的连接和请求。客户端在zygote中使用zygoteConnection对象表示。

    ②客户端的请求有zygoteconnection的runOnce函数处理。该函数实现代码较为复杂,主要原理是:从socket命令行里面读取一个开始命令。如果读取成功,就fork一个子进程并在这个子进程中抛出zygoteInit.MethodAndArgsCaller异常,而父进程中就正常返回;如果读取失败,那么就不会fork子进程,而是log出错误消息。至于父进程的返回值也有两种:如果在socket命令中读取到了一个EOF标志,就返回true值,如果还有命令可以读取,就返回false值——这个返回值就是标记客户端请求是否完成的~

    1.6 zygote的总结

    Zygote是Android系统中创建JAVA世界的盘古——它创建了第一个JAVA虚拟机;它也是女娲——它成功的fork了framework的核心system_server进程。现在回顾一下Zygote创建JAVA世界的步骤:

    ①创建AppRuntime对象,并调用它的start方法。此后的活动均由AppRuntime控制。

    ②调用startVM创建虚拟机,然后调用startReg来注册JNI函数。

    ③通过JNI调用com.android.internal.os.ZygoteInit类的main函数,从此进入了JAVA世界。只不过这个世界刚创造,里面什么东西都没有。

    ④调用registerZygoteSocket函数,这个函数相应子孙后代的请求,同时zygote调用preloadClass,preloadResource,preloadOpenGL为JAVA世界添砖加瓦。

    ⑤zygote觉得自己的工作量有点大,就fork了一个进程system_server来为java世界服务。

    ⑥zygote完成了Java世界的初创工作,它有点累了。以后的工作就由runSelectLoop函数来处理,zygote睡觉去了。

    ⑦以后的日子:zygote随时守护在我们的周围,当接收到子孙后代的请求时,它随时醒来,为它们工作。

  • 相关阅读:
    流形学习(Mainfold Learning)
    陈皓的博客
    背包九讲
    阮一峰的个人网站
    PyTorch教程【六】Transforms的使用
    PyCharm设置代码提示忽略大小写
    PyTorch教程【五】TensoBoard的使用
    pip 换源
    PyCharm常用快捷键
    JAVA基础篇—HashMap
  • 原文地址:https://www.cnblogs.com/wanyuanchun/p/3746658.html
Copyright © 2020-2023  润新知