Android 9 Zygote程序啟動源碼分析指南二
Android 9 (P) 系統啟動及程序建立源碼分析目錄:
Android 9 (P)之init程序啟動源碼分析指南之一
Android 9 (P)之init程序啟動源碼分析指南之二
Android 9 (P)之init程序啟動源碼分析指南之三
Android 9 (P)核心服務和關鍵程序啟動
Android 9 (P)Zygote程序啟動源碼分析指南一
Android 9 (P)Zygote程序啟動源碼分析指南二
Android 9 (P)系統啟動之SystemServer大揭秘上
Android 9 (P)系統啟動之SystemServer大揭秘下
Android 9 (P)應用程序建立流程大揭秘
引言
各位老司機們,現在閑下來終于有時間接着續寫Android 9 Zygote程序啟動源碼分析指南二了,在前面的篇章Android 9(P)Zygote程序啟動源碼分析指南一中,我們已經講解了zygote啟動的前面階段主要是為了孵化Android世界做的前期準備工作,大概的内容如下所示:
- Zygote程序啟動流程整體概括
- Zygote 程序從何而來
- zygote建立參數解析
- 建立虛拟機
- 注冊JNI函數
都說zygote開創了Android的世界,創造了五彩缤紛的Android生态,孵化了各式的上層App。那麼本篇将會帶領大夥看看,我們的zygote是怎麼做到的!要的羅,讓我們開幹。
注意:本文示範的代碼是Android P高通msm8953平台源碼。其中涉及的源碼路徑如下:
frameworks/base/include/android_runtime/AndroidRuntime.h
frameworks/base/core/jni/AndroidRuntime.cpp
frameworks/base//core/java/com/android/internal/os/ZygoteInit.java
frameworks/base/core/java/com/android/internal/os/ZygoteServer.java
一. 通過JNI反射啟動ZygoteInit的main方法
在前面zygote啟動階段已經建立好了虛拟機,注冊好了系統JNI函數,一切就緒隻欠東風了!在接下來通過JNI的反射調用Java的類了。關于JNI的文法如果大夥有不熟悉的可以參見系列篇章JNI/NDK入門一鍋端。
1.1 JNI反射啟動ZygoteInit的main
還是熟悉的味道,還是原來的配方,經過一頓猛如虎的操作最終調用CallStaticVoidMethod反射進而調用ZygoteInit的main函數,
char* AndroidRuntime::toSlashClassName(const char* className)
{
char* result = strdup(className);
for (char* cp = result; *cp != '\0'; cp++) {
if (*cp == '.') {
*cp = '/';
}
}
return result;
}
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
......
/*
* We want to call main() with a String array with arguments in it.
* At present we have two arguments, the class name and an option string.
* Create an array to hold them.
*/
jclass stringClass;//申明一些jni變量,沒有什麼好說的
jobjectArray strArray;
jstring classNameStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
for (size_t i = 0; i < options.size(); ++i) {
jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
assert(optionsStr != NULL);
env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
/*
*将字元中的.轉換為/,我們前面傳遞過來的字元串是com.android.internal.os.ZygoteInit,
*通過這裡轉換為com/android/internal/os/ZygoteInit,不要問我為啥要這樣這個是JNI的規範
*/
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
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 {
env->CallStaticVoidMethod(startClass, startMeth, strArray);//調用main方法
#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
free(slashClassName);
ALOGD("Shutting down VM\n");
if (mJavaVM->DetachCurrentThread() != JNI_OK)//退出目前線程
ALOGW("Warning: unable to detach main thread\n");
if (mJavaVM->DestroyJavaVM() != 0)//等待所有線程結束關閉虛拟機
ALOGW("Warning: VM did not shut down cleanly\n");
......
}
二. 登陸Java世界
兜兜轉轉,調用CallStaticVoidMethod終于登陸Java的世界了,Java世界真香,讓我們暢遊一番。
2.1 ZygoteInit.main
該代碼定義在frameworks/base//core/java/com/android/internal/os/ZygoteInit.java中,這裡不過多詳細解釋,在main方法中主要做了如下幾件事情:
- 解析參數
- 調用zygoteServer.registerServerSocketFromEnv建立zygote通信的服務端
- 調用forkSystemServer啟動system_server
- zygoteServer.runSelectLoop進入循環模式
private static final String SOCKET_NAME_ARG = "--socket-name=";
public static void main(String argv[]) {
....
String socketName = "zygote";
String abiList = null;
boolean enableLazyPreload = false;
for (int i = 1; i < argv.length; i++) {//解析參數
if ("start-system-server".equals(argv[i])) {
startSystemServer = true;
} else if ("--enable-lazy-preload".equals(argv[i])) {
enableLazyPreload = true;
} else if (argv[i].startsWith(ABI_LIST_ARG)) {
abiList = argv[i].substring(ABI_LIST_ARG.length());
} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
socketName = argv[i].substring(SOCKET_NAME_ARG.length());
} else {
throw new RuntimeException("Unknown command line argument: " + argv[i]);
}
}
if (abiList == null) {
throw new RuntimeException("No ABI list supplied.");
}
zygoteServer.registerServerSocketFromEnv(socketName);//注冊zygote程序和AMS互動的通道
......
preload(bootTimingsTraceLog);//預加載類和資源
gcAndFinalize();//GC操作
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);//啟動system_server
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
if (r != null) {
r.run();
return;
}
}
Log.i(TAG, "Accepting command socket connections");
// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList);//進入循環模式
......
}
2.2 zygoteServer.registerServerSocketFromEnv
該代碼的路徑是frameworks/base/core/java/com/android/internal/os/ZygoteServer.java,這裡主要是通過解析zygote啟動傳入的變量值得到 zygote 檔案描述符,根據該檔案描述符建立 socket,用來和 ActivityManagerService 通信。AMS 通過 Process.start 來建立新的程序,而Process.start 會先通過 socket 連接配接到 zygote 程序,并最終由 zygote 完成程序建立。這裡傳入的-為-socket-name=zygote.。
/**
* Registers a server socket for zygote command connections. This locates the server socket
* file descriptor through an ANDROID_SOCKET_ environment variable.
*
* @throws RuntimeException when open fails
*/
void registerServerSocketFromEnv(String socketName) {
if (mServerSocket == null) {
int fileDesc;
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
try {
String env = System.getenv(fullSocketName);
fileDesc = Integer.parseInt(env);
} catch (RuntimeException ex) {
throw new RuntimeException(fullSocketName + " unset or invalid", ex);
}
try {
FileDescriptor fd = new FileDescriptor();
fd.setInt$(fileDesc);//設定檔案描述符
mServerSocket = new LocalServerSocket(fd);//建立socket本地服務端
mCloseSocketFd = true;
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to local socket '" + fileDesc + "'", ex);
}
}
}
LocalSocket是Android的媽咪谷歌為我們帶來了,比Java本身的socket效率要高,沒有經過協定棧,是Android自己實作的類似共享記憶體一樣的東東,在傳輸大量資料的時候就需要用到,關于LocalSocket的介紹和使用大夥可以參考篇章Android Framework層和Native層通過LocalSocket實作通信。
我們知道ActivityManagerService 通過 Process.start 來建立新的程序,而 Process.start 會先通過socket 連接配接到 zygote 程序,并最終由 zygote 完成程序建立。如下是App程序建立請求Zygote建立新的程序。
//定義在frameworks/base/core/java/android/os/Process.java
public static final ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String invokeWith,
String[] zygoteArgs) {
return zygoteProcess.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
}
//定義在frameworks/base/core/java/android/os/ZygoteProcess.java
public final Process.ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String invokeWith,
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */,
zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
throw new RuntimeException(
"Starting VM process through Zygote failed", ex);
}
}
private Process.ProcessStartResult startViaZygote(final String processClass,
final String niceName,
final int uid, final int gid,
final int[] gids,
int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String invokeWith,
boolean startChildZygote,
String[] extraArgs)
throws ZygoteStartFailedEx {
......
synchronized(mLock) {
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
......
}
private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
primaryZygoteState = ZygoteState.connect(mSocket);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
}
maybeSetApiBlacklistExemptions(primaryZygoteState, false);
maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
}
if (primaryZygoteState.matches(abi)) {
return primaryZygoteState;
}
// The primary zygote didn't match. Try the secondary.
if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
try {
secondaryZygoteState = ZygoteState.connect(mSecondarySocket);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
}
maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
}
if (secondaryZygoteState.matches(abi)) {
return secondaryZygoteState;
}
throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
}
我不知道大夥讀到這裡是否有一個疑問,為啥AMS和Zygote通信使用的是socket而不是Android的杜門絕技Binder,這個當初學習源碼的時候也是我思考的,來大夥思考思考為啥Android的設計者是這麼設計的呢,我認為主要有如下幾個方面的考慮:
- zygote比service manager先啟動,從這個意義觸發,zygote沒有service manager可以注冊binder,是以沒有辦法binder
- zygote程序和service manager程序都是由init程序啟動的,那怕先啟動service manager,也不能保證zygote起來的時候service manager啟動好了,這樣就需要額外的同步
- 同時這個socket的所有者是root,使用者組是system,隻有系統權限的使用者才能讀寫,這又多了一個安全保障(這裡的socket是unix域的socket,而不是internet域的socket)
- 最最主要的是zygote是通過fork生成程序的,而多線程是不允許fork的,可能造成死鎖,同時Binder又是多線程,為了避免這些麻煩是以幹脆使用socket
2.3 preload預加載
預加載類和資源,android Java 程序都是由 zygote 程序 fork,zygote 通過預加載類和資源可以加快子程序的執行速度和優化記憶體。因為預加載的類和資源較多,在開機優化過程中也需要重點關注 preload 的耗時。
//預加載位于/system/etc/preloaded-classes檔案中的類
preloadClasses();
//預加載資源,包含drawable和color資源
preloadResources();
//預加載OpenGL
preloadOpenGL();
//通過System.loadLibrary()方法,
//預加載"android","compiler_rt","jnigraphics"這3個共享庫
preloadSharedLibraries();
//預加載 文本連接配接符資源
preloadTextResources();
//僅用于zygote程序,用于記憶體共享的程序
WebViewFactory.prepareWebViewInZygote();
- preloadClasses()預加載的檔案路徑為/system/etc/preloaded-classes,預加載比較多有大概4000多個類,這裡對于類的加載主要通過Class.forName()方法來進行的。
[Landroid.accounts.Account;
[Landroid.animation.Animator;
[Landroid.animation.Keyframe$FloatKeyframe;
[Landroid.animation.Keyframe$IntKeyframe;
[Landroid.animation.Keyframe$ObjectKeyframe;
[Landroid.animation.Keyframe;
[Landroid.animation.PropertyValuesHolder;
[Landroid.app.LoaderManagerImpl;
[Landroid.content.ContentProviderResult;
[Landroid.content.ContentValues;
[Landroid.content.Intent;
[Landroid.content.UndoOwner;
[Landroid.content.pm.ActivityInfo;
[Landroid.content.pm.ConfigurationInfo;
[Landroid.content.pm.FeatureGroupInfo;
[Landroid.content.pm.FeatureInfo;
[Landroid.content.pm.InstrumentationInfo;
[Landroid.content.pm.PathPermission;
- preloadResources()主要預加載的是com.android.internal.R.array.preloaded_drawables和com.android.internal.R.array.preloaded_color_state_lists的,那這些資源的載體是什麼,主要是加載framework-res.apk中的資源,各位也可以反編譯看看。在應用程式中以com.android.internal.R.xxx開頭的資源,便是此時由Zygote加載到記憶體的。
06-04 15:39:01.041 2901 2901 D Zygote : begin preload
06-04 15:39:01.041 2901 2901 I Zygote : Installing ICU cache reference pinning...
06-04 15:39:01.041 2901 2901 I Zygote : Preloading ICU data...
06-04 15:39:01.061 2901 2901 I Zygote : Preloading classes...
06-04 15:39:01.065 2901 2901 W Zygote : Class not found for preloading: [Landroid.view.Display$ColorTransform;
06-04 15:39:01.150 2901 2901 E Typeface: Error mapping font file /system/fonts/DroidSansFallback.ttf
06-04 15:39:01.291 2901 2901 I art : Thread[1,tid=2901,Native,Thread*=0x7f77696a00,peer=0x12c060d0,"main"] recursive attempt to load library "/system/lib64/libmedia_jni.so"
06-04 15:39:01.291 2901 2901 D MtpDeviceJNI: register_android_mtp_MtpDevice
06-04 15:39:01.292 2901 2901 I art : Thread[1,tid=2901,Native,Thread*=0x7f77696a00,peer=0x12c060d0,"main"] recursive attempt to load library "/system/lib64/libmedia_jni.so"
06-04 15:39:01.292 2901 2901 I art : Thread[1,tid=2901,Native,Thread*=0x7f77696a00,peer=0x12c060d0,"main"] recursive attempt to load library "/system/lib64/libmedia_jni.so"
06-04 15:39:01.316 2901 2901 D : Environment::getOverlayDir() = /system/vendor/Default/system/vendor/overlay
06-04 15:39:01.352 2901 2901 W Zygote : Class not found for preloading: android.view.Display$ColorTransform
06-04 15:39:01.352 2901 2901 W Zygote : Class not found for preloading: android.view.Display$ColorTransform$1
06-04 15:39:01.449 2901 2901 I System : Loaded time zone names for "" in 32ms (30ms in ICU)
06-04 15:39:01.469 2901 2901 I System : Loaded time zone names for "en_US" in 19ms (16ms in ICU)
06-04 15:39:01.491 2901 2901 I System : Loaded time zone names for "zh_CN" in 22ms (20ms in ICU)
06-04 15:39:01.518 2901 2901 I Zygote : ...preloaded 4158 classes in 457ms.
06-04 15:39:01.518 2901 2901 I art : VMRuntime.preloadDexCaches starting
06-04 15:39:01.572 1374 1419 E slim_daemon: [NDK] bindNDKSensors: Sensor server is unavailable.
06-04 15:39:01.664 2901 2901 I art : VMRuntime.preloadDexCaches strings total=292199 before=40345 after=40345
06-04 15:39:01.665 2901 2901 I art : VMRuntime.preloadDexCaches types total=24111 before=8040 after=8075
06-04 15:39:01.665 2901 2901 I art : VMRuntime.preloadDexCaches fields total=115222 before=41429 after=41660
06-04 15:39:01.665 2901 2901 I art : VMRuntime.preloadDexCaches methods total=201879 before=83038 after=83782
06-04 15:39:01.665 2901 2901 I art : VMRuntime.preloadDexCaches finished
06-04 15:39:01.666 2901 2901 I Zygote : Preloading resources...
06-04 15:39:01.682 2901 2901 W Resources: Preloaded drawable resource #0x108025d (android:drawable/dialog_background_material) that varies with configuration!!
06-04 15:39:01.683 2901 2901 W Resources: Preloaded drawable resource #0x1080299 (android:drawable/editbox_dropdown_background_dark) that varies with configuration!!
06-04 15:39:01.686 2901 2901 W Resources: Preloaded color resource #0x10600e2 (android:color/material_grey_800) that varies with configuration!!
06-04 15:39:01.686 2901 2901 W Resources: Preloaded drawable resource #0x10802ca (android:drawable/floating_popup_background_dark) that varies with configuration!!
06-04 15:39:01.694 2901 2901 W Resources: Preloaded drawable resource #0x108030c (android:drawable/ic_clear_disabled) that varies with configuration!!
06-04 15:39:01.694 2901 2901 W Resources: Preloaded drawable resource #0x1080311 (android:drawable/ic_clear_normal) that varies with configuration!!
06-04 15:39:01.698 2901 2901 W Resources: Preloaded drawable resource #0x108035a (android:drawable/ic_go) that varies with configuration!!
06-04 15:39:01.704 2901 2901 W Resources: Preloaded drawable resource #0x1080038 (android:drawable/ic_menu_close_clear_cancel) that varies with configuration!!
06-04 15:39:01.707 2901 2901 W Resources: Preloaded drawable resource #0x1080045 (android:drawable/ic_menu_more) that varies with configuration!!
06-04 15:39:01.714 2901 2901 W Resources: Preloaded drawable resource #0x10804f1 (android:drawable/menu_background_fill_parent_width) that varies with configuration!!
06-04 15:39:01.744 2901 2901 W Resources: Preloaded drawable resource #0x1080787 (android:drawable/text_edit_paste_window) that varies with configuration!!
06-04 15:39:01.748 2901 2901 W Resources: Preloaded drawable resource #0x1080096 (android:drawable/toast_frame) that varies with configuration!!
06-04 15:39:01.748 2901 2901 I Zygote : ...preloaded 114 resources in 82ms.
06-04 15:39:01.752 2901 2901 W Resources: Preloaded color resource #0x106010b (android:color/background_cache_hint_selector_material_dark) that varies with configuration!!
06-04 15:39:01.753 2901 2901 W Resources: Preloaded color resource #0x1060110 (android:color/btn_default_material_dark) that varies with configuration!!
06-04 15:39:01.755 2901 2901 I Zygote : ...preloaded 41 resources in 7ms.
06-04 15:39:01.757 2901 2901 D libEGL : loaded /vendor/lib64/egl/libEGL_adreno.so
06-04 15:39:01.773 2901 2901 D libEGL : loaded /vendor/lib64/egl/libGLESv1_CM_adreno.so
06-04 15:39:01.785 2901 2901 D libEGL : loaded /vendor/lib64/egl/libGLESv2_adreno.so
06-04 15:39:01.803 2901 2901 I Zygote : Preloading shared libraries...
06-04 15:39:01.815 2901 2901 I Zygote : Uninstalled ICU cache reference pinning...
06-04 15:39:01.816 2901 2901 I Zygote : Installed AndroidKeyStoreProvider in 2ms.
06-04 15:39:01.830 2901 2901 I Zygote : Warmed up JCA providers in 13ms.
06-04 15:39:01.830 2901 2901 D Zygote : end preload
06-04 15:39:01.831 2901 2901 I art : Starting a blocking GC Explicit
06-04 15:39:01.856 2901 2901 I art : Explicit concurrent mark sweep GC freed 58740(6MB) AllocSpace objects, 134(2MB) LOS objects, 71% free, 3MB/11MB, paused 152us total 24.977ms
06-04 15:39:01.858 2901 2901 I art : Starting a blocking GC Explicit
06-04 15:39:01.870 2901 2901 I art : Explicit concurrent mark sweep GC freed 4484(191KB) AllocSpace objects, 1(24KB) LOS objects, 72% free, 2MB/10MB, paused 127us total 11.445ms
06-04 15:39:01.873 2901 2908 I art : Starting a blocking GC HeapTrim
06-04 15:39:01.876 2901 2901 I art : Starting a blocking GC Background
當經過上述的步驟後,zygote程序内加載了preload()方法中的所有資源,當需要fork新程序時,采用COW(copy-on-write)技術,這裡盜用已不在Android界的gityuan的一張圖來說明:
copy-on-write即寫時拷貝技術,zygote在這裡使用了copy-on-write技術可以提高應用運作速度,因為該種方式對運作在記憶體中的程序實作了最大程度的複用,并通過庫共享有效降低了記憶體的使用量。也就是說當新的App通過fork()建立的的時候不進行記憶體的複制,這是因為複制記憶體的開銷是很大的,此時子程序隻需要共享父程序的記憶體空間即可,因為這個時候他們沒有差異。而當子程序需要需要修改共享記憶體資訊時,此時才開始将記憶體資訊複制到自己的記憶體空間中,并進行修改。感覺很高大上啊,這也就是為啥我們的App裡面也能使用預加載的資源,so庫等。這下大夥明白為啥我們能import com.android.internal.R.xxx的資源了嗎。
2.4 forkSystemServer
該代碼定義在frameworks/base//core/java/com/android/internal/os/ZygoteInit.java,其邏輯主要就是準備參數,并啟動 system_server 程序,後續啟動的Android Java 系統服務都将駐留在該程序中,它是是Android framework核心。這裡主要設定了system_server 程序uid和gid,process name,class name。并且從zygote程序fork新程序後,需要關閉zygote原有的socket,另外,對于有兩個zygote程序情況,需等待第2個zygote建立完成。
private static Runnable forkSystemServer(String abiList, String socketName,
ZygoteServer zygoteServer) {
long capabilities = posixCapabilitiesAsBits(
OsConstants.CAP_IPC_LOCK,
OsConstants.CAP_KILL,
OsConstants.CAP_NET_ADMIN,
OsConstants.CAP_NET_BIND_SERVICE,
OsConstants.CAP_NET_BROADCAST,
OsConstants.CAP_NET_RAW,
OsConstants.CAP_SYS_MODULE,
OsConstants.CAP_SYS_NICE,
OsConstants.CAP_SYS_PTRACE,
OsConstants.CAP_SYS_TIME,
OsConstants.CAP_SYS_TTY_CONFIG,
OsConstants.CAP_WAKE_ALARM,
OsConstants.CAP_BLOCK_SUSPEND
);
/* Containers run without some capabilities, so drop any caps that are not available. */
StructCapUserHeader header = new StructCapUserHeader(
OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
StructCapUserData[] data;
try {
data = Os.capget(header);
} catch (ErrnoException ex) {
throw new RuntimeException("Failed to capget()", ex);
}
capabilities &= ((long) data[0].effective) | (((long) data[1].effective) << 32);
/* Hardcoded command line to start the system server */
String args[] = {//參數準備
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
"--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
"com.android.server.SystemServer",
};
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try {
//用于解析參數,生成目标格式
parsedArgs = new ZygoteConnection.Arguments(args);
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
boolean profileSystemServer = SystemProperties.getBoolean(
"dalvik.vm.profilesystemserver", false);
if (profileSystemServer) {
parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
}
/* Request to fork the system server process */
//fork子程序,用于運作system_server
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.runtimeFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
/* For child process */
if (pid == 0) {//進入子程序system_server
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
zygoteServer.closeServerSocket();
// 完成system_server程序剩餘的工作
return handleSystemServerProcess(parsedArgs);
}
return null;
}
06-04 15:39:01.920 2901 2901 I Zygote : System server process 2910 has been created
06-04 15:39:01.923 2901 2901 I Zygote : Accepting command socket connections
06-04 15:39:01.940 2910 2910 I Zygote : Process: zygote socket opened, supported ABIS: armeabi-v7a,armeabi
06-04 15:39:01.941 2910 2910 I InstallerConnection: connecting...
06-04 15:39:01.942 771 771 I : new connection
06-04 15:39:01.945 2910 2910 I InstallerConnection: disconnecting...
06-04 15:39:01.945 771 771 E : eof
06-04 15:39:01.945 771 771 E : failed to read size
06-04 15:39:01.945 771 771 I : closing connection
06-04 15:39:01.955 2910 2910 V appproc : App process: starting thread pool.
06-04 15:39:01.959 2910 2910 I SystemServer: Entered the Android system server!
這裡我有一個疑問就是設定的system_server程序的uid=1000,gid=1000可是實際ps檢視的并不是,這個希望大夥一起探讨。各位不好意思誤導大夥了,這裡的USER為sytem就代表uid為1000,這裡把PID和UID混為一談了。
127|msm8953_64:/ # ps 1515
USER PID PPID VSIZE RSS WCHAN PC NAME
system 1515 762 2384060 133700 SyS_epoll_ 0000000000 S system_server
2.5 ZygoteServer.runSelectLoop
該代碼定義在frameworks/base/core/java/com/android/internal/os/ZygoteServer.java,在這個階段zygote将進入循環狀态等待AMS來和zygote進行通信,進而孵化新的App。
Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
//sServerSocket是socket通信中的服務端,即zygote程序。儲存到fds[0]
fds.add(mServerSocket.getFileDescriptor());
peers.add(null);
while (true) {
//每次循環,都重新建立需要監聽的pollFds
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 {
//處理輪詢狀态,當pollFds有事件到來則往下執行,否則阻塞在這裡
Os.poll(pollFds, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
/*注意這裡是倒序處理的,網上有的部落格說是優先處理已建立連接配接的資訊,後處理建立連接配接的請求
* 我覺得這個表述不是很正确,我覺得采用倒序是為了先處理已經建立連接配接的請求,但是這個優先反而是後面建立連接配接的請求有資料到來是優先處理了
* 然後接着最後處理sServerSocket,此時即有新的用戶端要求建立連接配接
*/
for (int i = pollFds.length - 1; i >= 0; --i) {
//采用I/O多路複用機制,當接收到用戶端發出連接配接請求 或者資料處理請求到來,則往下執行;
// 否則進入continue,跳出本次循環。
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
if (i == 0) {
//即fds[0],代表的是sServerSocket因為它最先加入,則意味着有用戶端連接配接請求;
// 則建立ZygoteConnection對象,并添加到fds。
ZygoteConnection newPeer = acceptCommandPeer(abiList);
//加入到peers和fds,下一次也開始監聽
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
try {
//i>0,則代表通過socket接收來自對端的資料,并執行相應操作
ZygoteConnection connection = peers.get(i);
final Runnable command = connection.processOneCommand(this);
if (mIsForkChild) {
// We're in the child. We should always have a command to run at this
// stage if processOneCommand hasn't called "exec".
if (command == null) {
throw new IllegalStateException("command == null");
}
return command;
} else {
// We're in the server - we should never have any commands to run.
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(i);
fds.remove(i);//處理完則從fds中移除該檔案描述符
}
}
} catch (Exception e) {
if (!mIsForkChild) {
// 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);
// Make sure the socket is closed so that the other end knows immediately
// that something has gone wrong and doesn't time out waiting for a
// response.
ZygoteConnection conn = peers.remove(i);
conn.closeSocket();
fds.remove(i);
} else {
// We're in the child so any exception caught here has happened post
// fork and before we execute ActivityThread.main (or any other main()
// method). Log the details of the exception and bring down the process.
Log.e(TAG, "Caught post-fork exception in child process.", e);
throw e;
}
} finally {
// Reset the child flag, in the event that the child process is a child-
// zygote. The flag will not be consulted this loop pass after the Runnable
// is returned.
mIsForkChild = false;
}
}
}
}
}
從上面的代碼可以看出,Zygote采用高效的I/O多路複用機制,保證在沒有用戶端連接配接請求或資料處理時休眠,否則響應用戶端的請求。從前面可以看到初始時fds中僅有server socket,是以當有資料到來時,将執行i等于0的分支。此時,顯然是需要建立新的通信連接配接,是以acceptCommandPeer将被調用。讓我們接着分析看看它究竟幹了些啥!
2.6 ZygoteServer.acceptCommandPeer
讓我們接着分析這段代碼,如下:
private ZygoteConnection acceptCommandPeer(String abiList) {
try {
// socket程式設計中,accept()調用主要用在基于連接配接的套接字類型,比如SOCK_STREAM和SOCK_SEQPACKET
// 它提取出所監聽套接字的等待連接配接隊列中第一個連接配接請求,建立一個新的套接字,并傳回指向該套接字的檔案描述符
// 建立立的套接字不在監聽狀态,原來所監聽的套接字的狀态也不受accept()調用的影響,這個就是套接字程式設計的基礎了
return createNewConnection(mServerSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
}
}
protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
throws IOException {
return new ZygoteConnection(socket, abiList);
}
通過上面的代碼我們可以看到,acceptCommandPeer主要是基礎的socket套接字程式設計,調用了server socket的accpet函數等待用戶端的連接配接。當有新的連接配接建立時,zygote程序将會建立出一個新的socket與其通信,并将該socket加入到fds中。是以一旦和用戶端程序的通信連接配接建立後,fds中将會有多個socket至少會有兩個。
當poll監聽到這一組sockets上有資料到來時,就會從阻塞中恢複。于是,我們需要判斷到底是哪個socket收到了資料。
2.7 ZygoteConnection.processOneCommand
該代碼定義在frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java中,解析socket用戶端即AMS傳遞過來的參數,然後調用forkAndSpecialize建立App程序。
Runnable processOneCommand(ZygoteServer zygoteServer) {
String args[];
Arguments parsedArgs = null;
FileDescriptor[] descriptors;
try {
//讀取socket用戶端發送過來的參數清單
args = readArgumentList();
descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) {
...
throw new IllegalStateException("IOException on command socket", ex);
}
...
//将socket用戶端傳遞過來的參數,解析成Arguments對象格式
parsedArgs = new Arguments(args);
...
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
parsedArgs.instructionSet, parsedArgs.appDataDir);
try {
if (pid == 0) {
// in child
//子程序執行
zygoteServer.setForkChild();
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
return handleChildProc(parsedArgs, descriptors, childPipeFd,
parsedArgs.startChildZygote);
} else {
// In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
//父程序執行
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
handleParentProc(pid, descriptors, serverPipeFd);
return null;
}
} finally {
IoUtils.closeQuietly(childPipeFd);
IoUtils.closeQuietly(serverPipeFd);
}
}
好了到此處runSelectLoop已經講解完畢了,我們可以看出它采用的是倒序的方式進行輪詢。且由于server socket第一個被加入到fds,是以它是最後被輪詢到的。是以最後輪詢到的socket才需要處理建立連接配接的操作;其它socket收到資料時,僅需要調用zygoteConnection的processOneCommand函數執行資料對應的操作。若一個連接配接處理完所有對應消息後,該連接配接對應的socket和連接配接等将被移除。
總結
到這裡zygote啟動就基本告一段落了,zygote啟動的調用流程圖如下所示:
細數下來,zygote程序啟動主要幹了如下的相關大事:
- 解析init.zygotexxx.rc傳遞過來的參數,建立AppRuntime并調用AppRuntime.start()方法;
- 調用AndroidRuntime的startVM()方法建立虛拟機,再調用startReg()注冊JNI函數
- 虛拟機和JNI環境建構後以後,通過JNI方式調用ZygoteInit.main(),正式進入的Java世界
- 調用registerServerSocketFromEnv()建立socket通道,zygote作為通信的服務端,用于響應用戶端請求,這裡大夥可以思考一個問題就是為啥用的是zygote通信而不是binder
- preload()預加載通用類、drawable和color資源、openGL以及共享庫以及WebView,用于提高app啟動效率
- zygote完畢大部分工作,接下來再通過forkSystemServer(),fork得力幫手system_server程序,也是上層framework的運作載體
- zygote功成身退,調用runSelectLoop(),随時待命,當接收到請求建立新程序請求時立即喚醒并執行相應工作。
寫在最後
Android 9 zygote啟動就告一段落了,但是這個隻是一個開端,因為其中的forkSystemServer啟動system_server和forkAndSpecialize啟動App并沒有講解,這個牽涉的東西太多了,現在功力不足以将其寫清楚,是以接下來會修煉内功,在接下來的篇章中力求講上述兩個遺留的問題說透徹。如果對給位有幫助歡迎點贊一個,如果寫得有問題也歡迎多多指正。未完待續,下個篇章再見。好了system_server啟動的部落格終于來了,參見Android 9 (P)系統啟動之SystemServer大揭秘上篇章!