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進程通信實際上並不頻繁,維護線程池,在拷貝線程池也是一種負擔。

 

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