【Android面试题】Android Framework核心面试题——Zygote进程的启动流程

Zygote进程的启动流程

这道题想考察什么?

这道题想考察同学对Zygote进程的了解。

考生应该如何回答

zygote进程是由init进程启动的,init进程通过解读init.rc文件的方式启动了zygote,但是zygote所涉及的内容非常多,我们需要一步一步的分析它的细节。

1.Zygote是什么

Zygote是孵化器,在init进程启动时创建的,Zygote通过fork(复制进程)的方式创建android中几乎所有的应用程序进程和SystemServer进程。另外在Zygote进程启动的时候会创建DVM或者ART虚拟机,因此使用fork而建立的应用程序进程和SystemServer进程可以在内部得到一个DVM或者ART的实例副本,这样就保障了每个app进程在Zygote fork的那一刻就有了虚拟机。

2.Zygote启动脚本

init.rc文件中采用了如下所示的Import类型语句来导入Zygote启动脚本:import /init.${ro.zygote}.rc
这里根据属性ro.zygote的内容来导入不同的Zygote启动脚本。从Android 5.0开始,Android开始支持64位程序,Zygote有了32/64位之别,ro.zygote属性的取值有4种:

  • init.zygote32.rc
  • init.zygote64.rc
  • init.zygote64_32.rc

注意:在system/core/rootdir目录中存放上面的Zygote的启动脚本。
上述脚本文件中的部分代码如下:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main

意思就是,执行 app_process64,该程序的路径是 /system/bin/app_process64,类名是 main,进程名为 zygote。

3.Zygote进程启动流程

1.上述init脚本实际执行的是 /frameworks/base/cmds/app_process/app_main.cpp 中的 main 方法。
2.启动 zygote 进程在main 方法中会执行。

runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

3.runtime.start 实际执行的是 /frameworks/base/core/jni/AndroidRuntime.cpp 的 start 方法

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    // 1.启动 java 虚拟机
    if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
        return;
    }
    onVmCreated(env);

    // 2.为Java虚拟机注册JNI方法
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

    // 3.classNameStr是传入的参数,值为com.android.internall.os.ZygoteInit
    classNameStr = env->NewStringUTF(className);
    // 4.使用toSlashClassName函数将className的 "." 替换为 "/",得到 com/android/internal/os/ZygoteInit
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
    } else {
        // 5.找到 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);
        } else {
            // 6.使用JNI调用ZygoteInit的main函数
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
        }
    }
}

通过上面的代码我们不难发现,Zygote在启动过程中分别做了以下几件事:1)启动java虚拟机;2)注册android SDK需要使用的JNI方法;3)执行ZygoteInit的main函数,进而启动zygote的java层代码,最终Zygote就从Native层进入了Java FrameWork层。

特别说明

启动java虚拟机和 注册JNI的过程是zygote进程存在的一个非常关键的原因。正是因为zygote做了这样的事,因此它被用来fork其他进程。

到目前为止,并没有任何代码进入Java FrameWork层面,因此可以认为,Zygote开创了Java FrameWork层。进入java层代码后,main函数的实现如下所示:

@UnsupportedAppUsage
    public static void main(String argv[]) {
        // Server socket class for zygote processes.
        ZygoteServer zygoteServer = null; //用来管理和子进程通信的socket服务端 

        // Mark zygote start. This ensures that thread creation will throw
        // an error.

        ZygoteHooks.startZygoteNoThreadCreation();  //这里其实只是设置一个标志位,为创建Java线程时做判断处理,如果是zygote进程,则不需要开启线程

        // Zygote goes into its own process group.
        try {
            Os.setpgid(0, 0);  //为zygote进程设置pgid(Process Group ID),详见:`https://stackoverflow.com/questions/41498383/what-do-the-identifiers-pid-ppid-sid-pgid-uid-euid-mean`
        } catch (ErrnoException ex) {
            throw new RuntimeException("Failed to setpgid(0,0)", ex);
        }

        Runnable caller;
        try {
            // Report Zygote start time to tron unless it is a runtime restart
            if (!"1".equals(SystemProperties.get("sys.boot_completed"))) { //获取系统属性,判断系统重启完成
                MetricsLogger.histogram(null, "boot_zygote_init",
                        (int) SystemClock.elapsedRealtime());
            }

            String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";//判断当前进程是64位程序还是32位程序,并设置标记
            TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,
                    Trace.TRACE_TAG_DALVIK);
            bootTimingsTraceLog.traceBegin("ZygoteInit");
            RuntimeInit.enableDdms();//注册到ddms服务端,内部调用`DdmServer.registerHandler()`

            boolean startSystemServer = false;
            String zygoteSocketName = "zygote";
            String abiList = null;
            boolean enableLazyPreload = false;
            //对参数进行解析
            for (int i = 1; i < argv.length; i++) {
                if ("start-system-server".equals(argv[i])) { //参数重包含`start-system-server`
                    startSystemServer = true; //设置标志为位true
                } else if ("--enable-lazy-preload".equals(argv[i])) {
                    enableLazyPreload = true;
                } else if (argv[i].startsWith(ABI_LIST_ARG)) { //获取支持的架构列表
                    abiList = argv[i].substring(ABI_LIST_ARG.length());
                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
                    zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());
                } else {
                    throw new RuntimeException("Unknown command line argument: " + argv[i]);
                }
            }
            
            final boolean isPrimaryZygote =  zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);//根据socketName判断是否是primaryZygote,可能还有secondZygote

            if (abiList == null) { //如果支持架构为空,直接抛出异常
                throw new RuntimeException("No ABI list supplied.");
            }

            // In some configurations, we avoid preloading resources and classes eagerly.
            // In such cases, we will preload things prior to our first fork.
            if (!enableLazyPreload) {
                bootTimingsTraceLog.traceBegin("ZygotePreload");
                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                        SystemClock.uptimeMillis());
                preload(bootTimingsTraceLog); //加载各种系统res资源,类资源
                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                        SystemClock.uptimeMillis());
                bootTimingsTraceLog.traceEnd(); // ZygotePreload
            } else {
                Zygote.resetNicePriority();
            }

            // Do an initial gc to clean up after startup
            bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
            gcAndFinalize(); //调用ZygoteHooks.gcAndFinalize()进行垃圾回收
            bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC

            bootTimingsTraceLog.traceEnd(); // ZygoteInit
            // Disable tracing so that forked processes do not inherit stale tracing tags from Zygote.
            Trace.setTracingEnabled(false, 0);


            Zygote.initNativeState(isPrimaryZygote);//jni调用初始化zygote的状态,是否为isPrimaryZygote

            ZygoteHooks.stopZygoteNoThreadCreation(); //结束zygote创建,其实内部是调用`runtime`给`zygote_no_threads_`赋值为false,为创建本地线程做准备

            zygoteServer = new ZygoteServer(isPrimaryZygote); //创建zygoteServer,为其他进程初始化创建时与zygote通信做准备

            if (startSystemServer) { //判断是否需要startSystemServer
                Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);//通过fork的方式开启zygote的子进程,systemServer,并返回一个Runnale对象
                // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
                // child (system_server) process.
                if (r != null) {//如果是zygote进程,则r==null,如果不是zygote进程,也就是systemServer进程,则执行下面的代码
                    r.run();
                    return;
                }
            }

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

            // The select loop returns early in the child process after a fork and
            // loops forever in the zygote.
            caller = zygoteServer.runSelectLoop(abiList); //zygote进程进入死循环中,来获取子进程发送的消息
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
        } finally {
            if (zygoteServer != null) {
                zygoteServer.closeServerSocket(); //如果发生异常,则说明zygote初始化失败,zygoteServer也需要关闭
            }
        }

        // We're in the child process and have exited the select loop. Proceed to execute the command.
        if (caller != null) {
            caller.run();
        }
    }

zygote java层的大概流程我们已经梳理完了,现在我们来总结一下:

  1. 基于传参解析对应的zygote.rc脚本,设置进程名为zygote等信息。
  2. 创建ZygoteServer ,这个server存在的目的是让zygote接收来自socket的请求,进而fork进程,zygoteServer里面封装了socket。
  3. preload(bootTimingsTraceLog):加载系统资源res,加载 Android sdk class资源 和其他libc。
  4. forkSystemServer,就是fork 产生了SystemServer进程
  5. 调用runSelectionLoop(),接收其他进程发送的socket消息,进而创建子进程。

特别说明

调用preload完成系统资源的预加载,主要包括preloadClasses,preloadResources,preloadDrawables,preloadSharedLibraries。这些资源并不是给zygote自己实际显示使用的,它之所以存在,是为后面fork 出来的App去运行而准备的。因为app 进程需要运行就需要framework 层的系统资源,而这些资源就会在这里得到加载。

总结

zygote进程的启动分为两大部分,第一部分:执行Native层面的代码,这个过程主要包含:虚拟机启动,JNI资源函数的注册,启动zygote的java层;第二部分:执行java层面的代码,这个过程主要包含:zygote脚本解读,加载公用的各种资源,创建socket服务器并在runSelectionLoop中死循环等待socket消息,fork 了systemServer进程等操作。

最后

有需要面试题的朋友可以关注一下哇哇,以上都可以分享!!!

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

推荐阅读更多精彩内容