Zygote研究報告

一.Zygote的前世今生
1.前世
在init.rc中,zygote作爲一個service被啓動,其配置爲:
service zygote /system/bin/app_process --zygote /system/bin --zygote --start-system-server
其中:
app_process命令是frameworks/base/cmds/app_process/app_main.cpp編譯生成的,作爲zygote進程的主程序,zygote這個進程名字是進入程序後修改的。
--zygote 指定程序的進程名,nice name
/system/bin 指定程序的parent dir
--zygote 表示啓動com.android.internal.os.ZygoteInit類的main函數
--start-system-server 表示啓動system_server
2.今生
大體上,zygote完成了如下的使命
-----------------------------                                                                  -------------------------
|     zygote process     |                                                                   |   some process   |
-----------------------------                                                                  -------------------------
                  |             fork                  ----------------------------------                    |
                  +--------------------------->|   system server process  |                       |
                  |                                     ----------------------------------                   |
                  |                                fork command                                            |
                  |<------------------------------------------<------------------------------------|
                  |             fork                   --------------------------------                     |
                  +---------------------------->|           new process       |                      |
                  |                                      --------------------------------                     |
                  |                                   return pid                                               |
                  |-------------------------------------------->---------------------------------->|
1.fork system server進程,並執行com.android.server.SystemServer的main函數
system server進程主要是啓動各種service,並進入loop循環等待處理消息
2.等待其他進程的command,在收到command之後進行必要的安全檢查之後,fork一個新的進程,並執行指定類的main函數

二.Zygote-受精卵-孕育生命
zygote的中文意思是受精卵,在android中它也的確起到了孕育生命的作用,下面我們將看到其孕育生命的過程
1.調用樹
可參考如下的調用樹來閱讀下面的分析過程:
main[app_main.cpp]
    +--AppRuntime.start[即AndroidRuntime.start][AndroidRuntime.cpp]
              +--AndroidRuntime.startVM[AndroidRuntime.cpp]
                      +--JNI_CreateJavaVM[ dalvik/vm/Jni.cpp ]
              +--AndroidRuntime.startReg[AndroidRuntime.cpp]
              +--env->CallStaticVoidMethod
                       +--ZygoteInit::main[ ZygoteInit.java ]
                             +--ZygoteInit::registerZygoteSocket[ ZygoteInit.java ]
                             +--ZygoteInit::preloadClasses[ ZygoteInit.java ]
                             +--ZygoteInit::startSystemServer[ ZygoteInit.java ]
                                  +--nativeForkSystemServer [ Zygote.java]
                                  +--handleSystemServerProcess [ZygoteInit.java ]
                                          +--zygoteInitNative[ZygoteInit.java ]
                                          +--ApplicationInit[ZygoteInit.java ]
                             +--ZygoteInit::runSelectLoopMode[ ZygoteInit.java ]


2.app_main.cpp
app_main.cpp中的main函數是zygote的主函數,它的流程如下:
1)解析參數
2)調用AppRuntime.start
AppRuntime從AndroidRuntime繼承的,在main函數中存在一個AppRuntime的棧變量,在這裏會調用AppRuntime的構造函數,也即AndroidRuntime的構造函數,AndroidRuntime類定義於frameworks/base/core/jni/AndroidRuntime.cpp中,具體如下所示。由於這個main函數不會退出,所以這個實例是AndroidRuntime的唯一實例。從構造函數中,我們可以看出skia庫就是在這裏初始化的,gCurRuntime是一個全局的指針,在後面的調用中將會通過調用它來調用AndroidRuntime的虛函數實現。
AndroidRuntime::AndroidRuntime()
{
    SkGraphics::Init();
    // this sets our preference for 16bit images during decode
    // in case the src is opaque and 24bit
    SkImageDecoder::SetDeviceConfig(SkBitmap::kRGB_565_Config);
    // This cache is shared between browser native images, and java "purgeable"
    // bitmaps. This globalpool is for images that do not either use the java
    // heap, or are not backed by ashmem. See BitmapFactory.cpp for the key
    // java call site.
    SkImageRef_GlobalPool::SetRAMBudget(512 * 1024);
    // There is also a global font cache, but its budget is specified in code
    // see SkFontHost_android.cpp

    // Pre-allocate enough space to hold a fair number of options.
    mOptions.setCapacity(20);

    assert(gCurRuntime == NULL);        // one per process
    gCurRuntime = this;
}

3.AndroidRuntime.start
AppRuntime.start函數實際上是調用了基類的start的函數,
        runtime.start("com.android.internal.os.ZygoteInit",//指定啓動java類的類名
                startSystemServer ? "start-system-server" : "");
它的流程如下:
1)AndroidRuntime::startVm
2)AndroidRuntime::startReg
3)env->CallStaticVoidMethod

4.AndroidRuntime::startVm函數的流程:
根據system的property設置傳遞給虛擬機的參數
1).checkjni
根據dalvik.vm.checkjni和ro.kernel.android.checkjni來確定是否設置checkjni
jni check是在native調用jni函數的時候進行一些檢查工作,保證jni調用的正確性
2).dalvik.vm.heapsize
設置虛擬機的heap大小,默認是16M
關於dalvik的說明在Dalvik/Docs/Dexopt.html
3).調用JNI_CreateJavaVM

5.JNI_CreateJavaVM調用流程如下:
1).初始化gDvmJni
gDvmJni最重要的一個字段是一個指向JavaVMExt的指針
2).初始化gDvm
gDvm中的內容相當多,主要是關於虛擬機的各種設定和native函數
調用完成後,具有如下的結構:
                                    __________
gDvmJni  -------------->|    JavaVM   |---->gInvokeInterface
                                   -----------------        ---------------
                                   |      enList    |----> |   JNIEnv   |----->gNativeInterface
                                   -----------------        ---------------
                                                                |      next     |------->JNIEnv
                                                                ----------------
                                                                |     prev      |
                                                                ---------------- 
這裏需要說明的是從內存佈局看,JavaVM和JavaVMExt具有類似C++的派生關係,JNIEnv和JNIEnvExt也具有這種關係,所以他們可以互相強制轉換,上圖中使用的結構定義如下所述:
dalvik/vm/init.cpp定義
struct DvmGlobals gDvm;
struct DvmJniGlobals gDvmJni;

dalvik/vm/globals.h
struct DvmJniGlobals {
    bool useCheckJni;
    bool warnOnly;
    bool forceCopy;

    // Provide backwards compatibility for pre-ICS apps on ICS.
    bool workAroundAppJniBugs;

    // Debugging help for third-party developers. Similar to -Xjnitrace.
    bool logThirdPartyJni;

    // We only support a single JavaVM per process.
    JavaVM*     jniVm;
};

dalvik/vm/jniinternal.h定義
struct JavaVMExt {
    const struct JNIInvokeInterface* funcTable;     /* must be first */

    const struct JNIInvokeInterface* baseFuncTable;

    /* head of list of JNIEnvs associated with this VM */
    JNIEnvExt*      envList;
    pthread_mutex_t envListLock;
};
struct _JavaVM {
    const struct JNIInvokeInterface* functions;

#if defined(__cplusplus)
    jint DestroyJavaVM()
    { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
    jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};

dalvik/vm/jni.cpp
static const struct JNIInvokeInterface gInvokeInterface = {
    NULL,
    NULL,
    NULL,

    DestroyJavaVM,
    AttachCurrentThread,
    DetachCurrentThread,

    GetEnv,

    AttachCurrentThreadAsDaemon,
};

------------------------------------------------------------------------------

struct JNIEnvExt {
    const struct JNINativeInterface* funcTable;     /* must be first */

    const struct JNINativeInterface* baseFuncTable;

    u4      envThreadId;
    Thread* self;

    /* if nonzero, we are in a "critical" JNI call */
    int     critical;

    struct JNIEnvExt* prev;
    struct JNIEnvExt* next;
};
struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;
....
};
static const struct JNINativeInterface gNativeInterface = {
......
}
如果要研究java虛擬機,可以從此做進一步分析,但是總體上來說,JNIEnv表示JavaVM的運行環境

6.AndroidRuntime::startReg
AndroidRuntime::startReg函數流程
1.設置Thread的線程創建函數
2.在JNIEnv中push一個Frame
3.使用JNI機制,向JNIEnv中註冊內置的java類
4.在JNIEnv中pop一個Frame
爲什麼需要Push和Pop Frame?
在該函數的代碼中有這樣一段註釋:
    /*
     * Every "register" function calls one or more things that return
     * a local reference (e.g. FindClass).  Because we haven't really
     * started the VM yet, they're all getting stored in the base frame
     * and never released.  Use Push/Pop to manage the storage.
     */
大致意思是說,每個reigister函數會調用很多返回local ref的東西,因爲現在VM還沒有啓動,所以這些東西不會自動釋放,所以這裏需要手動加入Frame
這 裏的Frame應該是stack frame,用來構成frame鏈表的,查看FindClass函數(dalvik/vm/jni.cpp),其中調用了一個 addLocalReference的函數,這個函數就是用來把FindClass返回的local ref加入到當前Frame的一個引用表中,在native函數的棧推出的時候,根據Frame的引用表釋放這些local reference的。當然,由於此時VM還沒有啓動,Frame還沒有建立起來,所以需要我們受到的push一個Frame。該函數的註釋如下:
/*
 * Add a local reference for an object to the current stack frame.  When
 * the native function returns, the reference will be discarded.
 *
 * We need to allow the same reference to be added multiple times.
 *
 * This will be called on otherwise unreferenced objects.  We cannot do
 * GC allocations here, and it's best if we don't grab a mutex.
 *
 * Returns the local reference (currently just the same pointer that was
 * passed in), or NULL on failure.
 */

7.env->CallStaticVoidMethod
該函數調用com.android.internal.os.ZygoteInit類的main函數,這一步完成了從C++到java的過程,從此一去不復返,調用這個main函數的時候,傳入了start-system-server作爲參數。其中com.android.internal.os.ZygoteInit這個類名是在app_main.cpp的man函數中調用AndroidRuntime.start的時候指定的

8.ZygoteInit::main
該函數定義於framework/base/core/java/com/android/internal/os/ZygoteInit.java,其流程如下:
1)registerZygoteSocket函數
2)preloadClasses函數
3)startSystemServer函數
4)runSelectLoopMode函數

8.ZygoteInit::registerZygoteSocket函數
通過對init進程的分析,我們知道在啓動服務的時候,會在/dev /socket目錄下創建一個以服務名字命名的socket,並把這個socket的fd加入到環境變量中,命名爲 ANDROID_SOCKET_[service_name],在這個函數中,我們看到的正是這個 socket,ANDROID_SOCKET_zygote,
    private static void registerZygoteSocket() {
        if (sServerSocket == null) {
            int fileDesc;
            try {
                String env = System.getenv(ANDROID_SOCKET_ENV);//從環境變量中獲得socket fd
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(
                        ANDROID_SOCKET_ENV + " unset or invalid", ex);
            }

            try {
                sServerSocket = new LocalServerSocket(
                        createFileDescriptor(fileDesc));//createFileDescriptor 是一個native函數,對應的c函數定義於framework/base/core/jni /com_android_internal_os_zygoteinit.cpp文件中 com_android_internal_os_ZygoteInit_createFileDescriptor ,該函數的主要作用就是在JNIEnv中創建一個與fd對應的jobject,讓java環境通過這個jobject來訪問fd。
            //LocalServerSocket是一個封裝類,它定義於framework/base/core/java/android/net /LocalServerSocket.java,它的功能由LocalSocketImpl類實現,LocalServerSocket的構造函數就直 接調用了LocalSocketImpl的listen方法,該方法調用native函數實現功能,對應的c函數定義於framework/base /core/jni/android_net_LocalSocketImpl.cpp中的socket_listen
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }
由 上面的代碼可以知道,該函數就是啓動監聽/dev/socket/zygote socket,除此之外,關於java層通過jni調用native函數,我們也可以知道一些規則,如果一個java文件中的某個函數是native函 數,那麼對應的C++函數實現所在的文件名的命名規則爲:java文件的包名與文件名組成的路徑,以LocalSocketImpl類的listen方法 爲例,該類的全路徑爲android.net.LocalSocketImpl,則對應的native函數所在的文件的路徑爲 framework/base/core/jni/android_net_LocalSocketImpl.cpp,其中framework/base /core/jni是一個基準路徑

9.ZygoteInit::preloadClasses函數
該函數主要用來預加載類,frameworks/base/preloaded-classes文件中,列出了需要加載的所有類名,該文件是由framworks/base/tools/preload生成的。
該函數有幾點需要說明:
1.使用VMRuntime.getTargetHeapUtilization
VMRuntime 是對VM運行期的功能的封裝,它封裝了很多的native函數,通過getTargetHeapUtilization和 setTargetHeapUtilization方法,用來獲取和設置heap的使用率。這裏設置爲最高0.8的使用率。這樣可能控制了gc的次數,加 快加載速度
2.統計加載過程中內存分配的大小,超過閥值的時候,進行GC

10.ZygoteInit::preloadResources函數
該函數使用frameworks/base/core/java/android/content/res/Resources.java中定義的Resources類來加載
com.android.internal.R.array.preloaded_drawables
com.android.internal.R.array.preloaded_color_state_lists

11.ZygoteInit::startSystemServer函數
該函數的流程如下:
11.1.fork一個進程作爲system_server
java層的nativeForkSystemServer函數 定義於libcore/dalvik/src/main/java/dalvik/system/Zygote.java,它對應dalvik/vm /native/dalvik_system_Zygote.cpp中 的 Dalvik_dalvik_system_Zygote_forkSystemServer函數,該函數的功能如下:
1).設置sigchild信號的處理爲kill(petpid(),SIGKILL),即在收到子進程退出的信號後,kill自己,即zygote進程,這樣init進程會重新啓動zygote進程,繼而由zygote進程重新啓動system_server進程
2).fork一個子進程,即system_server進程,pid設置給gDvm.systemServerPid
11.2.調用handleSystemServerProcess作爲這個子進程的處理函數
該函數定義於frameworks/base/core/java/com/android/internal/os/ZygoteInit.java,其流程如下:
1)調用RuntimeInit::zygoteInit(frameworks/base/core/java/com/android/internal/os/RuntimeInit.java)
public static final void zygoteInit(int targetSdkVersion, String[] argv)
            throws ZygoteInit.MethodAndArgsCaller {
        if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");

        redirectLogStreams();

        commonInit();
        //該函數是一個native函數,對應frameworks/base/core/jni/AndroidRuntime.cpp中的 com_android_internal_os_RuntimeInit_zygoteInit函數,該函數調用 gCurRuntime->onZygoteInit();其中gCurRuntime是AndroidRuntime構造函數中初始化的時候賦值 的,而AndroidRuntime實例是在app_main.cpp的main函數中作爲棧變量聲明的,實際上是作爲AppRuntime實例存在 的,AppRuntime是AndroidRuntime的派生類。這個main函數是Zygote的主函數,不會退出。onZygoteInit是啓動 線程與Binder通信
        zygoteInitNative();
       
        //該函數最 終會調用invokeStaticMain,即調用參數指定類的main函數,即在startSystemServer中指定的類名 com.android.server.SystemServer。比較有意思的是,該函數並非直接調用這個main函數,而是通過拋出異常的方式,拋出 了ZygoteInit.MethodAndArgsCaller異常,這個異常最終是在Zygote的main函數中catch住,並調用了該實例的 run方法。這種方式應該是爲了釋放多次調用函數的棧變量。需要注意的是,此時,異常的執行已經是在子進程中了。
        applicationInit(targetSdkVersion, argv);
    }

12.runSelectLoopMode函數
該函數的流程如下:
1)把註冊的zygote socket加入到select的文件描述符表中
2)selectReadable,等待可讀的文件描述符
3)如果zygote socket可讀,則accept一個新的ZygoteConnection,並把它加入到文件描述符表中
4)如果是除zygote以外可讀,則調用對應的ZygoteConnection.runOnce執行請求的fork命令。
這裏有兩個問題需要特別說明:
1.ZygoteConnection代表一個connection,在其構造函數中,調用了getPeerCredential函數,該函數對應getPeerCredentials_native函數,最終對應getsockopt函數,通過SO_PEERCRED選項,得到cred信息,對於這個選項和cred結構,我們通過
man 7 unix
man 7 socket
得到以下的信息
getsockopt得到的cred信息是隻讀的,發送端通過使用socket option SO_PASSCRED來設置是否允許獲取cred,接收端通過SO_PEERCRED選項,得到cred信息,cred的結構內容如下:
struct ucred {
                      pid_t pid;    /* process ID of the sending process */
                      uid_t uid;    /* user ID of the sending process */
                      gid_t gid;    /* group ID of the sending process */
                  };
zygote進程通過這個結構的內容來判斷是否允許請求fork新的進程。
2.誰發送的fork請求
ZygoteConnection.readArgumentList方法的註釋中給出了答案,frameworks/base/core/java/android/os/Process.java的 zygoteSendArgsAndGetResult函數發送參數,並獲取執行結果,
    /**
         * See android.os.Process.zygoteSendArgsAndGetPid()
         * Presently the wire format to the zygote process is:
         * a) a count of arguments (argc, in essence)
         * b) a number of newline-separated argument strings equal to count
         *
         * After the zygote process reads these it will write the pid of
         * the child or -1 on failure.
         */
註釋中的zygoteSendArgsAndGetPid應該是老版本的代碼,在使用新的代碼後,沒有更新註釋
再進一步查看,該函數被Process::startViaZygote調用,在這裏添加了各種調用的參數,如果nice-name指定進程的名稱,processClass指定執行的類名稱,runtime-init說明通過RuntimeInit來運行類。
再進一步查看,Process::startViaZygote又被Process::start調用,也就是說,如果有進程通過android.os.Process.start方法啓動進程的時候,實際上就是發送指定的參數給zygote進程

對比zygoteSendArgsAndGetResult方法,再來看ZygoteConnection.runOnce
1)ZygoteConnection.readArgumentList
2)安全性檢查
3)Zygote.forkAndSpecialize fork一個進程
4)在子進程中,執行RuntimeInit.zygoteInit執行指定的進程
5)在父進程中,把子進程pid發送回去。

三.背景知識
1.JNI調用
在進行JNI調用的時候,會用到如下的signature定義,例如:
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
"()V"

"(II)V"

"(Ljava/lang/String;Ljava/lang/String;)V"

實際上這些字符是與函數的參數類型一一對應的。

"()" 中的字符表示參數,後面的則代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具體的每一個字符的對應關係如下

字符 Java類型 C類型

V      void            void
Z       jboolean     boolean
I        jint              int
J       jlong            long
D      jdouble       double
F      jfloat            float
B      jbyte            byte
C      jchar           char
S      jshort          short

數組則以"["開始,用兩個字符表示

[I       jintArray      int[]
[F     jfloatArray    float[]
[B     jbyteArray    byte[]
[C    jcharArray    char[]
[S    jshortArray   short[]
[D    jdoubleArray double[]
[J     jlongArray     long[]
[Z    jbooleanArray boolean[]

上面的都是基本類型。如果Java函數的參數是class,則以"L"開頭,以";"結尾中間是用"/" 隔開的包及類名。而其對應的C函數名的參數則爲jobject. 一個例外是String類,其對應的類爲jstring

2.子進程與父進程
子進程繼承了父進程的文件描述符表,環境,運行時,與。子進程複製了父進程的環境,得到了一份拷貝,所以它對於環境的修改,並不影響父進程。但是文件描述符表卻是與父進程共享的。
四.待研究的問題
1.Thread
對於Thread需要特別研究一下,在JavaVM的InvokeInterface中,有attachThread的函數,在startReg中有設置 Thread的創建函數,這些都需要詳細研究一下。

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