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.\n");

        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'\n", 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'\n", 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\n");

        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 ---\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是一个全局大数组,每个元素对应了一个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\n", 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随时守护在我们的周围,当接收到子孙后代的请求时,它随时醒来,为它们工作。

 

posted @ 2014-05-22 22:48  WanChouchou  阅读(932)  评论(0编辑  收藏  举报