Zygote

Zygote

1. Zygote的作用

Android系统是基于Linux系统,所以当开机后,第一个启动的是Init进程,而后面所有的进程都是Init的子进程,Zygote就是Init进程通过解析init.rc文件之后,启动的一个进程。

主要作用

1. 启动SystemServer进程

SystemServer是一个用于启动手机内部各种服务的进程,我们常说的PMS、AMS等都是由SystemServer所启动。

2. 在系统运行过程中,即时地去孵化App进程

当我们每次点击App图标启动App时,Zygote就开始运作了。

关于孵化 Zygote进程创建别的进程的时候,用的不是创建,而是孵化这个词,如何理解孵化?

  1. 我们Android系统的程序,都是基于虚拟机所启动的,如果每次启动一个APP都要新启动一个虚拟机,则会很卡很慢,所以为了避免这种场景,Zygote进程在启动的时候,就会直接预加载虚拟机所需要的内存等资源,等后面创建应用需要用到的时候,直接共享使用,这样就避免了多次启动虚拟机的情况。

  2. Zygote进程在创建进程的时候,最后会调到Linux内核自带的fork方法,来复制一个子进程,从而达到1所说的共享虚拟机内容的情况

  3. Zygote进程的作用,就是生成别的进程,自己是不负责做事的

关于fork

  • fork在Zygote进程中是一个很重要的概念,这是Linux内核自带的一个方法,这个方法的作用就是复制一个子进程。那么,在理解这个复制的意思,简单来说,就是从成员变量,到内存空间,再到当前所执行的代码指令,都会生成一个副本后放到子进程中。

  • 当进程A调用fork后,进程A和复制的子进程B,都会得到fork方向的return值,并且继续往下执行。返回值:若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1。

  • 进程A在开辟一块内存空间之后,持有这个内存空间的引用的话,在fork之后,子进程B也会持有这个内存空间。

2. Zygote的启动流程

init进程读取init.rc文件后,根据文件里面的指令:

  1. 启动了虚拟机
  2. 注册了JNI(Java Native Interface,也就是我们常看见的那些Native方法)
  3. 启动了Zygote进程,具体启动方式就是通过反射,拿到ZygoteInit.java这个类,然后调用里面的main方法

init.rc

这段脚本要求init进程创建一个名为zygote的进程,该进程要执行的程序是"/system/bin/app_process"。并且为zygote进程创建一个socket资源 (用于进程间通信,ActivityManagerService就是通过该socket请求 zygote进程fork一个应用程序进程)。

c 复制代码
// system\core\rootdir\init.zygote32.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

Zygote要执行的程序便是system/bin/app_process,它的源代码在frameworks/base/cmds/app_process/app_main.cpp

  • App_main::main:
c 复制代码
int main(int argc, char* const argv[])
{
    ...
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {//是否有--zygote参数。这个是启动zygote进程的时候的参数
            zygote = true;
            // 进程名称,设置为zygote
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {//是否有--start-system-server
            startSystemServer = true;
	....
    if (zygote) {
        // 如果是zygote进程,则启动ZygoteInit。
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    }
}
  • AndroidRuntime::start
c 复制代码
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ...
    JNIEnv* env;
	  //重点方法:创建VM虚拟机,参数是指针,可以用于获取返回的值,可以使用env来和Java层来做交互
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);
    //重点方法:给虚拟机注册一些JNI函数(系统so库、用户自定义so库 、加载函数等。)
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

    	//找到类的main方法,并调用。如果是zygote的话,这里就会启动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 {
        	//调用main方法。这里通过JNI调用Java方法之后,Zygote(Native层)就进入了Java的世界,从而开启了Android中Java的世界。
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
        }
}

总结

c 复制代码
App_main.main
  AndroidRuntime.start
    startVm//创建虚拟机
    startReg//注册JNI函数
    ZygoteInit.main//这里就进入到了Java层了
        registerZygoteSocket//建立IPC的通讯机制
        preload//预加载类和资源
        startSystemServer//启动system_server
        runSelectLoop//等待进程创建的请求

Zygote进程的启动过程中,除了会创建一个Dalvik虚拟机 实例外,还会将Java运行时库加载到进程中。

Zygote进程初始化时启动虚拟,并加载一些系统资源。这样Zygote fork出子进程之后,子进程也会继承能正常工作的虚拟机和各种系统资源,剩下的只需要装载APK文件和字节码就可以运行程序。

Java应用程序不能以本地进程的形态运行,必须在一个独立的虚拟机中运行。如果每次都重新启动虚拟机,肯定就会拖慢应用程序的启动速度。

  • 注意:APK应用程序进程被Zygote进程孵化出来以后,不仅会获得Dalvik虚拟机实例拷贝,还会与Zygote一起共享Java运行时库。

3. Zygote的工作原理

3.1 工作入口:ZygoteInit

  • ZygoteInit.java
C 复制代码
// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String argv[]) {
    //....

    // 注册socket
    zygoteServer.registerServerSocketFromEnv(socketName); 
        
    // 预加载资源
    preload(bootTimingsTraceLog);

    // 启动 systemServer
    Runnable r = forkSystemServer(abiList, socketName, zygoteServer);    

    // loops forever in the zygote
    caller = zygoteServer.runSelectLoop(abiList);
}

3.2 启动SystemServer

如果app_process的调用参数中带有"---start-system-server",那么此时会通过forkSystemServer来启动。这里稍后再看,先考虑一个问题:

Zygote在前期主要担任启动系统服务的工作,后期则又担当"孵化进程"的重任,但是Zygote只在 init.rc中被启动一次,它如何协调好这两项工作呢?通过forkSystemserver这个方法名,我们可以明显看出这是启动了一个新的进程来承载系统服务,而我们推断app_process所在进程会继续执行,从而转变为Zygote的"孵化器"守护进程。

forkSystemServer

还记得Zygote启动时,是使用fork+execve方式,而此处使用fork+handle(即直接继承资源,未使用 execve进行资源覆盖),简单表示如下:

接着来看,handleSystemServerProcess方法里最后调用的是RuntimeInit.zygoteInit方法:

c 复制代码
void zygoteInit(String[] argv, ..,){
    commonInit();
    nativeZygoteInit();
    applicationInit(argv, ...)
}

这个方法比较短,核心就上面三行,我们来介绍一下:

  • commonInit:通用部分的初始化,包括设置默认的uncaught exception handler;TimeZone,LogManager,AndroidConfig, HTTP User-Agent等模块的初始化。

  • nativeZygoteInit:本地初始化函数,也是zygoteInit中的重点

  • applicationInit:它是SystemServer的"起点",而参数argv里会带有startClass和startArgs。这两个变量是在ZygoteInit.forkSystemServer方法定义并传入的参数。实际上最终将调用SystemServer的main方法

经过这个zygoteInit方法之后,程序现在有两个分支,其一是nativeZygoteInit主导的native系统服务启动;另一个则是applicationInit负责的Java层系统服务启动。

Native系统服务启动

c 复制代码
virtual void onZygoteInit(){
    sp<ProcessState> proc = ProcessState::self();
    proc->startThreadPool();
}

这段代码是Binder机制中的重要组成部分,其中startTheardPool将开启Binder线程池,以保证其它进程可以正确访问的Zygote所提供的服务。Zygote通过JNI和回调的方式非常巧妙的把Native层和Java层、SystemServer和app process关联起来了。

Java层系统服务启动

System Server在启动前首先需要做很多初始化设置,包括将VM的版本记录到系统变量中,设置线程优先级,设置堆的使用率。

我们知道,Android的系统服务也被分为两类,其中,Native层通过 System.loadLibrary("android_servers") 加载,而java层又继续细分如下:

  • startBootstrapServices(): "引导程序"服务,代表系统服务中最核心的部分,如 ActivityManagerService, Power Manager, Display Manger, PackageManger等

  • startCoreServices():次核心,包括LED和背光管理器,电池电量管理器,应用程序使用情况(Usage Status)管理器等

  • startOtherServices():优先级较低,但是Service数最多,如AccountManagerService, VibratorService, MountService, NetworkManagementService, WindowMangerService, AudioService 等。这些服务一起构建起Android System Server这座"参天大厦",为其它进程、应用程序的正常运行奠定了基础。

最后,SystemServer通过Looper.loop() 进入死循环,并依托onZygoteInit中启动的Binder服务接受和处理外界的请求。

c 复制代码
private void run(){
    Looper.prepareMainLooper();
    
    System.loadLibrary("android_servers:);
    createSystemContext();
    
    startBootstrapServices();
    startCoreServices();
    startOtherServices();
    
    Looper.loop()
}

3.3 孵化应用进程

runSelectLoop从方法名可以猜到,这应该是一个死循环,除非Zygote退出或出现异常才会跳出循环。

我们看到runSelectLoop的主体的确是一个while死循环,它将作为zygote的守护体存在。

mServerSocket.getFileDescriptor() 获取到的是前面通过registerServerSocketFromEnv创建的Server Socket的文件描述符。但它没有回到fds中,这同时也说明了zygote中不止有一个文件描述符来与zygotę通讯,通过后面的for循环,找出当前可读的fd 。

i == 0 时

fds中,添加的第一个元素为Zygote的Server Socket,所以此时表示有新的连接请求,这和网络连接中的 Socket概念是一致的。

我们通过acceptCommandPeer接受一个新的客户端连接,产生一个新的ZygoteConnection ,然后更新peers 和fds这样值班表中对象的index就是一致。(这就是为啥前面 peers.add(null))

i > 0 时

说明已经建立Socket连接中有来自客户端的数据需要处理,具体的工作就是 processOneCommand

这边同样是典型的使用fork + handle 模式来处理fork进程。不用细看也知道做了两件事:

创建承载应用程序的新进程值得注意的是,这边没有直接用fork方法来启动新进程,而调用了 forkAndSpecialize方法。Specialize意为 "特殊化",其实就是在fork的同时也把它转变为Android应用程序。

AMS向Zygote发起创建进程请求时传入的参数,其中有最重要的参数之一是className (android.app.ActivityThread), handleChildProc会把这个类加载到内存中,然后执行它的main函数。换句话说,我们熟悉的Android应用程序的"主线程"就此启动了

父进程的扫尾工作handleParentProc包括将子进程加入进程组,正确关闭文件,调用方返回结果值等

c 复制代码
Runnable runSelectLoop(String abiList) {
    //创建两个数组
    //数组socketFDs里面存放的是一些fd,用来做socket通信的
    //数组peers里面存放的是ZygoteConnection
    ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();
    ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

    //第0位添加一个从ZygoteSocket获取的fd
    socketFDs.add(mZygoteSocket.getFileDescriptor());
    peers.add(null);

    while (true) {
        //这前面的一大段逻辑都是处理UsapPool相关的
        //并且会将socketFDs进行一些处理,构造出一个structs poll的结构体,然后通过USAP连接池进行处理
        //根据Zygote的USAP池的状态有不同的Zygote(可以是常规Zygote,WebView Zygote或AppZygote)。
        ...

        try {
            //等待事件socket的fd事件
            Os.poll(pollFDs, -1);
        } catch (ErrnoException ex) {
            throw new RuntimeException("poll failed", ex);
        }

        boolean usapPoolFDRead = false;

        while (--pollIndex >= 0) {
            if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
                continue;
            }

            if (pollIndex == 0) {
                // ZygoteSocket收到请求,通过acceptCommandPeer创建一个ZygoteConnection
                ZygoteConnection newPeer = acceptCommandPeer(abiList);
                peers.add(newPeer);
                socketFDs.add(newPeer.getFileDescriptor());

            } else if (pollIndex < usapPoolEventFDIndex) {
                // Session socket accepted from the Zygote server socket
                try {
                    ZygoteConnection connection = peers.get(pollIndex);
                    //处理socket消息
                    final Runnable command = connection.processOneCommand(this);
                    //在Child端
                    if (mIsForkChild) {

                        if (command == null) {
                            throw new IllegalStateException("command == null");
                        }

                        return command;
                    } else {
                        //在Server端
                        if (command != null) {
                            throw new IllegalStateException("command != null");
                        }

                        // We don't know whether the remote side of the socket was closed or
                        // not until we attempt to read from it from processOneCommand. This
                        // shows up as a regular POLLIN event in our regular processing loop.
                        if (connection.isClosedByPeer()) {
                            connection.closeSocket();
                            peers.remove(pollIndex);
                            socketFDs.remove(pollIndex);
                        }
                    }
                } catch (Exception e) {
                    if (!mIsForkChild) {
                        //在server端,因此这里发生的任何异常之前都是
                        //在处理命令或从读写socket。 对此做一个提醒以便
                        //确切知道失败的原因。
                        // We're in the server so any exception here is one that has taken place
                        // pre-fork while processing commands or reading / writing from the
                        // control socket. Make a loud noise about any such exceptions so that
                        // we know exactly what failed and why.

                        Slog.e(TAG, "Exception executing zygote command: ", e);

                        //关闭socket避免超时
                        ZygoteConnection conn = peers.remove(pollIndex);
                        conn.closeSocket();

                        socketFDs.remove(pollIndex);
                    } else {
                        //在child端,异常发生在ActivityThread.main之前

                        Log.e(TAG, "Caught post-fork exception in child process.", e);
                        throw e;
                    }
                } finally {
                    //重置child的标志,在Runnable之后不在使用
                    mIsForkChild = false;
                }
            } else {
                // USAP池事件FD或USAP报告管道。
                //如果这是USAP池事件FD,则有效负载将是已删除的USAP的数量。
                //如果这是USAP报告管道FD,则有效负载将是USAP的PID
                //这只是专门的。

                long messagePayload = -1;

                try {
                    byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];
                    int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);

                    if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {
                        DataInputStream inputStream =
                                new DataInputStream(new ByteArrayInputStream(buffer));

                        messagePayload = inputStream.readLong();
                    } else {
                        Log.e(TAG, "Incomplete read from USAP management FD of size "
                                + readBytes);
                        continue;
                    }
                } catch (Exception ex) {
                    if (pollIndex == usapPoolEventFDIndex) {
                        Log.e(TAG, "Failed to read from USAP pool event FD: "
                                + ex.getMessage());
                    } else {
                        Log.e(TAG, "Failed to read from USAP reporting pipe: "
                                + ex.getMessage());
                    }

                    continue;
                }

                if (pollIndex > usapPoolEventFDIndex) {
                    Zygote.removeUsapTableEntry((int) messagePayload);
                }

                usapPoolFDRead = true;
            }
        }

        //检查USAP池是否需要重新填充
        if (usapPoolFDRead) {
            int[] sessionSocketRawFDs =
                    socketFDs.subList(1, socketFDs.size())
                            .stream()
                            .mapToInt(fd -> fd.getInt$())
                            .toArray();

            final Runnable command = fillUsapPool(sessionSocketRawFDs);

            if (command != null) {
                return command;
            }
        }
    }
}

4. 总结

4.1 孵化应用进程为什么不给SystemServer来做,而专门设计一个Zygote?

我们知道,应用在启动的时候需要做很多准备工作,包括启动虚拟机,加载各类系统资源等等,这些都是非常耗时的,如果能在zygote里就给这些必要的初始化工作做好,子进程在fork的时候就能直接共享,那么这样的话效率就会非常高。这个就是zygote存在的价值,这一点呢SystemServer是替代不了的,主要是因为SystemServer里跑了一堆系统服务,这些是不能继承到应用进程的。而且我们应用进程在启动的时候,内存空间除了必要的资源外,最好是干干净净的,不要继承一堆乱七八糟的东西。所以呢,不如给SystemServer和应用进程里都要用到的资源抽出来单独放在一个进程里,也就是这的zygote进程,然后zygote进程再分别孵化出SystemServer进程和应用进程。孵化出来之后,SystemServer进程和应用进程就可以各干各的事了。

4.2 Zygote的IPC通信机制为什么不采用binder?

第一个原因,我们可以设想一下采用binder调用的话该怎么做,首先zygote要启用binder机制,需要打开binder驱动,获得一个描述符,再通过mmap进行内存映射,还要注册binder线程,这还不够,还要创建一个binder对象注册到serviceManager,另外AMS要向zygote发起创建应用进程请求的话,要先从serviceManager查询zygote的binder对象,然后再发起binder调用,这来来回回好几趟非常繁琐,相比之下,zygote和SystemServer进程本来就是父子关系,对于简单的消息通信,用管道或者socket非常方便省事。第二个原因,如果zygote启用binder机制,再fork出SystemServer,那么SystemServer就会继承了zygote的描述符以及映射的内存,这两个进程在binder驱动层就会共用一套数据结构,这显然是不行的,所以还得先给原来的旧的描述符关掉,再重新启用一遍binder机制,这个就是自找麻烦了。

相关推荐
C4rpeDime1 小时前
自建MD5解密平台-续
android
鲤籽鲲3 小时前
C# Random 随机数 全面解析
android·java·c#
m0_548514776 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php
凤邪摩羯7 小时前
Android-性能优化-03-启动优化-启动耗时
android
凤邪摩羯7 小时前
Android-性能优化-02-内存优化-LeakCanary原理解析
android
喀什酱豆腐7 小时前
Handle
android
m0_748232929 小时前
Android Https和WebView
android·网络协议·https
m0_748251729 小时前
Android webview 打开本地H5项目(Cocos游戏以及Unity游戏)
android·游戏·unity
m0_7482546611 小时前
go官方日志库带色彩格式化
android·开发语言·golang
zhangphil11 小时前
Android使用PorterDuffXfermode模式PorterDuff.Mode.SRC_OUT橡皮擦实现“刮刮乐”效果,Kotlin(2)
android·kotlin