Android ZygoteServer 多路复用机制与CopyOnWrite

一、Android  Zygote_Server进程

Android中创建app进程使用了3种通信技术:

  • Binder
  • LocalSocket
  • Pipe

Android 中创建应用进程的方式有3类:

  • Zygote_Server  
  • JNI fork
  • shell  +  app_process

Zygote_Server是Android中非常重要的服务进程,无论是System_Server、WebView_Zygote(Android O之时是init进程的子进程,Android Q是ZygoteServer的子进程)这样的Android服务进程,还是我们的各种app,他们共同的父进程都是ZygoteServer。

Android ZygoteServer历来都是单线程+同步非阻塞IO模型,在Android 5.0之前,使用linux select 机制实现单线程多路复用,在Android 5.0之后该用poll机制,主要原因是select 使用复杂,最大能监听1024个文件描述符(Linux上一切皆文件),而Poll机制进行了改进,文件描述符FD数量取消了限制。当然,有人可能要问,为什么不实用epoll呢?

主要我们fork线程的频度可能还没有我们使用Handler Looper的频度高,epoll在低并发场景下优势并不明显,而且epoll有个事件队列要维护,对於单纯fork进程的服务,必要性不是很高。

二、多路复用

【1】Android 6.0 之前使用的linux select 单线程多路复用 (ZygoteInit.java)

/**
602     * Runs the zygote process's select loop. Accepts new connections as
603     * they happen, and reads commands from connections one spawn-request's
604     * worth at a time.
605     *
606     * @throws MethodAndArgsCaller in a child process when a main() should
607     * be executed.
608     */
609    private static void runSelectLoop() throws MethodAndArgsCaller {
610        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
611        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
612        FileDescriptor[] fdArray = new FileDescriptor[4];
613
614        fds.add(sServerSocket.getFileDescriptor());
615        peers.add(null);
616
617        int loopCount = GC_LOOP_COUNT;
618        while (true) {
619            int index;
620
621            /*
622             * Call gc() before we block in select().
623             * It's work that has to be done anyway, and it's better
624             * to avoid making every child do it.  It will also
625             * madvise() any free memory as a side-effect.
626             *
627             * Don't call it every time, because walking the entire
628             * heap is a lot of overhead to free a few hundred bytes.
629             */
630            if (loopCount <= 0) {
631                gc();
632                loopCount = GC_LOOP_COUNT;
633            } else {
634                loopCount--;
635            }
636
637
638            try {
639                fdArray = fds.toArray(fdArray);
640                index = selectReadable(fdArray);
641            } catch (IOException ex) {
642                throw new RuntimeException("Error in select()", ex);
643            }
644
645            if (index < 0) {
646                throw new RuntimeException("Error in select()");
647            } else if (index == 0) {
648                ZygoteConnection newPeer = acceptCommandPeer();
649                peers.add(newPeer);
650                fds.add(newPeer.getFileDesciptor());
651            } else {
652                boolean done;
653                done = peers.get(index).runOnce();
654
655                if (done) {
656                    peers.remove(index);
657                    fds.remove(index);
658                }
659            }
660        }
661    }
662

【2】Android 6.0之后改为了Linux polll

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

        fds.add(mServerSocket.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) {
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    boolean done = peers.get(i).runOnce(this);
                    if (done) {
                        peers.remove(i);
                        fds.remove(i);
                    }
                }
            }
        }
    }

 

【3】什么是多路复用?

数据通信系统或计算机网络系统中,传输媒体的带宽或容量往往会大于传输单一信号的需求,为了有效地利用通信线路,希望一个信道同时传输多路信号,这就是所谓的多路复用技术(Multiplexing),当然,我们可以简单的理解为类似Looper主线程队列。

【4】我们知道Zygote_Server单线程如何处理Socket请求?

我们知道,Socket在Java多线程模型中,会创建一个线程专门处理Socket数据通信,否则阻塞就会很严重,为什么Zygote_Server不会阻塞,或者阻塞不明显,而且还能正常和Client端通信呢?

主要原因是:在操作系统中,我们的很多任务并不需要CPU处理,也不需要用户态处理,即便是copy_to_user和copy_from_user这类IO操作都是内核完成的,在Java中,之所以线程阻塞或者Socket阻塞,并不是因为用户态在执行某些操作,而是用户态在等待内核态任务完成的信号,线程要么是wait,要么是悬挂等待cpu中断唤醒。

当然,单线程fork和IO还是有些差别的,fork时用户态会调用CPU的,那么问题来了,android fork进程为什么很快?为什么不使用多线程呢?

接下来我们解答

三、Copy On Write机制

Linux中进程和线程的关系,Linux中,进程属于重量级线程,线程属于轻量级线程,都会占用资源。回到之前的问题,android fork进程为什么很快?为什么不使用多线程呢?

【1】fork的本质是什么?

fork的本质是“拷贝”他所在的线程状态和数据,注意,fork不会“拷贝”其他线程,但是会“拷贝”其他线程的对象,fork采用和Copy On Write机制(类似Java中CopyOnWriteArrayList,但linux fork是汇编指令来完成的),在变量和状态变更之前,和父进程共享资源。fork不会“拷贝子线程”,但会“拷贝”子线程的对象,因此,保证了fork基本不会出现阻塞。

【2】fork时为什么不使用多线程?

fork“拷贝的当前线程”,如果在自线程拷贝,并不能完整“拷贝”主进程中主线程的状态和变量。

【3】fork前为什么不使用Binder?

Android中经常能问到的问题是,为什么创建进程不使用Binder呢?

看了很多回答,但基本存在问题:

很多回答是fork时会死锁。

反驳点:fork进程之前完全可以使用Binder和Zygote_Server通信,毕竟ServiceManager服务创建早于Zygote_Server。此外,并不意味这fork时也需要多线程,我们将线程交由队列不就行了?

那么问题在哪里呢?

  • fork 中copy on write是为了尽可能完整复制主进程(重量级线程),Binder线程池还需要将消息传递给转向主线程队列等待,反而体现不了binder多线程的优势。
  • fork时,对于Zygote_Server进程通信实际上并不频繁,维护线程池,在拷贝线程池也是一种负担。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章