Android Framework | 一種新型的應用啓動機制:USAP

本文分析基於Android Rjava

前言

一說到應用啓動,估計大夥兒就會想到zygote進程。確實,正如其中文釋義「受精卵」同樣,其主要的做用就是孵化出一個又一個的應用進程。android

傳統的應用啓動模式由system_server中的AMS接收請求,以後經過socket告知zygote,讓其完成fork動做,這樣新進程便建立出來。不過從Android Q(10)開始,Google引入了一種新的機制:USAP(Unspecialized App Process)。經過prefork的方式提早建立好一批進程,當有應用啓動時,直接將已經建立好的進程分配給它,從而省去了fork的動做,所以能夠提高性能。markdown

這種機制在AOSP的源碼中默認是關閉的,但估計不少手機廠家已經提早嚐鮮了。session

目錄

1. USAP進程的工做流程

當開啓USAP的功能後,zygote會維護一個進程池,其中最多可容納10個USAP進程。如下是根據圖示羅列的詳細工做流程。socket

  1. Zygote首先會fork出10個進程,將其加入到進程池中。被建立出來的USAP進程並不會執行具體邏輯,而是等待socket通訊的到來。不過有一個細節,在等待socket通訊以前,USAP進程會提高調度優先級,這樣若是應用須要啓動時,它能以最快的速度響應。

[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();
複製代碼
  1. USAP進程等待的socket通訊將會什麼時候到來?這取決於應用進程什麼時候啓動。當AMS接收到啓動需求時,其會調用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
複製代碼
  1. USAP進程被喚醒,調用specializeAppProcess來完成「騰籠換鳥」的工做。最終調入ActivityThread.main方法中去,進而完成應用的啓動。

當該進程退出時,它不會回到USAP Pool中,而是直接被zygote回收。zygote接收到SIGCHLD信號後,會調用SigChldHandler進行處理。過程當中會經過socket將回收的進程數告知zygote。目前這個信息未被使用,可能將來會有些新的設計。測試

2. USAP Pool的填充過程

隨着啓動的應用愈來愈多,USAP Pool中的進程將會不斷消耗,所以便會涉及到從新填充(refill)的過程。ui

USAP Pool中設定了兩個閾值,分別對應兩種refill的方式。spa

2.1 Delayed Refill

每當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);
複製代碼

2.2 Immediate Refill

在Delay Refill的3秒內,系統有可能會有新的啓動請求。當USAP Pool中的進程不斷被消耗,以致於消耗殆盡時,這時便須要另外一種機制,來保證USAP Pool的正常輪轉。

當USAP Pool中最後一個進程被用掉後,zygote會發起一次immediate fork。因爲此次fork在時間上和應用啓動過程衝突,因此zygote只fork出一個進程,從而將影響降到最低。新fork出的進程會當即補充到pool中,這樣接下來再有應用啓動時,不會落入無USAP可用的境地。

此外,zygote會再安排一次Delayed Refill用於完整填充,多數狀況下這一步沒有必要(以前有一次Delayed Refill正在執行的路上),但加上它更加保險。

3. 如何開啓/關閉

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。

4. 總結

整體而言,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進程

至於此項機制到底能帶來多少性能提高,筆者還沒有測試過。若是有手機廠家的夥伴掌握了一手測試數據,不妨在評論裏分享下😊。

相關文章
相關標籤/搜索