kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
--- title: 'Android 啟動過程 - Zygote 進程' disqus: kyleAlien --- Zygote 進程 === [**init 進程**](https://hackmd.io/pJpAeKAeRea0VIW3oI-mXw?view) 會透過分析 .rc 檔案來 `fork` + `execv` Zygote 進程 ## OverView of Content [TOC] ## 系統進程之間 - 關係 這個章節著重講解的是 **++Zygote 進程++**,但了解它的來源也是相當重要的,請參考下圖 > ![](https://i.imgur.com/ZNKX6wT.png) ### Zygote 進程 - 概述 * Zygote 進程又稱回 **孵化器**,之後啟動的 APP 進程 or SystemServer 都是以 **Zygote 作為模板來 fork,但 ++不執行 execv++**,因為它會在前面創建幾個重要的零件 1. **DVM、ART 虛擬機** 2. **加載系統基礎資源 android framework** * **Zygote 只是該服務的名稱,但並不是要執行的檔案**,**`app_process` 才是 init 進程要執行的檔案** :::info * Zygote 是一個 **C/S 模型** (使用 Socket) **Zygote 進程作為服務端 (主要負責創建 Java Vm、加載系統資源、啟動 SystemServer 進程)** 其他客戶端應用程式若要啟動 (eg. AMS 就是客戶端),就會對 Zygote 發出請求,而 Zygote 就會 fork 出一個新進程 (forkAndSpecialize) ::: > ![](https://i.imgur.com/31cwX2M.png) * 從 `init.rc` 被 init 進程分析後,觸發 `app_main.cpp` 的流程圖 > ![](https://i.imgur.com/HZstNk8.png) ### 透過 init.rc 啟動 Zygote * 關於 init 進程如何分析 `init.rc` 並執行可以參考 [**Android 啟動過程 - init 進程**](https://hackmd.io/zhLTuxZ-RjewJEhDuC73uQ#Zygote-%E6%9C%8D%E5%8B%99),以下以 `init.zygote64_32.rc` 為例 ```shell= # /rootdir/init.zygote64_32.rc # 1. Service Name = zygote # 2. Path = system/bin/app_process64,app_process64 是資料夾 # 3. Argument: # -Xzygote /system/bin --zygote --start-system-server # 主 zygote service zygote /system/bin/app_process64 \ -Xzygote /system/bin --zygote --start-system-server # 4. 分類為 main 群組 class main priority -20 user root group root readproc reserved_disk ## 啟動兩個 Socket,權限為 660 socket zygote stream 660 root system socket usap_pool_primary stream 660 root system onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse ... # zygote_secondary 為輔助 service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload class main priority -20 user root group root readproc reserved_disk socket zygote_secondary stream 660 root system socket usap_pool_secondary stream 660 root system onrestart restart zygote task_profiles ProcessCapacityHigh MaxPerformance ``` * 從上面可以看到啟動的路徑是 `app_process64`,這在 `frameworks/base/cmds/app_process` 中,所以先來看 [**/cmds/app_process**](https://android.googlesource.com/platform/frameworks/base/+/master/cmds/app_process) 資料夾內的 [**Android.bp 檔**](https://android.googlesource.com/platform/frameworks/base/+/master/cmds/app_process/Android.bp) > ![](https://i.imgur.com/MBu9xFQ.png) ### Zygote 進程啟動 Socket * 透過 init 進程分析 `init.rc` 檔後,會將 Zygote 要啟動的 Socket 參數也一併紀錄,並在啟動 Zygote (或指定服務) 時,順代啟動 Socket :::success * 分析過程可以參考 [**ParseLineSection 處理 section 參數 - 添加 Socket**](https://hackmd.io/zhLTuxZ-RjewJEhDuC73uQ#ParseLineSection-%E8%99%95%E7%90%86-section-%E5%8F%83%E6%95%B8---%E6%B7%BB%E5%8A%A0-Socket) ::: * C++ 啟動 [**service**](https://cs.android.com/android/platform/superproject/+/master:system/core/init/service.cpp) 的過程如下:創建必要 Socket,再來主要是透過 `fork` + `exec` 這兩個 SystemCall 在 init 進程開啟一個新進程 ```cpp= // /init/service.cpp Result<void> Service::StartIfNotDisabled() { // 判斷是否 Disable if (!(flags_ & SVC_DISABLED)) { return Start(); } else { flags_ |= SVC_DISABLED_START; } return {}; } Result<void> Service::Start() { ... 省略部分判斷 pid_t pid = -1; if (namespaces_.flags) { pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr); } else { // 使用 fork 創建子進程 pid = fork(); } std::vector<Descriptor> descriptors; for (const auto& socket : sockets_) { // 透過 Create 函數創建 Socket 物件 // @ 分析 Create if (auto result = socket.Create(scon); result.ok()) { descriptors.emplace_back(std::move(*result)); } else { ... log msg } } ... 省略部分 if (pid == 0) { umask(077); ... 省略部分 // 執行 ececv 函數,並啟動 Service 子進程 if (!ExpandArgsAndExecv(args_, sigstop_)) { PLOG(ERROR) << "cannot execv('" << args_[0] << "'). See the 'Debugging init' section of init's README.md for tips"; } _exit(127); } } ``` * 查看 [**service_utils**](https://cs.android.com/android/platform/superproject/+/master:system/core/init/service_utils.cpp)#Create 函數:呼叫 `CreateSocket` 函數 (下面再說)、`Descriptor` 函數則會創建一個 `ANDROID_SOCKET_`+`<name>` 的檔案描述 > 目前是創建 `ANDROID_SOCKET_zygote` 檔案描述 ```cpp= // socket.h #define ANDROID_SOCKET_ENV_PREFIX "ANDROID_SOCKET_" // ---------------------------------------------------------------- // service_utils.cpp Result<Descriptor> SocketDescriptor::Create(const std::string& global_context) const { const auto& socket_context = context.empty() ? global_context : context; // 查看 CreateSocket auto result = CreateSocket(name, type | SOCK_CLOEXEC, passcred, listen, perm, uid, gid, socket_context); if (!result.ok()) { return result.error(); } return Descriptor(ANDROID_SOCKET_ENV_PREFIX + name, unique_fd(*result)); } ``` * [**util**](https://cs.android.com/android/platform/superproject/+/master:system/core/init/util.cpp)#**`CreateSocket` 函數**:這個函數才是真正創建 Socket 的地方,這裡需要做的事情如下 1. 創建 `socket` 2. 創建一個 `sockaddr_un`,其類型為 `AF_UNIX`,之後會創建一個 `"/dev/socket/<name>"` 的虛擬裝置 > 目前是創建 `/dev/socket/socket` 虛擬裝置檔案 :::info * **AF_UNIX** AF_UNIX 型態的 Socket 是用來執行畚箕處理程序間的通訊標誌 ::: 3. 使用 `chown`、`chmod` 設定該 Socket 檔案權限 > 目前 Zygote 設定為 660 ```cpp= // socket.h #define ANDROID_SOCKET_ENV_PREFIX "ANDROID_SOCKET_" #define ANDROID_SOCKET_DIR "/dev/socket" // ---------------------------------------------------------------- // util.cpp Result<int> CreateSocket(const std::string& name, // 在 init.rc 設定的名稱 int type, // 當前是 stream bool passcred, bool should_listen, mode_t perm, uid_t uid, gid_t gid, const std::string& socketcon) { ... 省略部分 // 1. 創建 `socket` android::base::unique_fd fd(socket(PF_UNIX, type, 0)); if (fd < 0) { return ErrnoError() << "Failed to open socket '" << name << "'"; } ... // 2. 創建一個 `sockaddr_un` struct sockaddr_un addr; memset(&addr, 0 , sizeof(addr)); addr.sun_family = AF_UNIX; // 處理本機通訊 snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR "/%s", name.c_str()); ... int ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr)); ... // 修改檔案權限 if (lchown(addr.sun_path, uid, gid)) { return ErrnoError() << "Failed to lchown socket '" << addr.sun_path << "'"; } if (fchmodat(AT_FDCWD, addr.sun_path, perm, AT_SYMLINK_NOFOLLOW)) { return ErrnoError() << "Failed to fchmodat socket '" << addr.sun_path << "'"; } ... return fd.release(); } ``` ### Zygote - app_process - [Android.bp](https://android.googlesource.com/platform/frameworks/base/+/master/cmds/app_process/Android.bp) 檔 (觸發 app_main.cpp) :::info 有關 **==Bp 檔簡單來說就是替換 Android.mk 的組態檔==**,詳細使用 & 介紹可以看這一篇 [**文章**](https://www.gushiciku.cn/pl/pSrC/zh-tw) ::: * **Android.bp** 檔案中的模組,以 ++模組型別++ 開頭,後面跟一組 name: "value",格式的屬性 (Like Json Format) ```json= // Example cc_binary { // 每個模塊都要有 name 屬性 name: "gzip", // 指定建構模組的原始檔 srcs: ["src/test/minigzip.c"], shared_libs: ["libz"], stl: "none", } ``` * 以下就是 Android.bp 建構 Multilib (32、64 位元) 的一個腳本,詳細請看註釋 ```json= // /cmds/app_process/Android.bp ... cc_binary { name: "app_process", // 觸發的檔案名稱 srcs: ["app_main.cpp"], // 建構的版本,目前分為 32/64 位元 multilib: { lib32: { suffix: "32", // 串接在後面 }, lib64: { suffix: "64", // 串接在後面 }, }, version_script: "version-script.txt", // 依賴的厙 shared_libs: [ "libandroid_runtime", "libbinder", "libcutils", "libdl", "libhidlbase", "liblog", "libnativeloader", "libsigchain", "libutils", "libwilhelm", ], // 準備建構的版本,both 代表 32/64 都建構 compile_multilib: "both", // C 語言的設定 cflags: [ "-Wall", "-Werror", "-Wunused", "-Wunreachable-code", ], sanitize: { memtag_heap: true, }, } ``` > ![](https://i.imgur.com/DT0GOm4.png) ## Zygote 啟動過程 `app_main.cpp#main` 中,主要做的就是參數解析 (**解析 `rc 檔` 傳入的參數**),並 **分成 ++2 種++ 啟動模式** 1. Zygote 模式: 初始化 Zygote 進程 2. Application 模式: 啟動普通的 APP 進程 (傳遞的參數有 class name、class 參數) 而上述兩者最終都會執行 **`AndroidRuntime#start` 方法** > ![](https://i.imgur.com/HQwNibe.png) ### Zygote 服務 - 分析 rc 傳入的參數 * **`app_process` 是透過 `Android.bp 檔` 所喚醒**,接下來分析 `app_main.cpp` 檔案的 main 函數 ([**app_main.cpp**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/cmds/app_process/app_main.cpp) 中),**main 函數會先分析 rc 檔案所傳入的 argument** ```shell= # /rootdir/init.zygote64.rc # 1. Service Name = `zygote` # 2. Path = `system/bin/app_process64` # 3. Argument: `-Xzygote /system/bin --zygote --start-system-server` # # (這些參數會傳入 app_main 進行分析) service zygote /system/bin/app_process64 \ -Xzygote /system/bin --zygote --start-system-server ... 省略部分 ``` | argument | 功能 | | -------- | -------- | | -\-zygote | 當前進程用於承載 zygote | | -\-start-system-server | 是否需要啟動 system server | | -\-application | 啟動進入獨立的程序模式 (一般 APP 啟動) | | -\-nice-name= | 對於進程的別名 | ```cpp= // /cmds/app_process/app_main.cpp int main(int argc, char* const argv[]) { ... bool zygote = false; bool startSystemServer = false; bool application = false; String8 niceName; String8 className; ++i; // Skip unused "parent dir" argument. // 分析 rc 傳入的 argument while (i < argc) { const char* arg = argv[i++]; // strcmp 比較字串無差異就返回 0 if (strcmp(arg, "--zygote") == 0) { // 當前進程是否用於承載 zygote zygote = true; niceName = ZYGOTE_NICE_NAME; } else if (strcmp(arg, "--start-system-server") == 0) { // 是否用於啟動 system service startSystemServer = true; } else if (strcmp(arg, "--application") == 0) { application = true; } else if (strncmp(arg, "--nice-name=", 12) == 0) { // 限制長度為 12 niceName.setTo(arg + 12); } else if (strncmp(arg, "--", 2) != 0) { className.setTo(arg); break; } else { --i; break; } } ... 先暫時省略下半部 } ``` ### AndroidRuntime - 啟動 Java ZygoteInit 類 :::success * AppRuntime#start 這個方法就會調用到 Java 層代碼,並透過 AndroidRuntime 做到以下工作的初始化 (當然不只這些) 1. 啟動 JVM 2. 註冊 JNI 3. Java Heap 4. ClassLoader ::: * **由於 ZygoteInit 是 java 語言**,所以 AndroidRuntime.cpp 要透過 JNI 來調用到 Java 層,啟動 Android Runtime 詳細啟動請看 [**Runtime 篇**](https://hackmd.io/SKv3cRkNTl66owHVhVG8Ow?view) ```cpp= // /cmds/app_process/app_main.cpp int main(int argc, char* const argv[]) { ... // argv[0] 是啟動名稱,eg. zygote AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); int i; ...依照分析的參數啟動 Android Runtime // init.zygote64.rc 傳入參數分析過後,zygote = true if (zygote) { // 啟動 Zygote 服務 // runtime 是 AndroidRuntime 對象 // 啟動 ZygoteInit 類 runtime.start("com.android.internal.os.ZygoteInit", args, zygote); } else if (className) { // 啟動指定 Class runtime.start("com.android.internal.os.RuntimeInit", args, zygote); } else { ... } } ``` * 目前的情況是 `app_main.cpp` **對 AppRuntime 傳入 zygote + class 路徑** 後,執行 **AppRuntime#`start` 函數** > runtime.start("com.android.internal.os.ZygoteInit", args, zygote) ```cpp= // app_main.cpp // AppRuntime 繼承於 AndroidRuntime class AppRuntime : public AndroidRuntime { ... 省略 } ``` 1. **startVm 函數**:**ZygoteInit 就會啟動在 Java 虛擬機上**(透過 [**AndroidRuntime**](https://android.googlesource.com/platform/frameworks/base/+/master/core/jni/AndroidRuntime.cpp) 的 start 函數) 2. **startReg 函數**:**啟動 Java 虛擬機註冊 JNI 方法**,其中就包括 Binder 會使用到的 JNI ```cpp= // /core/jni/AndroidRuntime.cpp void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ... JNIEnv* env; // 1. 啟動 Java 虛擬機 if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) { return; } // 啟動後的回調 onVmCreated(env); /* * 2. Register android functions. */ if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); return; } ... 省略部分 } ``` 3. **從 Native 進入 Java 層**:**VM 啟動後就會透過 JNI,進入指定 Java 類 (根據傳入 class name 為主,eg. ZygoteInit.java) 的 ==main 函數==** (Java 的啟動入口) ```cpp= // /core/jni/AndroidRuntime.cpp // 當前狀況 class Name 傳入:"com.android.internal.os.ZygoteInit" void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ... 省略部分 (上面就是啟動 JVM) jclass stringClass; jobjectArray strArray; jstring classNameStr; // 參數 stringClass = env->FindClass("java/lang/String"); assert(stringClass != NULL); strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL); assert(strArray != NULL); // 目標 Java class classNameStr = env->NewStringUTF(className); assert(classNameStr != NULL); // 將目標 class name 設定成 Array 的第一個元素 env->SetObjectArrayElement(strArray, 0, classNameStr); // 設定傳入 main 的參數 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); } /* * 啟動虛擬機 * * 該線程成為虛擬機的主線程,並將在虛擬機退出之前不返回。 */ // 替換傳入的 className "." -> "/" // 轉換後 com/android/internal/os/ZygoteInit char* slashClassName = toSlashClassName(className != NULL ? className : ""); // 找目標 class jclass startClass = env->FindClass(slashClassName); if (startClass == NULL) { // JVM 無法啟動的 Log ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { // 找 class 中的靜態 main 函數 jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { ... log msg } else { env->CallStaticVoidMethod(startClass, startMeth, strArray); #if 0 if (env->ExceptionCheck()) threadExitUncaughtException(env); #endif } } // 釋放指針空間 free(slashClassName); ALOGD("Shutting down VM\n"); // 該進程若是結束就會關閉該 JVM 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"); } ``` > ![](https://i.imgur.com/NvEs7vr.png) ### [ZygoteInit](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/ZygoteInit.java) - 建構 ZygoteServer 對象 * Zygote JVM 啟動後就會執行 ZygoteInit.java 的 main 函數,首先先回顧 `init.zygote64.rc` 傳入的參數(`--start-system-server`),這些參數會傳到 main#argv ```shell= # /rootdir/init.zygote64.rc # 1. Service Name = zygote # 2. Path = system/bin/app_process64 # 3. Argument: -Xzygote /system/bin --zygote --start-system-server service zygote /system/bin/app_process64 \ -Xzygote /system/bin --zygote --start-system-server ... ``` * **在 VM 啟動 ZygoteInit 類後就會先進入 main 函數後**,^1^ 判斷 init.zygote64.rc 傳入參數,^2^ 建構一個 ZygoteServer 對象,^3^ 透過 `forkSystemServer()` 方法啟動 SystemServer 服務進程 ```java= // /android/internal/os/ZygoteInit.java public static void main(String[] argv) { ... try { ... boolean startSystemServer = false; String zygoteSocketName = "zygote"; String abiList = null; boolean enableLazyPreload = false; // 1. 判斷傳入的參數 for (int i = 1; i < argv.length; i++) { // 當前就是傳入 "start-system-server" if ("start-system-server".equals(argv[i])) { // 需要啟動 SystemServer startSystemServer = true; } else if ("--enable-lazy-preload".equals(argv[i])) { // 是否懶加載資源 // 像是 zygote_secondary 為輔助 就有傳入該參數 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)) { zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length()); } else { throw new RuntimeException("Unknown command line argument: " + argv[i]); } } ... // 判斷裝置是否有可使用的 ABI if (abiList == null) { throw new RuntimeException("No ABI list supplied."); } if (!enableLazyPreload) { ... preload(bootTimingsTraceLog); // 預加載資源 (下面解釋) ... } ... // 2. 建構一個 ZygoteServer 對象 // (將 Zygote 進程作為 Socket 的 Server 端) !! zygoteServer = new ZygoteServer(isPrimaryZygote); if (startSystemServer) { // 3. fork System Server 返回一個 Runnable // @ 接下來會 ++分析 forkSystemServer 方法++ Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer); // {@code r == null} in the parent (zygote) process, // and {@code r != null} in the child (system_server) process. if (r != null) { r.run(); // 直接執行 // fork SystemServer 成功後直接跳出 // (跳出後會先跑到 finally return; } } ... // Zygote 進入循環 Loop,等待 AMS 的請求 !! caller = zygoteServer.runSelectLoop(abiList); } catch (Throwable ex) { Log.e(TAG, "System zygote died with exception", ex); throw ex; } finally { if (zygoteServer != null) { // 關閉 socket zygoteServer.closeServerSocket(); } } // caller 是一個 Runnable 類型 if (caller != null) { caller.run(); // 其內容包裝的是 fork 出來的靜態 main 方法 } } ``` :::info 從這裡可以看到 SystemServer 是沒有 Socket 的,它在 Fork Zygote 成功後,就會關閉 Socket ::: * **preload 函數**:預加載的資源,用於加載虛擬機運行時所需的各種資源 ```java= // ZygoteInit.java static void preload(TimingsTraceLog bootTimingsTraceLog) { ... preloadClasses(); // framework.jar preloadResources(); // 加載 Drawable、color、style、array... 等系統預設資源 preloadSharedLibraries(); // 共用 C、C++ Lib preloadTextResources(); // 加載 Text 資源、字體 ... } ``` :::info * **preloadClasses 使用** 像是需要加載的 classes 就會被紀錄在 `framework.jar` 的 preloaded-classed 中 preload 函數其實也是很耗費 手機啟動時間的,但在加載完後,其他 fork 的進程 (一般應用 APP) 都會使用加載好的資源 > 使用虛擬記憶體的 [**Copy-on-write**](https://hackmd.io/ptxTEwCzQBuxv61dGL_lvA#%E5%AF%AB%E5%85%A5%E6%99%82%E8%A4%87%E8%A3%BD-Copy-on-write---%E9%AB%98%E6%95%88%E8%A1%8C%E7%A8%8B%E5%BB%BA%E7%AB%8B) 技術 ::: > ![](https://i.imgur.com/tcCElgP.png) ### [ZygoteInit](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/ZygoteInit.java) - 啟動 SystemServer * 在 ZygoteInit#main 函數中,若是有攜帶 `start-system-server` 參數,那就會執行 **[forkSystemServer 函數](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/ZygoteInit.java#663)**,forkSystemServer 函數則會 fork 出一個子進程 :::success * `Zygote.forkSystemServer` 函數: 最終會使用 UNIX 的 fork 機制,**核心複製當前的 ++分頁表++,創建了一個新記憶體空間** (新進程) ::: * 可以看到內部實現的主要順序是 1. **分析 hardcode 的指令參數** 2. **ZygoteArguments 分析 cmd** 3. **[Zygote](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/Zygote.java)#forkSystemServer 一個新進程**(fork 成功後就可以得到 pid 值) 4. 若 fork 成功 (`forkSystemServer` 方法返回 0 代表成功),**啟動 System Server** ```java= // /internal/os/ZygoteInit.java private static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) { ... // 1. hardcode 指令參數,之後會分析使用這些指令 String[] args = { // 用戶 id "--setuid=1000", // 群組 id "--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,3011", "--capabilities=" + capabilities + "," + capabilities, "--nice-name=system_server", "--runtime-args", "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, // 進程名稱 "com.android.server.SystemServer", }; ZygoteArguments parsedArgs; int pid; try { ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args); try { // 2. ZygoteArguments 分析 cmd parsedArgs = ZygoteArguments.getInstance(commandBuffer); } catch (EOFException e) { throw new AssertionError("Unexpected argument error for forking system server", e); } commandBuffer.close(); ... 省略部分 // 3. fork 一個新進程 pid = Zygote.forkSystemServer( parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, parsedArgs.mRuntimeFlags, null, parsedArgs.mPermittedCapabilities, parsedArgs.mEffectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* For child process */ // pid == 0 代表成功 fork 出子進程 if (pid == 0) { // 目前子進程是 SystemServer 進程 // 如果有備用 Zygote 則進行等待 if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } // 由於 已經 fork 出一個新的進程,所以這個是關閉 SystemServer 的 Socket zygoteServer.closeServerSocket(); // 4. 啟動 System Server 進程 return handleSystemServerProcess(parsedArgs); } // Parent Process (Zygote) 就是走這裡,返回 null return null; } ``` :::success * **`fork` 函數返回值**:使用 `fork` 返回時代表了兩種情況 1. Parent Proc:`fork` 返回得到 ChildProc 的 pid 參數 2. Child Proc:`fork` 返回 0 代表成功,其他則是失敗 ::: > ![](https://i.imgur.com/9BMjRKu.png) ### ZygoteInit 進程 - 創建 Socket 監聽 :::info 目前是仍在 Zygote 進程分析 ::: * 啟動 SystemServer 進程後 **Zygote 進程** 就會使用呼叫 **[ZygoteServer](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/com/android/internal/os/ZygoteServer.java)#runSelectLoop** 方法 (**==SystemServer 進程不會走到 runSelectLoop 方法==**) 1. 手動添加 mZygoteSocket 的 FD 成為該 Array 列表的第一個 (之後會使用到) ```java= // ZygoteServer.java /** * Listening socket that accepts new server connections. */ private LocalServerSocket mZygoteSocket; Runnable runSelectLoop(String abiList) { ArrayList<FileDescriptor> socketFDs = new ArrayList<>(); ArrayList<ZygoteConnection> peers = new ArrayList<>(); // 1. 加入自身 FD (第一個 FD,再輪詢時會使用到) socketFDs.add(mZygoteSocket.getFileDescriptor()); // 配合 mZygoteSocket 添加一個 null peers.add(null); mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP; .... } ``` :::info * **FD (mZygoteSocket)是透過 name**,到全域系統中找到對應的 Socket (透過系統環境變數中取得 `System.getenv()` > **目前的 name 就是 `ANDROID_SOCKET_zygote`,也 ==就是取得 Native Zygote 創建的 Socket==** > > ![](https://i.imgur.com/mlXgZ6M.png) ::: 2. 透過 while 無限循環,來達成 ZygoteInit 的 ServerSocket 不斷循環監聽 Socket 請求 3. 分配空間給 StructPollfd array ```java= // ZygoteServer.java /** * Listening socket that accepts new server connections. */ private LocalServerSocket mZygoteSocket; Runnable runSelectLoop(String abiList) { // 紀錄 ServerClient ArrayList<FileDescriptor> socketFDs = new ArrayList<>(); ArrayList<ZygoteConnection> peers = new ArrayList<>(); // ServerSocket 加入列表 socketFDs.add(mZygoteSocket.getFileDescriptor()); peers.add(null); ... // 2. 讓呼叫者 (ZygoteInit) 進入無限循環 while (true) { fetchUsapPoolPolicyPropsWithMinInterval(); mUsapPoolRefillAction = UsapPoolRefillAction.NONE; int[] usapPipeFDs = null; StructPollfd[] pollFDs; // 為輪詢結構分配足夠的空間 (給 StructPollfd Array 空間) // (可以是常規 Zygote、WebView Zygote 或 AppZygote)。 if (mUsapPoolEnabled) { // 同時考慮此 Zygote 的 USAP 池的狀態 usapPipeFDs = Zygote.getUsapPipeFDs(); pollFDs = new StructPollfd[socketFDs.size() + 1 + usapPipeFDs.length]; } else { pollFDs = new StructPollfd[socketFDs.size()]; } // 當前輪巡的 index int pollIndex = 0; for (FileDescriptor socketFD : socketFDs) { // 3-1. 賦予 pollFDs Array 每個元素值 pollFDs[pollIndex] = new StructPollfd(); pollFDs[pollIndex].fd = socketFD; pollFDs[pollIndex].events = (short) POLLIN; ++pollIndex; } final int usapPoolEventFDIndex = pollIndex; if (mUsapPoolEnabled) { // 3-2. 賦予 pollFDs 值 pollFDs[pollIndex] = new StructPollfd(); pollFDs[pollIndex].fd = mUsapPoolEventFD; pollFDs[pollIndex].events = (short) POLLIN; ++pollIndex; // The usapPipeFDs array will always be filled in if the USAP Pool is enabled. assert usapPipeFDs != null; for (int usapPipeFD : usapPipeFDs) { FileDescriptor managedFd = new FileDescriptor(); managedFd.setInt$(usapPipeFD); pollFDs[pollIndex] = new StructPollfd(); pollFDs[pollIndex].fd = managedFd; pollFDs[pollIndex].events = (short) POLLIN; ++pollIndex; } } ... 檢查時間 int pollReturnValue; try { pollReturnValue = Os.poll(pollFDs, pollTimeoutMs); } catch (ErrnoException ex) { throw new RuntimeException("poll failed", ex); } // 當超時,就會返回 0 if (pollReturnValue == 0) { // 或是發出非阻塞輪巡,並且 沒有 FD 準備好 // 就應該重新填充 FD 池 mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP; mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED; } else { ... } } } ``` 4. **判斷若是第一個 FD,則創建新 ZygoteConnection,該 ++ZygoteConnection 用來接收 AMS 創建應用 APP 進程的請求++** ```java= // ZygoteServer.java /** * Listening socket that accepts new server connections. */ private LocalServerSocket mZygoteSocket; Runnable runSelectLoop(String abiList) { ArrayList<FileDescriptor> socketFDs = new ArrayList<>(); ArrayList<ZygoteConnection> peers = new ArrayList<>(); // 2. 讓呼叫者 (ZygoteInit) 進入無限循環 while (true) { // 當超時,就會返回 0 if (pollReturnValue == 0) { ... } else { boolean usapPoolFDRead = false; // 輪巡 不同的 FD (FileDescriptor) while (--pollIndex >= 0) { // 輪巡檢查 if ((pollFDs[pollIndex].revents & POLLIN) == 0) { // 如果不需要輪尋則繼續往下一個檢查 continue; } // 在上面有手動作第一個添加的 FD if (pollIndex == 0) { // Zygote 進程 Socket 已經與 AMS Socket 連線 // 4. 呼叫 acceptCommandPeer 創建新 // ZygoteConnection ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); socketFDs.add(newPeer.getFileDescriptor()); } else if(...) { ... } else { ... } } } ... } ... } // 創建新的 ZygoteConnection,接收單個命令連結 private ZygoteConnection acceptCommandPeer(String abiList) { try { // 創建與 Zygote ServerSocket 連接的 Socket return createNewConnection(mZygoteSocket.accept(), abiList); } catch (IOException ex) { throw new RuntimeException( "IOException during accept()", ex); } } ``` 5. 接收到 ClientSocket 後再透過 [**ZygoteConnection**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/com/android/internal/os/ZygoteConnection.java)#**processCommand** 準備 fork 新進程 (一般的應用 APP),結果會依照不同進程做不同的行為 (如下) - Child fork 成功就會返回要執行的 Runnable(內部其實是包目標 class 的 main 方法),並跳出循環 - Parent 則會返回 null,繼續循環監聽 ```java= // ZygoteServer.java /** * Listening socket that accepts new server connections. */ private LocalServerSocket mZygoteSocket; Runnable runSelectLoop(String abiList) { ArrayList<FileDescriptor> socketFDs = new ArrayList<>(); ArrayList<ZygoteConnection> peers = new ArrayList<>(); // 2. 讓呼叫者 (ZygoteInit) 進入無限循環 while (true) { // 當超時,就會返回 0 if (pollReturnValue == 0) { ... } else { // 輪巡 不同的 FD (FileDescriptor) while (--pollIndex >= 0) { ... if (pollIndex == 0) { ... } else if (pollIndex < usapPoolEventFDIndex) { // Zygote 接收 Session ClientSocket try { ZygoteConnection connection = peers.get(pollIndex); boolean multipleForksOK = !isUsapPoolEnabled() && ZygoteHooks.isIndefiniteThreadSuspensionSafe(); // 5. 透過 ZygoteConnection#processCommand 進行真正 fork // a. Child fork 成功就會返回要執行的 Runnable // b. Parent 則會返回 null // 分析 @ processCommand 方法 final Runnable command = connection.processCommand(this, multipleForksOK); if (mIsForkChild) { // We're in the child. We should always have a command to run at // this stage if processCommand hasn't called "exec". if (command == null) { throw new IllegalStateException("command == null"); } // Child Process 將靜態 main 方法包裝並返回 return command; } else { ... } } /* 省略 catch、finally */ } else { ... 省略部分 } } ... 省略部分 } ... 省略部分 } } ``` * 透過 [**ZygoteConnection**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/com/android/internal/os/ZygoteConnection.java)#processCommand 呼叫到 [**Zygote**](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/Zygote.java#334)#forkAndSpecialize 方法:該方法會以 Zygote 為樣本 fork 出一個新 APP 進程 ```java= // ZygoteConnection.java Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) { ZygoteArguments parsedArgs; try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) { while (true) { try { ... 省略部分 FileDescriptor fd = mSocket.getFileDescriptor(); if (fd != null) { fdsToClose[0] = fd.getInt$(); } FileDescriptor zygoteFd = zygoteServer.getZygoteSocketFileDescriptor(); if (zygoteFd != null) { fdsToClose[1] = zygoteFd.getInt$(); } if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote || !multipleOK || peer.getUid() != Process.SYSTEM_UID) { // @ 分析 forkAndSpecialize 方法 pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName, /* 省略部分參數 */); try { if (pid == 0) { // in child zygoteServer.setForkChild(); zygoteServer.closeServerSocket(); IoUtils.closeQuietly(serverPipeFd); serverPipeFd = null; // child process 呼叫 handleChildProc return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote); } else { // In the parent. A pid < 0 indicates a failure and will be handled in // handleParentProc. IoUtils.closeQuietly(childPipeFd); childPipeFd = null; handleParentProc(pid, serverPipeFd); return null; } } finally { IoUtils.closeQuietly(childPipeFd); IoUtils.closeQuietly(serverPipeFd); } } } /* 省略 catch */ } ... 省略部分 } } // ----------------------------------------------------------------------------------- // Zygote.java static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName /*省略部分入參*/ ) { ZygoteHooks.preFork(); // 最終使用 Linux System call "fork" int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp, pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs, bindMountAppStorageDirs); // fork 成功 if (pid == 0) { // Note that this event ends at the end of handleChildProc, Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); // If no GIDs were specified, don't make any permissions changes based on groups. if (gids != null && gids.length > 0) { NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids)); } } ... 省略部分 return pid; } ``` ## Zygote Fork SystemServer 進程 * 在 ZygoteInit#main 函數中,若是有攜帶 `start-system-server` 參數,那就會執行 **[forkSystemServer 函數](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/ZygoteInit.java#663)**,forkSystemServer 函數則會 fork 出一個子進程 :::success * Zygote.forkSystemServer 函數: 最終會使用 UNIX 的 fork 機制,複製當前的分頁表,創建了一個新記憶體空間 (新進程) ::: * 可以看到內部實現的主要順序是 1. **分析 hardcode 的指令參數** 2. **ZygoteArguments 分析 cmd** 3. **[Zygote](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/Zygote.java)#forkSystemServer 一個新進程**(fork 成功後就可以得到 pid 值) 4. 若 fork 成功 (pid == 0),**啟動 SystemServer 進程** ```java= // /internal/os/ZygoteInit.java private static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) { ... // 1. hardcode 指令參數,之後會分析使用這些指令 String[] args = { // 用戶 id "--setuid=1000", // 群組 id "--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,3011", "--capabilities=" + capabilities + "," + capabilities, "--nice-name=system_server", "--runtime-args", "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, // 進程名稱 "com.android.server.SystemServer", }; ZygoteArguments parsedArgs; int pid; try { ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args); try { // 2. ZygoteArguments 分析 cmd parsedArgs = ZygoteArguments.getInstance(commandBuffer); } catch (EOFException e) { throw new AssertionError("Unexpected argument error for forking system server", e); } commandBuffer.close(); ... 省略部分 // 3. fork 一個新進程 pid = Zygote.forkSystemServer( parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, parsedArgs.mRuntimeFlags, null, parsedArgs.mPermittedCapabilities, parsedArgs.mEffectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* For child process */ // pid == 0 代表成功 fork 出子進程 if (pid == 0) { // 目前子進程是 SystemServer 進程 // 如果有備用 Zygote 則進行等待 if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } // 由於 已經 fork 出一個新的進程,所以這個是關閉 SystemServer 的 Socket zygoteServer.closeServerSocket(); // 4. 啟動 System Server 進程 // @ 分析 handleSystemServerProcess return handleSystemServerProcess(parsedArgs); } // Parent Process (Zygote) 就是走這裡,返回 null return null; } ``` > ![](https://i.imgur.com/9BMjRKu.png) ### nativeForkSystemServer - 真正 fork 進程 * 透過 [**Zygote**](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/Zygote.java)#forkSystemServer 最終會呼叫到 system call fork,真正 fork SystemServer 進程 ```java= // /os/Zygote.java static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { ZygoteHooks.preFork(); int pid = nativeForkSystemServer( uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities); // Set the Java Language thread priority to the default value for new apps. Thread.currentThread().setPriority(Thread.NORM_PRIORITY); ZygoteHooks.postForkCommon(); return pid; } private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); ``` * nativeForkSystemServer 是 Native 方法,既然它是 Native 方法就有對應的 JNI 類,它的 JNI 實現是 [**com_android_internal_os_zygote.cpp**](https://android.googlesource.com/platform/frameworks/base/+/master/core/jni/com_android_internal_os_Zygote.cpp)。最終會透過 Linux systemcall 的 fork 參數進行複製 ```cpp= // /jni/com_android_internal_os_Zygote.cpp static const JNINativeMethod gMethods[] = { ... 省略部分 {"nativeForkSystemServer", "(II[II[[IJJ)I", (void*)com_android_internal_os_Zygote_nativeForkSystemServer}, } static jint com_android_internal_os_Zygote_nativeForkSystemServer( JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { ... 省略部分 // 使用 Linux System call fork current process // 追尋 ForkCommon 函數 pid_t pid = zygote::ForkCommon(env, true, fds_to_close, fds_to_ignore, true); if (pid == 0) { SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true, false, nullptr, nullptr, /* is_top_app= */ false, /* pkg_data_info_list */ nullptr, /* allowlisted_data_info_list */ nullptr, false, false); } else { ... } return pid; } pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, const std::vector<int>& fds_to_close, const std::vector<int>& fds_to_ignore, bool is_priority_fork, bool purge) { ... 省略部分 // 呼叫 System call#fork pid_t pid = fork(); if (pid == 0) { if (is_priority_fork) { // 傳入是 false setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX); } else { setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN); } ... 省略部分 } return pid; } ``` > ![](https://i.imgur.com/JC7tKwT.png) ### handleSystemServerProcess - 啟動 SystemServer 進程 * [**ZygoteInit**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/com/android/internal/os/ZygoteInit.java)#handleSystemServerProcess 做以下事情 1. 創建 or 取得類加載器 `PathClassLoader` 2. 透過 Native 方法 `nativeZygoteInit` 開啟 Binder 通訊 ```java= // ZygoteInit.java private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) { ... 省略部分 if (parsedArgs.mInvokeWith != null) { ... 省略部分 } else { // 1. 創建 PathClassLoader ClassLoader cl = getOrCreateSystemServerClassLoader(); if (cl != null) { // 設定當前線程的 Class Loader Thread.currentThread().setContextClassLoader(cl); } // @ 查看 靜態方法 zygoteInit return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, parsedArgs.mDisabledCompatChanges, parsedArgs.mRemainingArgs, cl); } /* should never reach here */ } public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { ... 省略 Debug 訊息 RuntimeInit.redirectLogStreams(); RuntimeInit.commonInit(); // 啟動 Binder 線程池 ZygoteInit.nativeZygoteInit(); // @ 查看 applicationInit 方法 return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv, classLoader); } private static native void nativeZygoteInit(); ``` 3. 透過 [**RuntimeInit**](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/RuntimeInit.java)#`applicationInit` 方法準備啟動 SystemServer 的 main 方法 ```java= // RuntimeInit.java protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { ... 省略部分 final Arguments args = new Arguments(argv); // The end of of the RuntimeInit event (see #zygoteInit). Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // Remaining arguments are passed to the start class's static main return findStaticMain(args.startClass, args.startArgs, classLoader); } ``` * MethodAndArgsCaller 是 [**RuntimeInit**](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/RuntimeInit.java) 的一個靜態內部類,主要再儲存要 main 的方法 & 傳入的參數,將其包裝 ```java= static class MethodAndArgsCaller implements Runnable { /** method to call */ private final Method mMethod; /** argument array */ private final String[] mArgs; public MethodAndArgsCaller(Method method, String[] args) { mMethod = method; mArgs = args; } public void run() { try { mMethod.invoke(null, new Object[] { mArgs }); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException(ex); } } } ``` * 最終會返回到 ZygoteInit#main 方法,**並運行 App 進程的 main 方法**,到這邊為止 ZygoteInit 結束任務 ```java= // /android/internal/os/ZygoteInit.java public static void main(String[] argv) { ... try { ... 省略部分 if (startSystemServer) { // 返回的 Runnable 就是 MethodAndArgsCaller Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer); // {@code r == null} in the parent (zygote) process, and {@code r != null} in the // child (system_server) process. if (r != null) { // 執行 SystemServer 的 main 方法 r.run(); // 直接執行 // fork SystemServer 成功後直接跳出 (跳出後會先跑到 finally) return; } } } catch (Throwable ex) { Log.e(TAG, "System zygote died with exception", ex); throw ex; } finally { // 再次檢查如果有尚未關閉的 Socket 則關閉 if (zygoteServer != null) { zygoteServer.closeServerSocket(); } } ... } ``` ## Zygote Fork APP 進程 這是接續著 runSelectLoop 函數中的內容繼續分析, **繼續分析 APP fork 出的過程 (不是 init -> Zygote,而是 Zygote -> APP)** :::success fork app 關鍵方法名: **forkAndSpecialize** ::: * 從 [**Zygote.forkAndSpecialize**](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/Zygote.java#334) 開始看,該函數是真正 APP fork 進程的入口,主要分為 `preFork`、`nativeForkAndSpecialize`、`postForkCommon` 三種 Fork 步驟 ```java= // /core/java/com/android/internal/os/Zygote.java static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList, boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) { // 1. 完成堆空間的初始化 ZygoteHooks.preFork(); // 2. 真正孵化進程 // 調用 Linux System call : fork int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp, pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs, bindMountAppStorageDirs); if (pid == 0) { ... } // Set the Java Language thread priority to the default value for new apps. Thread.currentThread().setPriority(Thread.NORM_PRIORITY); // 3. ZygoteHooks.postForkCommon(); return pid; } ``` :::info * nativeForkAndSpecialize 的實現在 [**com_android_internal_os_Zygote.cpp**](https://android.googlesource.com/platform/frameworks/base/+/master/core/jni/com_android_internal_os_Zygote.cpp) ::: ### [preFork](https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/ZygoteHooks.java#134) - 分配堆空間 * 該函數的實現在 [**ZygoteHooks.java**](https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/ZygoteHooks.java) 中 ```java= // /java/dalvik/system/ZygoteHooks.java public static void preFork() { Daemons.stop(); // 呼叫 Native 本地函數 token = nativePreFork(); waitUntilAllThreadsStopped(); } private static native long nativePreFork(); // 本地來分配堆空間 ``` * Native (nativePreFork 方法) 具體實踐在 [**dalvik_system_ZygoteHooks.cc**](https://android.googlesource.com/platform/art/+/master/runtime/native/dalvik_system_ZygoteHooks.cc) 的 [**ZygoteHooks_nativePreFork**](https://android.googlesource.com/platform/art/+/master/runtime/native/dalvik_system_ZygoteHooks.cc#253) 方法 ```java= // /dalvik_system_ZygoteHooks.cc#253 static jlong ZygoteHooks_nativePreFork(JNIEnv* env, jclass) { Runtime* runtime = Runtime::Current(); CHECK(runtime->IsZygote()) << "runtime instance not started with -Xzygote"; // 完成堆空間的初始操作 runtime->PreZygoteFork(); return reinterpret_cast<jlong>(ThreadForEnv(env)); } ``` > ![](https://i.imgur.com/JhFFCH5.png) ### nativeForkAndSpecialize - 真正 fork 進程 * 從名稱上就可以知道使用 Native 函數 ```java= // /java/dalvik/system/ZygoteHooks.java private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList, boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs); ``` * JNI 在 [**com_android_internal_os_Zygote.cpp**](https://android.googlesource.com/platform/frameworks/base/+/master/core/jni/com_android_internal_os_Zygote.cpp) 實現,在這裡會 ^1^ **真正 Fork APP 進程**、^2^ **呼叫 callPostForkChildHooks 函數(孵化進程後續的行為)** ```java= // /jni/com_android_internal_os_Zygote.cpp static const JNINativeMethod gMethods[] = { {"nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/" "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I", (void*)com_android_internal_os_Zygote_nativeForkAndSpecialize}, ... 省略部分 } static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) { // 開始 Fork APP 進程 pid_t pid = zygote::ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore,true); if (pid == 0) { SpecializeCommon(... /*省略參數*/); } return pid; } static void SpecializeCommon(... /*省略參數*/) { ... 省略部分 // 呼叫 callPostForkChildHooks 函數 env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags, is_system_server, is_child_zygote, managed_instruction_set); } pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, const std::vector<int>& fds_to_close, const std::vector<int>& fds_to_ignore, bool is_priority_fork, bool purge) { SetSignalHandlers(); ... 省略部分 pid_t pid = fork(); // 1. 真正 Fork 出進程 if (pid == 0) { if (is_priority_fork) { setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX); } else { setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN); } // The child process. PreApplicationInit(); ... 省略部分 } else { ALOGD("Forked child process %d", pid); } // We blocked SIGCHLD prior to a fork, we unblock it here. UnblockSignal(SIGCHLD, fail_fn); return pid; } ``` :::info 到目前為止,是底層 Linux Fork 出新進程,尚未碰到應用程式邏輯 (下面小節分析) ::: > ![](https://i.imgur.com/IQPOnTp.png) ### handleChildProc - 啟動 APP 進程 * 在 forkAndSpecialize 完成後,繼續執行 **runSelectLoop** 方法 * 在上面 Linux 孵化出新進程後,後續的處理跳回到 ZygoteConnection#processCommand 函數,接續 **使用 `handleChildProc` 方法處理應用程式響應** ```java= // /internal/os/ZygoteConnection.java // 複習一下... Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) { ZygoteArguments parsedArgs; try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) { while (true) { pid = Zygote.forkAndSpecialize(.../*省略參數*/) try { if (pid == 0) { // fork 成功返回 0 // in child zygoteServer.setForkChild(); zygoteServer.closeServerSocket(); IoUtils.closeQuietly(serverPipeFd); serverPipeFd = null; // @ 處理應用程式響應 return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote); } else { // in parent IoUtils.closeQuietly(childPipeFd); childPipeFd = null; handleParentProc(pid, serverPipeFd); return null; } } finally { IoUtils.closeQuietly(childPipeFd); IoUtils.closeQuietly(serverPipeFd); } } } ... 省略部分 } ``` * handleChildProc 方法:一般來說 `parsedArgs.mInvokeWith` 為空,並準備進入啟動進程 (這裡會區分是不是 Zygote 進程) | fork 種類 | 處理方法 | | -------- | -------- | | 一般 APP | ZygoteInit.zygoteInit | | Zygote 進程 | ZygoteInit.childZygoteInit | ```java= // /internal/os/ZygoteConnection.java private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor pipeFd, boolean isZygote) { // 運行到這時,已經關閉 2 個 Zygote Socket,並用 `/dev/null` 替代 // 因為一般進程不需要 Socket 連接 closeSocket(); Zygote.setAppProcessName(parsedArgs, TAG); // End of the postFork event. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); if (parsedArgs.mInvokeWith != null) { WrapperInit.execApplication(parsedArgs.mInvokeWith, parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, VMRuntime.getCurrentInstructionSet(), pipeFd, parsedArgs.mRemainingArgs); // Should not get here. throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); } else { if (!isZygote) { return // 啟動子進程 (一般 APP) // @ 查看 zygoteInit ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, parsedArgs.mDisabledCompatChanges, parsedArgs.mRemainingArgs, null /* classLoader */); } else { // 啟動 Zygote 進程 return ZygoteInit.childZygoteInit( parsedArgs.mRemainingArgs /* classLoader */); } } } ``` > ![](https://i.imgur.com/5c8S8yM.png) :::success * 傳統 Linux fork、Zygote fork 差異 ? 傳統 Linux 內核需要 **fork+exec**,**而 Zygote 並不會執行 exec** (因為它需要用到 ZygoteInit 的 JVM、JNI... 的初始化) 所以在某些情況下會造成障礙 (eg. valgrind 無法監測內存洩漏),而 Android 系統為了該情況特別實現 Wrapper 類,並通過 **parsedArgs.mInvokeWith 來控制** ::: ```java= // /os/ZygoteInit.java public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { ... 省略部分 RuntimeInit.commonInit(); // 設定時區,鍵盤資訊等等... ZygoteInit.nativeZygoteInit(); // 啟動 Binder (之後分析) // @ 查看 applicationInit return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv, classLoader); } // ------------------------------------------------------------------ // RuntimeInit.java protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { ... 省略部分 // @ 查看 findStaticMain return findStaticMain(args.startClass, args.startArgs, classLoader); } protected static Runnable findStaticMain(String className, String[] argv, ClassLoader classLoader) { Class<?> cl; try { // App 來講就是啟動 ActivityThread cl = Class.forName(className, true, classLoader); } /* 省略 catch 拋出 */ Method m; try { m = cl.getMethod("main", new Class[] { String[].class }); } /* 省略 catch */ int modifiers = m.getModifiers(); if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) { throw new RuntimeException( "Main method is not public and static on " + className); } /* * This throw gets caught in ZygoteInit.main(), which responds * by invoking the exception's run() method. This arrangement * clears up all the stack frames that were required in setting * up the process. */ return new MethodAndArgsCaller(m, argv); } ``` * MethodAndArgsCaller 是 [**RuntimeInit**](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/os/RuntimeInit.java) 的一個靜態內部類,主要再儲存要 main 的方法、傳入的參數 ```java= static class MethodAndArgsCaller implements Runnable { /** method to call */ private final Method mMethod; /** argument array */ private final String[] mArgs; public MethodAndArgsCaller(Method method, String[] args) { mMethod = method; mArgs = args; } public void run() { try { mMethod.invoke(null, new Object[] { mArgs }); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException(ex); } } } ``` * 最終會返回到 ZygoteInit#main 方法,**並運行 App 進程的 main 方法**,到這邊為止 ZygoteInit 結束任務 ```java= // /android/internal/os/ZygoteInit.java public static void main(String[] argv) { ... try { ... 省略部分 // 進入循環 Loop,等待 AMS 的請求 !! caller = zygoteServer.runSelectLoop(abiList); } catch (Throwable ex) { Log.e(TAG, "System zygote died with exception", ex); throw ex; } finally { if (zygoteServer != null) { zygoteServer.closeServerSocket(); } } // caller 是一個 Runnable 類型 (MethodAndArgsCaller 類) if (caller != null) { caller.run(); // 其內容包裝的是 fork 出來的靜態 main 方法 } } ``` ## Binder 線程池 在應用 APP 創建的過程中會啟動 Binder 線程池,不管是 SystemServer or 一般 APP 進程,都會執行 Binder 線程池啟動 ### Zygote fork 時啟動 Binder * 複習一下 Zygote 收到 AMS 通知後,fork App 進程後,就會呼叫 `zygoteInit` 函數,並在這裡啟動 Binder (進入 JNI) ```java= public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { ... 省略部分 // @ 查看 nativeZygoteInit ZygoteInit.nativeZygoteInit(); return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv, classLoader); } // 調用 JNI 函數 private static native void nativeZygoteInit(); ``` ### 啟動 Binder 線程池 - startThreadPool :::info App 主要使用 [nativeZygoteInit](https://android.googlesource.com/platform/frameworks/base/+/master/core/jni/AndroidRuntime.cpp#19),最終仍會透過 `startThreadPool` 啟動 BinderThread ::: * 以 SystemServer 為例:SystemServer 與一般 APP 在不同的進程,若要通訊就需要通過 Binder 通訊機制,所以 **ZygoteInit 透過 `nativeZygoteInit` 來開啟 SystemServer 的 Binder 通訊** ```cpp= // AndroidRuntime.cpp int register_com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env) { const JNINativeMethod methods[] = { { "nativeZygoteInit", "()V", (void*) com_android_internal_os_ZygoteInit_nativeZygoteInit }, }; return jniRegisterNativeMethods(env, "com/android/internal/os/ZygoteInit", methods, NELEM(methods)); } // 自身對象 static AndroidRuntime* gCurRuntime = NULL; static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz) { // onZygoteInit 是抽象方法 gCurRuntime->onZygoteInit(); } // ------------------------------------------------------------------------ // /jni/include/android_runtime/AndroidRuntime.h virtual void onZygoteInit() { } ``` :::info * AndroidRuntime 成員 **gCurRuntime** 的初始化,在 AndroidRuntime 建構函數中,而 init 進程 `app_main#main` 在一開始就會創建 AndroidRuntime 對象 ```cpp= // /cmds/app_process/app_main.cpp int main(int argc, char* const argv[]) { ... // argv[0] 是啟動名稱,eg. zygote AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); int i; ... } ``` AndroidRuntime 建構函數就賦予 gCurRuntime 為自身對象 ```cpp= // AndroidRuntime.cpp AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) : mExitWithoutCleanup(false), mArgBlockStart(argBlockStart), mArgBlockLength(argBlockLength) { ... assert(gCurRuntime == NULL); // one per process // 所有的 App 進程的 gCurRuntime 都是 AppRuntime gCurRuntime = this; } ``` ::: * AndroidRuntime#onZygoteInit 是抽象方法,它的實現類是 [**AppRuntime**](https://android.googlesource.com/platform/frameworks/base/+/master/cmds/app_process/app_main.cpp) (**這也是 AppRuntime 的特點,它的 `onZygoteInit` 用來初始化 BinderPool**) ```java= // app_main.cpp class AppRuntime : public AndroidRuntime { public: ... 省略部分 virtual void onZygoteInit() { // 每個進程中只會有一個 ProcessState 實例 sp<ProcessState> proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); // 準備分析 startThreadPool 方法 proc->startThreadPool(); } } ``` * Binder 線程池先了解到這邊,主要是要知道透過 `startThreadPool` 方法就可以創建一個 Thread 並與其他進程通訊 :::info * 每個進程都只能透過 ProcessState#`startThreadPool` 開啟一次線程池 ::: ### 啟動 Binder ThreadPool - startThreadPool * ProcessState#**startThreadPool 函數** * [**ProcessState**](https://android.googlesource.com/platform/frameworks/native/+/master/libs/binder/ProcessState.cpp) 啟動 PoolThread 線程池:這裡需要 ^1^ 為線程池取名,^2^ 產生 PoolThread (線程池) 對象,^3^ 在 [**Threads**](https://android.googlesource.com/platform/frameworks/native/+/jb-dev/libs/utils/Threads.cpp)#run 函數最後會呼叫回 PoolThread#`threadLoop` 函數 :::success * 創建一個 Thread 無限循環與 Binder 通訊 PoolThread#`threadLoop` 函數 會將剛剛創建的 Thread 加入 BinderThread,與 BinderDriver 通訊 ::: ```cpp= // /binder/ProcessState.cpp void ProcessState::startThreadPool() { AutoMutex _l(mLock); if (!mThreadPoolStarted) { mThreadPoolStarted = true; // 啟動線程池的 flag // spawn 產卵 spawnPooledThread(true); // 往下分析,產生一個線程池,注意參數為 true } } void ProcessState::spawnPooledThread(bool isMain) { if (mThreadPoolStarted) { // 1. 為了該線程池取名 String8 name = makeBinderThreadName(); ALOGV("Spawning new pooled thread, name=%s\n", name.string()); // 2. 產生 PoolThread (線程池) 對象 sp<Thread> t = sp<PoolThread>::make(isMain); @ 查看 run t->run(name.string()); } } // 線程池是 Thread 的子類 class PoolThread : public Thread { public: explicit PoolThread(bool isMain) : mIsMain(isMain) { } protected: virtual bool threadLoop() { // @ 最終會呼叫 joinThreadPool 方法 IPCThreadState::self()->joinThreadPool(mIsMain); return false; } const bool mIsMain; }; ``` * [**Thread**](https://cs.android.com/android/platform/superproject/+/master:system/core/libutils/Threads.cpp)#run 函數:創建一個 Thread,並在創建完 Thread 後調用 `threadLoop` 函數,這時就為 **調用到 `joinThreadPool` 函數** ```cpp= // Threads.cpp status_t Thread::run(const char* name, int32_t priority, size_t stack) { ... bool res; if (mCanCallJava) { // @ 查看函數指標 _threadLoop res = createThreadEtc(_threadLoop, this, name, priority, stack, &mThread); } else { // @ 查看函數指標 _threadLoop res = androidCreateRawThreadEtc(_threadLoop, this, name, priority, stack, &mThread); } if (res == false) { ...啟動失敗處理 } return NO_ERROR; } // 函數指標 int Thread::_threadLoop(void* user) { Thread* const self = static_cast<Thread*>(user); ... bool first = true; do { bool result; if (first) { ... if (result && !self->exitPending()) { // 呼叫回 PoolThread 的 threadLoop result = self->threadLoop(); } } else { // 呼叫回 PoolThread 的 threadLoop result = self->threadLoop(); } ... } while(strong != 0); return 0; } ``` ### 進入 Binder ThreadPool - joinThreadPool * IPCThreadState#**joinThreadPool 函數**:進入循環通訊 * [**IPCThreadState**](https://android.googlesource.com/platform/frameworks/native/+/jb-dev/libs/binder/IPCThreadState.cpp) **的 ==joinThreadPool 函數==**:這個函數就會不斷的循環跟 Binder 驅動交流,如同 SM 中的 binder_loop 函數([**talkWithDriver 參考另外一篇**](https://hackmd.io/_8ne10VrSK-mK6nlyxL5RQ?view#IPCThreadState---transact-%E5%82%B3%E9%80%81%E5%91%BD%E4%BB%A4)) * **executeCommand 函數** 則是執行接收的命令,之後分析 ```cpp= // /libs/binder/IPCThreadState.cpp void IPCThreadState::joinThreadPool(bool isMain) // 從 PoolThread 傳近來的參數是 true { // 是否進入主循環 mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER); ... status_t result; do { int32_t cmd; ... // 若 ioctl 無資料則會進入休眠 result = talkWithDriver(); // binder_thread_write & read 與 binder 驅動通訊 // 判斷接收資料是否錯誤 if (result >= NO_ERROR) { size_t IN = mIn.dataAvail(); // 是否有數據可讀 if (IN < sizeof(int32_t)) continue; cmd = mIn.readInt32(); ... // 分析接收到的 Command result = executeCommand(cmd); // 執行對應 cmd } set_sched_policy(mMyThreadId, SP_FOREGROUND); // Let this thread exit the thread pool if it is no longer // needed and it is not the main process thread. if(result == TIMED_OUT && !isMain) { break; } } while (result != -ECONNREFUSED && result != -EBADF); ... mOut.writeInt32(BC_EXIT_LOOPER); talkWithDriver(false); } ``` :::info 這一步結束後,該 APP 進程就準備好,可以跟 BinderDriver 通訊 ::: ## Appendix & FAQ :::info ::: ###### tags: `Android 系統`

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully