本文分析基於Android R
java
一說到應用啓動,估計大夥兒就會想到zygote進程。確實,正如其中文釋義「受精卵」同樣,其主要的做用就是孵化出一個又一個的應用進程。android
傳統的應用啓動模式由system_server中的AMS接收請求,以後經過socket告知zygote,讓其完成fork動做,這樣新進程便建立出來。不過從Android Q(10)開始,Google引入了一種新的機制:USAP(Unspecialized App Process)。經過prefork的方式提早建立好一批進程,當有應用啓動時,直接將已經建立好的進程分配給它,從而省去了fork的動做,所以能夠提高性能。markdown
這種機制在AOSP的源碼中默認是關閉的,但估計不少手機廠家已經提早嚐鮮了。session
當開啓USAP的功能後,zygote會維護一個進程池,其中最多可容納10個USAP進程。如下是根據圖示羅列的詳細工做流程。socket
[frameworks/base/core/java/com/android/internal/os/Zygote.java]oop
635 private static Runnable usapMain(LocalServerSocket usapPoolSocket, 636 FileDescriptor writePipe) {
637 final int pid = Process.myPid();
638 Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32");
639
640 LocalSocket sessionSocket = null;
641 DataOutputStream usapOutputStream = null;
642 Credentials peerCredentials = null;
643 ZygoteArguments args = null;
644
645 // Change the priority to max before calling accept so we can respond to new specialization
646 // requests as quickly as possible. This will be reverted to the default priority in the
647 // native specialization code.
648 boostUsapPriority(); <================= 提高進程調度優先級
649
650 while (true) {
651 try {
652 sessionSocket = usapPoolSocket.accept();
複製代碼
Process.start()
。當決定採用USAP的方式啓動時,system_server便會發起socket通訊,將全部啓動參數發送過去。因爲此時有10個USAP進程都在等待同一個socket端口,所以系統底層會隨機喚醒一個進程來處理這次通訊。[frameworks/base/core/java/android/os/ZygoteProcess.java]性能
495 try (LocalSocket usapSessionSocket = zygoteState.getUsapSessionSocket()) {
496 final BufferedWriter usapWriter =
497 new BufferedWriter(
498 new OutputStreamWriter(usapSessionSocket.getOutputStream()),
499 Zygote.SOCKET_BUFFER_SIZE);
500 final DataInputStream usapReader =
501 new DataInputStream(usapSessionSocket.getInputStream());
502
503 usapWriter.write(msgStr); <============== 發起socket請求
504 usapWriter.flush();
505
506 Process.ProcessStartResult result = new Process.ProcessStartResult();
507 result.pid = usapReader.readInt(); <=========== 等待USAP進程將本身的pid告知system_server
複製代碼
specializeAppProcess
來完成「騰籠換鳥」的工做。最終調入ActivityThread.main
方法中去,進而完成應用的啓動。當該進程退出時,它不會回到USAP Pool中,而是直接被zygote回收。zygote接收到SIGCHLD信號後,會調用SigChldHandler
進行處理。過程當中會經過socket將回收的進程數告知zygote。目前這個信息未被使用,可能將來會有些新的設計。測試
隨着啓動的應用愈來愈多,USAP Pool中的進程將會不斷消耗,所以便會涉及到從新填充(refill)的過程。ui
USAP Pool中設定了兩個閾值,分別對應兩種refill的方式。spa
每當USAP進程被使用時,它都會經過pipe將本身的pid告知zygote。這樣zygote就能夠將它從pool中刪去。刪去以後,zygote會去檢查pool中剩餘(空閒)進程的數量。當數量不超過一半(5)時,便會發起一次refill事件來fork出新的進程填充到pool中去。
真實的refill動做是滯後3秒的,這樣能夠和應用啓動過程間隔開。由於當zygote接收到USAP進程的pid時,也意味着USAP進程正要執行specializeAppProcess
,爲了不refill過程和應用啓動過程搶奪系統資源(CPU資源)從而影響啓動速度,原生設計中採用延時執行的方式。
[frameworks/base/core/java/android/os/ZygoteProcess.java]
731 try {
732 ByteArrayOutputStream buffer =
733 new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES);
734 DataOutputStream outputStream = new DataOutputStream(buffer);
735
736 // This is written as a long so that the USAP reporting pipe and USAP pool event FD
737 // handlers in ZygoteServer.runSelectLoop can be unified. These two cases should
738 // both send/receive 8 bytes.
739 outputStream.writeLong(pid);
740 outputStream.flush();
741
742 Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); <======= 往zygote發送pid
743 } catch (Exception ex) {
744 Log.e("USAP",
745 String.format("Failed to write PID (%d) to pipe (%d): %s",
746 pid, writePipe.getInt$(), ex.getMessage()));
747 throw new RuntimeException(ex);
748 } finally {
749 IoUtils.closeQuietly(writePipe);
750 }
751
752 specializeAppProcess(args.mUid, args.mGid, args.mGids, <================ 應用後續啓動過程
753 args.mRuntimeFlags, rlimits, args.mMountExternal,
754 args.mSeInfo, args.mNiceName, args.mStartChildZygote,
755 args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
756 args.mPkgDataInfoList, args.mWhitelistedDataInfoList,
757 args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);
複製代碼
在Delay Refill的3秒內,系統有可能會有新的啓動請求。當USAP Pool中的進程不斷被消耗,以致於消耗殆盡時,這時便須要另外一種機制,來保證USAP Pool的正常輪轉。
當USAP Pool中最後一個進程被用掉後,zygote會發起一次immediate fork。因爲此次fork在時間上和應用啓動過程衝突,因此zygote只fork出一個進程,從而將影響降到最低。新fork出的進程會當即補充到pool中,這樣接下來再有應用啓動時,不會落入無USAP可用的境地。
此外,zygote會再安排一次Delayed Refill用於完整填充,多數狀況下這一步沒有必要(以前有一次Delayed Refill正在執行的路上),但加上它更加保險。
USAP的開啓/關閉都經過property來實現。
一種方式是在build.prop中增長一行。
persist.device_config.runtime_native.usap_pool_enabled=true
複製代碼
另外一種方式是獲取root權限後,調用setprop設置。
setprop persist.device_config.runtime_native.usap_pool_enabled true
複製代碼
開啓和關閉的過程都是動態的,但生效的時機較爲有趣。只有等property修改完後再一次啓動應用時,10個USAP進程纔會建立出來。關閉的時機也同樣,只不過zygote會給空閒的USAP進程發送SIGTERM信號來結束它的生命,同時清空pool。
整體而言,USAP的機制較爲簡單,只是源碼中的socket/pipe名稱容易混亂,羅列以下。
名稱 | 做用 |
---|---|
名爲"usap_pool_primary"的socket | system_server經過它將啓動參數發送給USAP進程 |
gUsapPoolEventFD ZygoteServer.mUsapPoolEventFD |
SigChldHandler經過它將已退出的進程數發送給zygote進程 |
(gUsapTable中元素的)read_pipe_fd | USAP進程經過它將自身pid發送給zygote進程 |
名爲"zygote"的socket | system_server經過它將命令參數發送給zygote進程 |
至於此項機制到底能帶來多少性能提高,筆者還沒有測試過。若是有手機廠家的夥伴掌握了一手測試數據,不妨在評論裏分享下😊。