Android 系统启动 - Zygote 进程

前言

前面我们在 Android 系统启动 - init 进程 中提到,init 进程在解析完 init.rc 脚本后,会启动 Zygote 进程。具体来讲,Zygote 是以服务(service)的形式配置在 init.rc 中,其对应的可执行文件为 /system/bin/app_processXX。init 进程会通过 fork 为每个服务创建一个独立的进程。对于 Zygote 来说, /system/bin/app_processXX 就会被启动,然后该进程内部最终会通过 AppRuntime.start 真正启动 Zygote 进程。

接下来,我们就来分析下 Zygote 具体的启动过程:

Zygote 启动过程

我们先来看下启动 Zygote 服务源码:
framework/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{
    ···
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } 
    ···
}

看到调用的是 runtime.start("com.android.internal.os.ZygoteInit", args, zygote);,看到 "com.android.internal.os.ZygoteInit"这种包名形式,就大概知道 runtime.start 该函数内部应该会通过 JNI 来调用 Java 层的类了。究竟是不是,我们进入看一下:
frameworks/base/core/jni/AndroidRuntime.cpp

/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
   ···
    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    // 创建 Java 虚拟机
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     */
    // JNI 方法注册
    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;

    // 相当于 strArray= new String[options.size() + 1];
    stringClass = env->FindClass("java/lang/String");
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);

    // 相当于 strArray[0] = "com.android.internal.os.ZygoteInit"
    classNameStr = env->NewStringUTF(className);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    // 等价 strArray[1] = "start-system-server";
    // strArray[2] = "--abi-list=xxx";
    // 其中xxx为系统响应的cpu架构类型,比如arm64-v8a.
    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.
     */
    // 将"com.android.internal.os.ZygoteInit"转换为"com/android/internal/os/ZygoteInit"
    char* slashClassName = toSlashClassName(className);
    // 找到类 "com/android/internal/os/ZygoteInit"
    jclass startClass = env->FindClass(slashClassName);
    ···
    // 找到类 "com/android/internal/os/ZygoteInit"的静态方法main(String[])
    jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
        "([Ljava/lang/String;)V");
    ···
    // 调用 ZygoteInit.main(String[])
    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");
}

该方法主要就是启动 Android 运行时,主要做了两件事:创建虚拟机 startVm 和 注册 JNI 方法 startReg 并启动 ZygoteInit.main 函数。

到这里,Zygote 服务进程通过 JNI 反射调用 Java 层代码,正式将 Android 运行环境从 c++ 层转移到 Java 层。Zygote 进程也同时成为 Android 系统第一个 Java 进程。

那我们接下来看看 ZygoteInit.main 函数源码:
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

public static void main(String argv[]) {
    try {
        // 开启DDMS功能
        RuntimeInit.enableDdms();
        ···
        // 为Zygote注册Socket
        registerZygoteSocket(socketName);
        ···
        // 预加载类和资源
        preload();
        ···
        // Do an initial gc to clean up after startup
        // GC 释放资源
        gcAndFinalize();
        ···
        if (startSystemServer) {
            // 启动 SystemServer 进程
            startSystemServer(abiList, socketName);
        }
        // 进入监听状态,等待客户端请求
        runSelectLoop(abiList);
        // 关闭Socket
        closeServerSocket();
    } catch (MethodAndArgsCaller caller) {
        caller.run(); // 真正启动 SystemServer
    } catch (RuntimeException ex) {
        Log.e(TAG, "Zygote died with exception", ex);
        // 关闭Socket
        closeServerSocket();
        throw ex;
    }
}

可以看到,ZygoteInit.main 方法主要做了四件事:

  • 为 Zygote 进程注册一个服务端 Socket:registerZygoteSocket
  • 预加载类和资源:preload
  • 启动 SystemServer 进程:startSystemServer
  • 开启 Socket 监听,等待客户端请求:runSelectLoop

下面我们依次分析上述四个操作:

首先来看下 Zygote 注册服务端 Socket 具体过程:registerZygoteSocket
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

private static void registerZygoteSocket(String socketName) {
        if (sServerSocket == null) {
            int fileDesc;
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                // 从环境变量中获取Socket套接字描述符
                String env = System.getenv(fullSocketName);
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }

            try {
                FileDescriptor fd = new FileDescriptor();
                fd.setInt$(fileDesc); // 设置文件描述符
                sServerSocket = new LocalServerSocket(fd);// 创建Socket的本地服务端 
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }

其实就是从环境变量中获取 Socket 套接字文件描述符(此处的 socket 是由 service_start 创建的,并且创建完后会通过 publish_socket 方法发布出去,发布的途径就是设置到环境变量中),然后创建 Socket 本地服务端。

接下来看下 Zygote 预加载类和资源的具体过程:preload
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

static void preload() {
        Log.d(TAG, "begin preload");
        // 预加载常用类:/system/etc/preloaded-classes
        preloadClasses();
        // 预加载资源文件,包含drawable和color资源
        preloadResources();
        // 预加载OpenGL
        preloadOpenGL();
        // 预加载共享库:"android","compiler_rt" 和 "jnigraphics"
        preloadSharedLibraries();
        // 预加载文本连接符资源
        preloadTextResources();
        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
        // for memory sharing purposes.
        // 为了达到内存共享,WebViewFactory 需要进行的初始化操作
        WebViewFactory.prepareWebViewInZygote();
        Log.d(TAG, "end preload");
    }

该方法没有太多好说的,就是预加载了一些常用类库和资源文件等,这样后续创建新的虚拟机进程时,直接 fork Zygote 进程,就无需再次预加载这些资源了,节省时间,使虚拟机能更快启动。

ZygoteInit.main 第三个主要操作就是启动 SystemServer 进程,该部分详细内容请查看:Android 系统启动 - SystemServer 进程

最后我们来看下 Zygote 进程实现响应客户端请求的具体过程:
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

/**
     * Runs the zygote process's select loop. Accepts new connections as
     * they happen, and reads commands from connections one spawn-request's
     * worth at a time.
     *
     * @throws MethodAndArgsCaller in a child process when a main() should
     * be executed.
     */
    private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        // sServerSocket就是Zygote自己创建的Socket:registerZygoteSocket
        // 即 zygote 的 socket 通信服务端,保存到fds[0
        fds.add(sServerSocket.getFileDescriptor());
        peers.add(null);

        while (true) {
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            }
            try {
                // 处理轮询状态,当pollFds有事件到来时则往下执行,否则阻塞
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            for (int i = pollFds.length - 1; i >= 0; --i) {
                // 采用I/O多路复用机制,当接收到客户端发出连接请求 或者 数据处理请求到来,则往下执行
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) {
                    // 即fds[0],代表的是sServerSocket,意味着有客户端连接请求,则会创建ZygoteConnection对象,并添加到fds。
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    // i>0,说明接收到一个创建应用进程的请求,则调用ZygoteConnection的runOnce函数来创建一个新的应用程序进程。
                    boolean done = peers.get(i).runOnce();
                    if (done) {
                        peers.remove(i);
                        fds.remove(i);
                    }
                }
            }
        }
    }

该方法采用 I/O 多路复用机制,高效地实现了 Zygote 进程的 Socket 监听。当有客户端的连接请求或数据处理时,则响应客户端请求。特别的,当接收到客户端请求创建应用进程时,就会调用 ZygoteConnection.runOnce 方法创建一个新的应用进程,具体的进程创建方法,请查看:理解Android进程创建流程

至此,Zygote 进程的启动流程分析结束。

总结

Zygote 进程是由 init 进程启动的,其对应的可执行文件为 /system/bin/app_processXX,其内部最终会通过 AppRuntime.start方法启动 Zygote 进程:具体来说,该方法内部会创建 Java 虚拟机(startVm) 和 注册 JNI 本地方法(startReg),最后通过 JNI 调用启动 Java 层 Zygote 进程(ZygoteInit.main),从而让 Android 系统正式开启第一个 Java 进程,即 Zygote。

Java 框架层的 Zygote 主要做的就是:创建服务端 Socket 并进行监听(registerZygoteSocketrunSelectLoop),预加载常用类和资源,启动 SystemServer 进程。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258