现在的位置: 首页 > 移动开发 > 正文

Android情景分析之深入解析zygote

2019年07月27日 移动开发 ⁄ 共 15313字 ⁄ 字号 评论关闭
文章目录

概述

我们都知道,Android系统存在着两个完全不同的世界:

1.      Java世界,Google提供的SDK编写出来的程序大部分都是针对这个世界的。在这个世界中运行的程序都是基于Dalvik虚拟机的java程序。

2.      Native世界,也就是用Native语言C或者C++开发的程序,例如采用NDK开发的程序。

于是便有了这个疑问:

Android系统是基于linux内核构建的,那么最早肯定是native世界,可java世界是什么时候开始被创建出来的呢?

带着这个疑问,我们今天就来剖析下zygote进程的实现原理。

zygote分析

zygote本身是一个Native的应用程序,在前面对init进程的分析中,我们知道,zygote是系统在刚开始初始化时,init进程根据init.rc配置文件fork出来的子进程,映像路径为/system/bin/app_process。所以,zygote最初的名字叫“app_process”,在程序运行过程中,app_process通过Linux下的prctl系统调用将自己的名字改成了“zygote”,所以我们通过ps命令看到的进程名是“zygote”。

Zygote的原型app_process所对应的源文件为frameworks\base\cmds\app_process\App_main.cpp。相应main函数如下:

int main(int argc, char* const argv[])
{
	…..
/*
	Init.rc中的zygote启动参数:
-Xzygote /system/bin --zygote --start-system-server
*/
    mArgC = argc;
    mArgV = argv;

    mArgLen = 0;
    for (int i=0; i<argc; i++) {
        mArgLen += strlen(argv[i]) + 1;
    }
    mArgLen--;
	
	// 定义一个AppRuntime的对象runtime
    AppRuntime runtime;
    const char* argv0 = argv[0];

    argc--;
    argv++;
	
// 添加vm启动参数
    int i = runtime.addVmArguments(argc, argv);

    // 开始解析运行时参数
    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;	// parentDir = /system/bin
        } else if (strcmp(arg, "--zygote") == 0) {
            zygote = true;		// zygote确实也为true
            niceName = "zygote";
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;	// 为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);
		// 通过prctl系统调用把自己的名字改成zygote
        set_process_name(niceName);
    }

runtime.mParentDir = parentDir; 

if (zygote) {
	/*
		调用runtime的start函数,startSystemServer为true
		参数1:“com.android.internal.os.ZygoteInit”
		参数2:“start-system-server”
	*/
        runtime.start("com.android.internal.os.ZygoteInit",
                startSystemServer ? "start-system-server" : "");
    } else if (className) {
        ……
} 
……
}

zygote的main函数主要做了两件事情:

1.      app_process通过prctl系统调用把自己的名字改成zygote

2.      AppRuntime通过start函数接着启动system_server

AppRuntime分析

接着来看看AppRuntime的实现。

class AppRuntime : public AndroidRuntime
{
	……
}

AppRuntime是继承自AndroidRuntime的,主要重载了onVmCreated、onStarted、onZygoteInit和onExit函数。前面的代码runtime.start(…),这个start函数使用的是基类AndroidRuntime的start,我们来分析一下它。

// 参数1:“com.android.internal.os.ZygoteInit”
// 参数2:“start-system-server”
void AndroidRuntime::start(const char* className, const char* options)
{
……

	// 设置环境变量ANDROID_ROOT为"/system"
    const char* rootDir = getenv("ANDROID_ROOT");
    if (rootDir == NULL) {
        rootDir = "/system";
        if (!hasDir("/system")) {
            LOG_FATAL("No root directory specified, and /android does not exist.");
            return;
        }
        setenv("ANDROID_ROOT", rootDir, 1);
}

    /* start the virtual machine */
    JNIEnv* env;
    if (startVm(&mJavaVM, &env) != 0) {
        return;
    }
……

    /* Register android functions. */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

    /*
     * 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;
    jstring optionsStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(2, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);
    optionsStr = env->NewStringUTF(options);
    env->SetObjectArrayElement(strArray, 1, optionsStr);

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
	/*
		className本身为com.android.internal.os.ZygoteInit
		toSlashClassName函数将className的.转换成为/,于是
		转换之后为:com/android/internal/os/ZygoteInit
*/
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
} else {
	// 找到ZygoteInit类的main函数
        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 {
		/*
调用ZygoteInit类的main函数,此时将转到java世界开始执行
所以zygote进程相当于开创java世界的盘古
		*/
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    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");
}

上面的代码逻辑不难,但是要从native层进入到java层开始执行需要煞费一番苦心,我们一一来分析。

创建虚拟机 - startVm

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
{
……
    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;
        }
    }

   …..

    /*
     * The default starting and maximum size of the heap.  Larger
     * values should be specified in a product property override.
     */
    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);

    // Increase the main thread's interpreter stack size for bug 6315322.
    opt.optionString = "-XX:mainThreadStackSize=24K";
    mOptions.add(opt);

    ……

    /*
     * 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.
     */
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        goto bail;
    }

    result = 0;

bail:
    free(stackTraceFile);
    return result;
}

这个函数主要是根据init进程初始化的时候解析的属性配置文件得到的数据来进行初始化VM虚拟机,包括设置jni check、heap size等等属性。最后,通过JNI_CreateJavaVM函数创建虚拟机。代码注释中写的也很清楚了,返回值pJavaVM一般是针对整个进程的,而pEnv是针对每个线程的。

注册JNI函数– startReg

前面已经调用startVm函数来创建虚拟机,下一步则需要给整个虚拟机注册一些常用的JNI函数。正是因为后续java世界用到的一些函数是采用native方式实现的,所以才需要提前注册这些函数。

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

    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
env->PopLocalFrame(NULL);

    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) {
            return -1;
        }
    }
    return 0;
}

上面的代码通过register_jni_procs函数注册JNI函数,其中gRegJNI是一个数组,如下:

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_debug_JNITest),
    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),
    REG_JNI(register_android_content_AssetManager),
REG_JNI(register_android_content_StringBlock),
……
}

看第一个例子吧:

int register_android_debug_JNITest(JNIEnv* env)
{
    return jniRegisterNativeMethods(env, "android/debug/JNITest",
        gMethods, NELEM(gMethods));
}

其实就是调用jniRegisterNativeMethods函数来注册android.debug.JNITest类的JNI函数。

进入Java世界

虚拟机创建完毕,JNI函数也注册好了,可以说是万事俱备只欠东风了。那么我们来看看,JAVA世界的入口到底在哪里呢?根据前面的分析可知,CallStaticVoidMethod函数最终会调用com.android.internal.os.ZygoteInit类的main函数,下面就来看看这个入口函数吧。

public static void main(String argv[]) {
        try {
            // Start profiling the zygote initialization.
            SamplingProfilerIntegration.start();
			// 注册zygote用的socket
            registerZygoteSocket();
            // 预加载资源和类
            preload();

            // Finish profiling the zygote initialization.
            SamplingProfilerIntegration.writeZygoteSnapshot();

            // Do an initial gc to clean up after startup
            gc();

            // Disable tracing so that forked processes do not inherit stale tracing tags from
            // Zygote.
            Trace.setTracingEnabled(false);
            ……

            if (argv[1].equals("start-system-server")) {
                startSystemServer();
            } else if (!argv[1].equals("")) {
                throw new RuntimeException(argv[0] + USAGE_STRING);
            }

            runSelectLoop();	// 等待事件发生

            closeServerSocket();
        } catch (MethodAndArgsCaller caller) {
            caller.run(); // 这个很重要,后面会分析到
        } catch (RuntimeException ex) {
            Log.e(TAG, "Zygote died with exception", ex);
            closeServerSocket();
            throw ex;
        }
    }

建立IPC通信服务端– registerZygoteSocket
private static void registerZygoteSocket() {
        if (sServerSocket == null) {
            int fileDesc;
            try {
				/*
					    private static final String ANDROID_SOCKET_ENV = 
"ANDROID_SOCKET_zygote";
				*/
                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 {
                sServerSocket = new LocalServerSocket(
                        createFileDescriptor(fileDesc));
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }

从环境变量中获得名为“ANDROID_SOCKET_zygote”的值作为socket的fd,那么这个ANDROID_SOCKET_zygote环境变量又是什么时候设置的呢?

还记得在init进程初始化的时候会去解析init.rc的service服务,并且最后会调用service_start去启动服务。那么service_start函数其中有如下一段代码,用来创建通信用的socket fd:

for (si = svc->sockets; si; si = si->next) {
    int socket_type = (
            !strcmp(si->type, "stream") ? SOCK_STREAM :
                (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
	/*
create_socket其实就是往ANDROID_SOCKET_DIR中创建了一个文件,zygote进程为
/dev/socket/zygote,并返回文件fd
	*/
    int s = create_socket(si->name, socket_type,
                          si->perm, si->uid, si->gid);
if (s >= 0) {
	// zygote服务的si->name即socket名也为zygote
        publish_socket(si->name, s);
    }
}

其中publish_socket函数就是设置ANDROID_SOCKET_zygote环境变量的地方:

static void publish_socket(const char *name, int fd)
{
	/*
		#define ANDROID_SOCKET_ENV_PREFIX	"ANDROID_SOCKET_"
#define ANDROID_SOCKET_DIR		"/dev/socket"
	*/
    char key[64] = ANDROID_SOCKET_ENV_PREFIX;
    char val[64];
	
	// 拼接socket的名字,即ANDROID_SOCKET_zygote
    strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1,
            name,
            sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX));
snprintf(val, sizeof(val), "%d", fd);
// 添加socket的fd值到环境变量
    add_environment(key, val);

    /* make sure we don't close-on-exec */
    fcntl(fd, F_SETFD, 0);
}

知道了ANDROID_SOCKET_ENV的来龙去脉,那么我们接着回过头来看registerZygoteSocket函数,这个函数其实很简单,就是创建了一个服务端的socket,并赋值给全局变量sServerSocket。那么这个服务端的socket具体要接受什么消息,之后又需要处理什么事件呢?我们后面再来看。

预加载类、资源等
static void preload() {
	preloadClasses();
	preloadResources();
	preloadOpenGL();
}

先来看看类加载:

private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();
		
		// private static final String PRELOADED_CLASSES = "preloaded-classes";
		// 预加载类的信息存储在PRELOADED_CLASSES文件中
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(
                PRELOADED_CLASSES);
        if (is == null) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
        } else {
            ……

            try {
                BufferedReader br
                    = new BufferedReader(new InputStreamReader(is), 256);

                int count = 0;
                String line;
                while ((line = br.readLine()) != null) {
                    // Skip comments and blank lines.
                    line = line.trim();
                    if (line.startsWith("#") || line.equals("")) {
                        continue;
                    }

                    try {
                        ……
						// 通过Java反射来加载类,line为类名
                        Class.forName(line);
                        ……
                        count++;
                    } catch (ClassNotFoundException e) {
                        Log.w(TAG, "Class not found for preloading: " + line);
                    } catch (Throwable t) {
                        ……
                    }
                }
            } catch (IOException e) {
                Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
            } finally {
                ……
            }
        }
}

函数比较简单,就是读取preloaded-classes文件的内容,以行为单位。preloaded-classes文件的内容如下:

# Classes which are preloaded by com.android.internal.os.ZygoteInit.
# Automatically generated by frameworks/base/tools/preload/WritePreloadedClassFile.java.
# MIN_LOAD_TIME_MICROS=1250
# MIN_PROCESSES=10
android.R$styleable
android.accounts.Account
android.accounts.Account$1
android.accounts.AccountManager
android.accounts.AccountManager$12
android.accounts.AccountManager$13
android.accounts.AccountManager$6
android.accounts.AccountManager$AmsTask
android.accounts.AccountManager$AmsTask$1
android.accounts.AccountManager$AmsTask$Response
android.accounts.AccountManagerFuture
android.accounts.IaccountManager
……
// 省略N行

preloaded-classes这个文件主要由frameworks\base\tools\preload工具自动生成的,工具会判断每个类加载的时间是否大于1250微妙,超过这个时间就会被写到preloaded-classes文件中,最后由zygote来预加载。它总共有1000多行的类,可见Zygote进程初始化的时候任务还是很繁重的,这也是导致Android系统启动慢的原因之一。

preloadResources和preloadOpenGL我也就不多做解释了,大体处理都差不多。

启动system_server
/**
     * Prepare the arguments and fork for the system server process.
     */
    private static boolean startSystemServer()
            throws MethodAndArgsCaller, RuntimeException {
        long capabilities = posixCapabilitiesAsBits(
            OsConstants.CAP_KILL,
            OsConstants.CAP_NET_ADMIN,
            OsConstants.CAP_NET_BIND_SERVICE,
            OsConstants.CAP_NET_BROADCAST,
            OsConstants.CAP_NET_RAW,
            OsConstants.CAP_SYS_MODULE,
            OsConstants.CAP_SYS_NICE,
            OsConstants.CAP_SYS_RESOURCE,
            OsConstants.CAP_SYS_TIME,
            OsConstants.CAP_SYS_TTY_CONFIG
        );
        /* Hardcoded command line to start the system server */
        String args[] = {
            "--setuid=1000",	// #define AID_SYSTEM  1000
            "--setgid=1000",	// 1000 是系统属性
            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1032,3001,3002,3003,3006,3007",
            "--capabilities=" + capabilities + "," + capabilities,
            "--runtime-init",
            "--nice-name=system_server", 	// 进程名为system_server
            "com.android.server.SystemServer",	// 类名
        };
        ZygoteConnection.Arguments parsedArgs = null;

        int pid;

        try {
            parsedArgs = new ZygoteConnection.Arguments(args);
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

            /* 
				进程启动参数都已经在上面硬编码了
				最后调用fork启动system_server进程
 */
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids,
                    parsedArgs.debugFlags,
                    null,
                    parsedArgs.permittedCapabilities,
                    parsedArgs.effectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

        /* For child process */
        if (pid == 0) {
			// 子进程处理消息
            handleSystemServerProcess(parsedArgs);
        }

        return true;
}

系统初始化到这里之后,系统中已经有3个进程了,最开始的init,zygote和zygote分裂出来的system_server。system_server部门我们以后再看,接着看zygote的执行。

zygote等待请求– runSelectLoop
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;

            /*
             * Call gc() before we block in select().
             * It's work that has to be done anyway, and it's better
             * to avoid making every child do it.  It will also
             * madvise() any free memory as a side-effect.
             *
             * Don't call it every time, because walking the entire
             * heap is a lot of overhead to free a few hundred bytes.
             */
            if (loopCount <= 0) {
                gc();
                loopCount = GC_LOOP_COUNT;
            } else {
                loopCount--;
            }

            try {
                fdArray = fds.toArray(fdArray);
				/*
selectReadable是一个native函数,内部调用select等待客户端
					的连接,客户端连接上之后就会返回。
					返回值:
					<0: 	内部发生错误
					=0:	该客户端第一次连接到服务端
					>0:	客户端与服务端已经建立连接,并开始发送数据
				*/
                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) {
				/*
返回0,表明该客户端第一次请求服务端,服务端调用accept与客户
端建立连接。客户端在zygote中以ZygoteConnection对象表示。
				*/
                ZygoteConnection newPeer = acceptCommandPeer();
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
            } else {
                boolean done;
				/*
					返回>0,表明发送数据的客户端的index,peers.get(index)取得
					发送数据客户端的ZygoteConnection对象,之后调用runOnce
					函数处理具体的请求。
				*/
                done = peers.get(index).runOnce();
				// 请求处理完成之后,移除与该客户端的连接
                if (done) {
                    peers.remove(index);
                    fds.remove(index);
                }
            }
        }
}

runSelectLoop函数的逻辑比较简单,主要有两点

1、  处理客户端的连接和请求。其中客户端在zygote进程中使用ZygoteConnection对象表示。

2、  客户的请求有ZygoteConnection的runOnce来处理。

Zygote总结

Zygote是在android系统中创建java世界的盘古,它创建了第一个java虚拟机。同时,它又是女娲,它成功的繁殖了framework的核心system_server进程。主要步骤如下:

1、  创建AppRuntime对象,并调用其start函数。之后zygote的核心初始化都由AppRuntime中。

2、  调用startVm创建java虚拟机,然后调用startReg来注册JNI函数。

3、  通过JNI调用com.android.internal.os.ZygoteInit类的main函数,从此进入了java世界。

4、  调用registerZygoteSocket创建可以响应子孙后代请求的socket。同时,zygote调用preload函数预加载了常用的类、资源等,为java世界添砖加瓦。

5、  调用startSystemServer函数分裂了一个子进程system_server来为java世界服务。

6、  Zygote完成了java世界的初创工作,便调用runSelectLoop来让自己沉沉的睡去。之后,如果收到子孙后代的请求,它便会醒来为他们工作。

抱歉!评论已关闭.