Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Zygote进程

$
0
0

背景
从字面上看,zygote是受精卵的意思,它的主要工作就是进行细胞分裂。

在Android中,zygote的行为就如同受精卵一样,在系统发出请求时,负责分裂出其它的进程。

Android为什么要这么设计呢?

要知道从受精卵分裂出来的细胞,将继承受精卵的DNA。这些细胞只需要按照规则复制DNA就行了,而不需要重新思考DNA应该怎么排序。

大概也可以按照这个思路来理解zygote进程吧。

zygote进程启动时,将在内部启动Dalvik虚拟机,注册JNI函数,继而加载一些必要的系统资源和必要类等,然后进入到监听状态。

在后续的运作中,当其它系统模块希望创建新进程时,只需向zygote进程发出请求。zygote进程监听到该请求后,会相应地“分裂”出新的进程。

于是新创建出的进程,从一开始就具有了自己的Dalvik虚拟机以及一些必要的系统资源。这将减少每个进程启动消耗的时间。进一步来说,由于fork的copy-on-write策略,zygote的这种分裂方式还有可能减少系统整体内存的占用。

版本
android 6.0

主要流程分析
在分析init进程时,我们知道init进程会解析init.rc文件,然后加载对应的进程。zygote就是以这种方式,被init进程加载的。

在system/core/rootdir/init.rc中,可以看到:

import /init.${ro.zygote}.rc

从import的文件命名方式,我们可以看出在不同的平台(32、64及64_32)上,init.rc将包含不同的zygote.rc文件。不同的zygote.rc内容大致相同,主要区别体现在启动的是32位,还是64位的进程。

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

以上是init.zygote32.rc中的内容。

从init.zygote32.rc可以看出,zygote实际上对应于app_process进程,对应源文件为frameworks/base/cmds/app_process/app_main.cpp,其main函数对应的传入参数为:–zygote, –start-system-server 。

这里需要补充说明的是:
1、在init.zygote64.rc中,启动的进程为app_process64;在init.zyote64_32.rc中,会启动两个进程,分别为app_process64和app_process32(目前,不太清楚这种设置的实际含义)。
2、从zyote的rc文件,我们可以看到zygote启动的时候,创建了一个socket(后文将介绍这个socket)。

接下来我们按照zygote进程启动涉及文件的先后顺序,一起来看看zygote的主要工作情况。

1、app_main.cpp
首先从入口文件app_main.cpp的main函数开始分析。

int main(int argc, char* const argv[])
{
    //AppRuntime定义于app_main.cpp中,继承自AndroidRuntime
    //个人感觉该对象就是对Android运行时环境的一种抽象
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    ..........
    //开始解析输入参数
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            //init.zygote.rc中定义了该字段
            zygote = true;
            //记录app_process进程名的nice name,即zygote
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            //init.zygote.rc中定义了该字段
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }
    ..........
    //准备下一个函数调用所需的参数
    Vector<String8> args;
    if (!className.isEmpty()) {
        //启动zygote时,class name is empty,不进入该分支
        ........
    } else {
        //创建dalvikCache所需的目录,并定义权限
        maybeCreateDalvikCache();

        if (startSystemServer) {
            //增加参数
            args.add(String8("start-system-server"));
        }
        .........
        for (; i < argc; ++i) {
            //将main函数未处理的参数都递交给下个调用函数处理
            args.add(String8(argv[i]));
        }
    }

    if (!niceName.isEmpty()) {
        //将app_process的进程名,替换为zygote
        runtime.setArgv0(niceName.string());
        set_process_name(niceName.string());
    }

    if (zygote) {
        //调用Runtime的start函数
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        //启动zygote没有进入这个分支,但这个分支说明,通过配置init.rc文件,其实是可以不通过zygote来启动一个进程
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        ...............
    }
}

由于AppRuntime继承自AndroidRuntime,且没有重写start方法,因此zygote的流程进入到了AndroidRuntime.cpp。

2、AndroidRuntime.cpp
我们一起来看看在AndroidRuntime的start函数中,进行了哪些重要操作:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) {
    .........
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    //创建虚拟机,其中大多数参数由系统属性决定
    //最终,startVm利用JNI_CreateVM创建出虚拟机
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    ...........
    //注册JNI函数
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }
    ......

==============================以下非主干部分============================

我们先来看看startReg具体是在干什么。

int AndroidRuntime::startReg(JNIEnv* env) {
    ..........
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    ........
}

从上述代码可以看出,startReg函数中主要是通过register_jni_procs来注册JNI函数。

其中,gRegJNI是一个全局数组,该数组的定义类似于:

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_util_SeempLog),
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_android_os_SystemClock),
    REG_JNI(register_android_util_EventLog),
    ............

其中,REG_JNI的宏定义及RegJNIRec结构体的定义为:

#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

根据宏定义可以看出,宏REG_JNI将得到函数名;定义RegJNIRec数组时,函数名被赋值给RegJNIRec结构体,于是每个函数名被强行转换为函数指针。

明白宏及数组的定义后,我们再回头来看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) {
#ifndef NDEBUG
            ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
            return -1;
        }
    }
    return 0;
}

结合前面的分析,容易知道register_jni_procs函数,实际上就是调用函数指针对应的函数,以进行实际的JNI函数注册。

我们随意举个例子,看看register_android_util_SeempLog被调用时的情况:

/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "seemp_println_native",  "(ILjava/lang/String;)I",
            (void*) android_util_SeempLog_println_native },
};

int register_android_util_SeempLog(JNIEnv* env)
{
    jclass clazz = env->FindClass("android/util/SeempLog");
    if (clazz == NULL) {
        return -1;
    }

    return AndroidRuntime::registerNativeMethods(env, "android/util/SeempLog", gMethods, NELEM(gMethods));
}

可以看到,这实际上是自己定义JNI函数并进行动态注册的标准写法。

int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

最后,还是利用jniRegisterNativeMethods进行实际的注册工作。

==============================以上非主干部分============================

在介绍完AndroidRuntime.cpp中注册JNI的工作后,我们将思路拉回到它的start函数。

    ........
    //将"com.android.internal.os.ZygoteInit"替换为"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函数
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
        ........

当调用ZygoteInit.java的main函数后,zygote进程进入了java世界。

其实我们仔细想一想,就会觉得zygote的整个流程实际上是非常符合实际情况的。

在Android中,每个进程都运行在对应的虚拟机上,因此zygote首先就负责创建出虚拟机。

然后,为了反射调用java代码,必须有对应的JNI函数,于是zygote进行了JNI函数的注册。

当一切准备妥当后,zygote进程才进入到了java世界。

3、ZygoteInit.java

 public static void main(String argv[]) {
    try {
        .........
        String socketName = "zygote";
        .........
        //注册server socket
        registerZygoteSocket(socketName);
        .........
        //预加载
        preload();
        .........
        if (startSystemServer) {
            //启动system server
            startSystemServer(abiList, socketName);
        }

        //zygote进程进入无限循环,处理请求
        runSelectLoop(abiList);

        closeServerSocket();
    } catch (MethodAndArgsCaller caller) {
        //通过反射调用新进程函数的地方
        //后续介绍新进程启动时,再介绍
        caller.run();
    } catch (RuntimeException ex) {
        closeServerSocket();
        throw ex;
    }
}

上面是ZygoteInit的main函数的主干部分,接下来我们进一步分析它们的主要工作。

3.1 创建server socket

private static void registerZygoteSocket(String socketName) {
    if (sServerSocket == null) {
        int fileDesc;
        //此处的socket name,就是zygote
        final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
        try {
            //记得么?在init.zygote.rc被加载时,就会创建一个名为zygote的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();
            //获取zygote socket的文件描述符
            fd.setInt$(fileDesc);
            //将zygote socket包装成一个server socket
            sServerSocket = new LocalServerSocket(fd);
        } catch (IOException ex) {
            throw new RuntimeException("Error binding to local socket '" + fileDesc + "'", ex);
        }
    }
}

zygote进程与系统中其它进程的通信没有使用Binder,而是采用了基于AF_UNIX类型的socket。(实际上,在这个时候Binder根本无法使用)。

3.2 预加载

static void preload() {
    Log.d(TAG, "begin preload");
    //读取文件framework/base/preloaded-classes,然后通过反射加载对应的类
    //需要加载数千个类,启动慢的原因之一
    preloadClasses();
    //负载加载一些常用的系统资源
    preloadResources();
    //图形相关的
    preloadOpenGL();
    //一些必要库
    preloadSharedLibraries();
    //好像是语言相关的字符信息
    preloadTextResources();
    // Ask the WebViewFactory to do any initialization that must run in the zygote process, for memory sharing purposes.
    WebViewFactory.prepareWebViewInZygote();
    Log.d(TAG, "end preload");
}

为了让系统实际运行时更加流畅,在zygote启动时候,调用preload函数进行了一些预加载操作。

Android 通过zygote fork的方式创建子进程。zygote进程预加载这些类和资源,在fork子进程时,仅需要做一个复制即可。

这样可以节约子进程的启动时间。同时,根据fork的copy-on-write机制可知,有些类如果不做改变,甚至都不用复制,子进程可以和父进程共享这部分数据,从而省去不少内存的占用。

3.3 启动SystemServer进程

private static boolean startSystemServer(String abiList, String socketName) {
    //准备capabilities参数
    ........
    String args[] = {
        "--setuid=1000",
        "--setgid=1000",
        "--setgroups=.........",
        "--capabilities=" + capabilities + "," + capabilities,
        "--nice-name=system_server",
        "--runtime-args",
        "com.android.server.SystemServer",
    };
    ZygoteConnection.Arguments parsedArgs = null;

    int pid;

    try {
        //将上面准备的参数,按照ZygoteConnection的风格进行封装
        parsedArgs = new ZygoteConnection.Arguments(args);
        ...........

        //通过fork"分裂"出system server,具体的过程在介绍system server时再分析
        /* Request to fork the system server process */
        pid = Zygote.forkSystemServer(
            parsedArgs.uid, parsedArgs.gid,
            parsedArgs.gids,
            parsedArgs.debugFlags,
            null,
            parsedArgs.permittedCapabilities,
            parsedArgs.effectiveCapabilities);
    } catch (IllegalArgumentException ex) {
        throw new RuntimeException(ex);
    }

    if (pid == 0) {
        ............
        //pid = 0, 在进程system server中
        //system server进程处理自己的工作
        handleSystemServerProcess(parsedArgs);
    }

    return true;
}

3.4 处理请求信息

创建出SystemServer进程后,zygote进程利用函数runSelectLoop,处理server socket收到的命令。

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
    ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
    ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

    //首先将server socket加入到fds
    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 {
            //等待事件到来
            Os.poll(pollFds, -1);
        } catch (ErrnoException ex) {
            throw new RuntimeException("poll failed", ex);
        }
        //注意这里是倒序的
        for (int i = pollFds.length - 1; i >= 0; --i) {
            if ((pollFds[i].revents & POLLIN) == 0) {
                continue;
            }
            //server socket最先加入fds, 因此这里是server socket收到数据
            if (i == 0) {
                //收到新的建立通信的请求,建立通信连接
                ZygoteConnection newPeer = acceptCommandPeer(abiList);
                //加入到peers和fds
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
             } else {
                //其它通信连接收到数据,runOnce执行对应命令
                boolean done = peers.get(i).runOnce();
                if (done) {
                    //对应通信连接不再需要执行其它命令,关闭并移除
                    peers.remove(i);
                    fds.remove(i);
                }
            }
        }
    }
}

从上面代码可知,初始时,fds中仅有server socket,因此当有数据到来时,将执行i等于0的分支。
此时,显然是需要创建新的通信连接,因此acceptCommandPeer将被调用。

private static ZygoteConnection acceptCommandPeer(String abiList) {
    try {
        return new ZygoteConnection(sServerSocket.accept(), abiList);
    } catch (IOException ex) {
        throw new RuntimeException("IOException during accept()", ex);
    }
}

acceptCommandPeer封装了socket的accpet函数。于是我们知道,对应的新的连接,zygote将会创建出一个新的socket与其通信,并将该socket加入到fds中。因此,一旦通信连接建立后,fds中将会包含有多个socket。

当poll监听到这一组sockets上有数据到来时,就会从阻塞中恢复。于是,我们需要判断到底是哪个socket收到了数据。

在runSelectLoop中采用倒序的方式轮询,由于server socket第一个被加入到fds,因此最后轮询到的socket才需要处理新建连接的操作;其它socket收到数据时,仅需要调用zygoteConnection的runonce函数执行数据对应的操作。

若一个连接处理完所有对应消息后,该连接对应的socket和连接等将被移除。

socket编程中,accept()调用主要用在基于连接的套接字类型,比如SOCK_STREAM和SOCK_SEQPACKET。
它提取出所监听套接字的等待连接队列中第一个连接请求,创建一个新的套接字,并返回指向该套接字的文件描述符。
新建立的套接字不在监听状态,原来所监听的套接字的状态也不受accept()调用的影响。

结束语
以上是自己对zygote进程的一些初步分析,我们知道了zygote如何由init进程加载后,一步一步地进入到java世界。由于经验原因,自己的分析难免存在纰漏和不够详细的地方,欢迎大家指正。

作者:Gaugamela 发表于2016/8/20 13:16:45 原文链接
阅读:79 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>