對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機制。

發佈了4 篇原創文章 · 獲贊 1 · 訪問量 5178
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章