对android中Zygote的理解

Posted canbot

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对android中Zygote的理解相关的知识,希望对你有一定的参考价值。

谈谈对Zygote的简单理解

1. Zygote的作用

启动SystemServer
孵化应用进程
SystemServer也是通过Zygote启动的,因为它也需要Zygote的资源:常用类,JNI函数,主题资源,共享库等。

2. Zygote的启动流程

2.1 android进程启动三段式

进程启动-》 准备工作-》LOOP循环 (接受消息,处理消息。消息可能来自:socket,message queue,binder驱动)
只要是独立进程,都会是这样启动,例如Zygote进程,系统服务进程,应用进程等。

2.2 Zygote的启动流程分两步分析

1. Zygote进程怎么启动的?

init进程是Linux启动后的用户空间第一个进程,它首先会加载配置文件init.rc读取哪些系统服务需要启动,例如Zygote,Service Manager等。
Zygote启动通过fork() + execve()系统调用
execve()需要传可执行程序路径和参数,
init.rc文件里的Zygote配置如下:

service zygote /system/bin/app_process64 -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 audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

重点关注第一行,格式如下:
service 名称 可执行程序路径 参数
启动进程方式代码:

pid_t pid = fork();
if(pid == 0)
    //child process
    execve(path, argv, env);
else
    //parent process

fork()函数会返回两次,子进程返回的pid == 0,父进程返回的pid为子进程的pid(非0),默认情况下,fork()出的子进程会继承父进程的资源,但如果执行了execve(path,argv,env)新的二进制程序,那继承的资源会被清掉,
其中path为可执行程序路径,argv为配置参数,env为环境变量。

信号处理–SIGCHLD
这个信号很常见,fork出子进程后,父进程都会关注这个信号,
如果子进程死了,父进程会收到这个信号SIGCHLD,就会去重启子进程。
例如Zygote挂了,init进程会收到SIGCHLD信号,然后重启Zygote。

2. Zygote进程启动后做了什么事呢?

总的来说可以分成两个部分,一个是Zygote的Native世界,一个是Zygote的Java世界。Zygote进程启动之后执行execve()二进制可执行程序,(App_main.cpp)程序中有一个main()函数作为程序入口,在里面做了一些准备工作,然后JNI调用进入java世界。

2.1 Zygote的Native世界

启动android的虚拟机
注册android的JNI函数
JNI调用进入java世界
举个例子,如何从C++代码切换到java环境:

int main(int argc, char *argv[])
    javaVM *jvm;
    JNIEnv *env;
    JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);  //@1,创建虚拟机
    jclass clazz = env->FindClass("ZygoteInit");    //找到zygoteinit java类
    jmethodID method = env->GetStaticMethodID(clazz, "Main", "([java/lang/String;)V"); //找到里面的main函数
    env->CallStaticVoidMethod(clazz, method, args); //运行main函数
    jvm->DestroyJavaVM();//关闭虚拟机

我们的应用进程里没有重新创建虚拟机,是因为Zygote进程里已经创建好,应用进程直接继承过来,再重置虚拟机状态和重启里面的守护线程就可以了。

2.2 Zygote的java世界

Navite世界中的CallStaticVoidMethod最终会调用com.android.internal.os.ZygoteInit的main函数,其中做的工作如下:

1. 建立IPC通信服务端——registerZygoteSocket

Zygote以及系统中其他程序的通信没有使用Binder,而是采用了基于AF_UNIX类型的Socket。registerZygoteSocket函数的使命正是建立这个Socket。

2. 预加载类和资源

预加载类资源preloadClass函数,主要是加载preload-classes文件中记录的类信息。
预加载资源preloadResources函数,主要是加载framework-res.apk中的资源,例如在UI编程中常使用的com.android.R.XXX资源,是系统默认的资源,它们就是由Zygote加载的。

3. 启动system_server

startSystemServer(abiList, socketName),这个函数会创建Java世界中系统Service所驻留的进程system_server,该进程是framework的核心。如果它死了,就会导致zygote自杀。

ZygoteConnection.Arguments parsedArgs = null;
        int pid;
        try 
            parsedArgs = new ZygoteConnection.Arguments(args);
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
             //fork一个子进程,看来,这个子进程就是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);
        

        /* For child process */
        if (pid == 0) 
            if (hasSecondZygote(abiList)) 
                waitForSecondaryZygote(socketName);
            
            //处理system_server进程的工作
            handleSystemServerProcess(parsedArgs);
        

这里出现了一个分水岭,即Zygote进行了一次无性繁殖,分裂出了一个system_server进程。

4. 有求必应之等待请求——runSelectLoop

当Zygote从startSystemServer返回后,将进入第四个关键函数:runSelectLoop(abiList)。前面,在第一个关键点registerZygoteSocket中注册了一个用于IPC的Socket,不过那时还没有地方用到它。它的用途将在这个runSelectLoop中体现出来,请看下面的代码:

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller 
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
		//sServerSocket是我们先前在registerZygoteSocket建立的Socket
        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;
                
                if (i == 0) 
                	//如有一个客户端连接上,请注意客户端在Zygote的代表是ZygoteConnection
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                 else 
                	//客户端发送了请求,peers.get返回的是ZygoteConnection
            		//后续处理将交给ZygoteConnection的runOnce函数完成。
                    boolean done = peers.get(i).runOnce();
                    if (done) 
                        peers.remove(i);
                        fds.remove(i);
                    
                
            
        
    

runSelectLoop比较简单,就是:
· 处理客户连接和客户请求。其中客户在Zygote中用ZygoteConnection对象来表示。
· 客户的请求由ZygoteConnection的runOnce来处理,其主要代码如下:


boolean runOnce() throws ZygoteInit.MethodAndArgsCaller 
    String[] args = readArgumentList(); //读取参数列表
    int pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                    parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                    parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
                    parsedArgs.appDataDir);; //根据参数启动子进程
    
    if(pid == 0)
        //in child
        handleChildProc(args, ...); 
        //在子进程里面干活,其实执行的就是java类得main函数(入口函数),java类名来自上面读取的参数列表。
        //参数列表是AMS跨进程发过来的,类名就是ActivityThread.main(),
        //也就是说,应用程序进程执行后会马上执行ActivityThread.main()函数
        return true;
    

2.3 关于 Zygote启动流程的总结

Zygote是创建Android系统中Java世界的盘古,它创建了第一个Java虚拟机,同时它又是女娲,它成功地繁殖了framework的核心system_server进程。做为Java语言的受益者,我们理应回顾一下Zygote创建Java世界的步骤:

· 第一天:创建AppRuntime对象,并调用它的start。此后的活动则由AppRuntime来控制。

· 第二天:调用startVm创建Java虚拟机,然后调用startReg来注册JNI函数。

· 第三天:通过JNI调用com.android.internal.os.ZygoteInit类的main函数,从此进入了Java世界。然而在这个世界刚开创的时候,什么东西都没有。

· 第四天:调用registerZygoteSocket。通过这个函数,它可以响应子孙后代的请求。同时Zygote调用preloadClasses和preloadResources,为Java世界添砖加瓦。

· 第五天:Zygote觉得自己工作压力太大,便通过调用startSystemServer分裂一个子进程system_server来为Java世界服务。

· 第六天:Zygote完成了Java世界的初创工作,它已经很满足了。下一步该做的就是调用runSelectLoop后,便沉沉地睡去了。

· 以后的日子:Zygote随时守护在我们的周围,当接收到子孙后代的请求时,它会随时醒来,为它们工作。

3. Zygote的工作原理

zygote启动过程的两个细节:

1)zygote在fork时要保证单线程,

因为不管父进程有多少个线程,子进程创建时只有一个线程,多的线程就不见了,会导致很多奇怪的问题:子进程死锁,状态不一致等。
所以不如直接,fork子进程时,停掉其他线程,创建完了子进程再重启那些线程,
zygote就是这么做的,它不只有主线程,还有与虚拟机相关的守护线程。

2)zygote的IPC没有采用binder机制

Zygote采用的是socket,所以应用的binder机制不是从zygote继承的,而是AP进程创建后自己启动的binder机制。

思考的两个问题:

1,孵化AP为什么不交给systemServer,而是专门设计一个zygote?

应用在启动的时候,需要做很多准备工作,如启动虚拟机,加载各个类系统资源,都非常耗时,如果zygote把init工作做好,再在fork时共享给子进程,那效率就非常高。这就是zygote存在的价值,systemServer不能做,因为它跑了一堆系统服务,他们不能被继承到AP进程。
而且AP启动时,内存空间除了必要的资源外,最好是干净的,不要继承一堆乱七八糟的东西,因此不如给systemServer和AP进程都要用的资源抽出来单独放在一个进程里,这就是zygote进程。

2,zyogte的IPC为什么不用binder?用binder会有问题吗?

不用binder有个原因:

1)如果用了binder,zygote要先启动binder机制,打开binder驱动,获得描述符,map进程内存映射,注册binder线程,还要创建一个binder对象注册到serviceManager,另外AMS要向zygote发起创建应用进程请求的话,要先从serviceManager查询zygote的binder对象,再发起binder调用,非常繁琐。
相比之下,zygote和systemserver本就是父子关系,对于简单的消息通信,用管道或者socket非常方便,如果对管道和socket不了解,可以参考APUE和UNP。

2)如果zygote用了binder机制,再fork systemServer,那systemServer就继承了zygote的描述符和映射的内存,这两个进程在binder驱动层就会共用一套数据结构,这肯定是不行的。那还得把旧的描述符关掉,再重新启动一遍binder机制,自找麻烦。
Binder通讯是需要多线程操作的,代理对象对Binder的调用是在Binder线程,需要通过Handler调用主线程来操作。
比如AMS与应用进程通讯,AMS的本地代理IApplicationThread通过调用ScheduleLaunchActivity,调用到的应用进程ApplicationThread的ScheduleLaunchActivity是在Binder线程,需要再把参数封装为一个ActivityClientRecord,sendMessage发送给H类(主线程Handler,ActivityThread内部类)
fork不允许存在多线程,而非常巧的是Binder通讯偏偏就是多线程,所以干脆父进程(Zygote)这个时候就不使用binder机制。

以上是关于对android中Zygote的理解的主要内容,如果未能解决你的问题,请参考以下文章

小米Android岗二面:面试官让我说说对Zygote的理解!

安卓Zygote详解

[深入理解Android卷一全文-第四章]深入理解zygote

Android zygote 进程的启动过程分析

Android zygote 进程的启动过程分析

Android zygote 进程的启动过程分析