《10分鐘剖析》系統啟動3——Zygote的使命

LKV_劉言發表於2020-01-03

Previous

  • 使用者態1字號(pid=1)應用程式 init 透過 app_process 發起zygote啟動動作
  • app_process 通過操作 AppRuntimeAndroidRuntime 的派生類)初始化並啟動JVM
  • ART虛擬機器得到啟動,JNI呼叫環境得到初始化,眾多Android API相關JNI得以註冊
  • 通過 env->CallStaticVoidMethod() 發起對**ZygoteInit.java#main()**的呼叫,世界切換至Java
砸瓦魯多

總的層次呼叫為:

  • bootloader
    • kernel
      • init(首次切換到使用者態)
        • app_process64(對64位機型來說)在此啟動Java世界,啟動zygote

ZygoteInit.java的main()會做什麼?

原始碼位置:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

new ZygoteServer、ZygoteHooks、setpgid

《10分鐘剖析》系統啟動3——Zygote的使命

  • 建立ZygoteServer,它的職能是藉助socket暴露API(後邊會詳細介紹ZygoteServer)

  • 而後通過ZygoteHooks和虛擬機器進行互動,告知虛擬機器在zygote建立期間不要開啟新的執行緒,否則會報錯

  • Os.setpgid(0,0)並不是真的能將自己的pid設定成0,詳見其底層方法的官方解釋

    《10分鐘剖析》系統啟動3——Zygote的使命

    無關部分隱去。Linux API手冊中說的是,如果都給0,那麼會跟隨當前所處的程式的pid和pgid(對於zygote來說就是init在呼叫app_process時產生的程式的pid和pgid了)

使能DDMS

  • RuntimeInit.enableDdms()

    • android.ddm.DdmRegister.registerHandlers()

      《10分鐘剖析》系統啟動3——Zygote的使命

我們在App中常用來除錯檢視執行緒、記憶體、UI佈局等方法都是在這裡註冊的。

如果做效能收集專項,學習下系統是如何做的,可以從這裡入手。

解析引數

《10分鐘剖析》系統啟動3——Zygote的使命

還記得傳入ZygoteInit#main()方法的引數嗎?

  • com.android.internal.os.ZygoteInit
  • start-system-server
  • --abi-list=arm64-v8a

所以for迴圈裡i從1開始計數,自然也是可以理解的了。而socket又是哪裡來的呢?這個可以回頭看一下init在start service時做了什麼。

zygote socket插曲

回憶init.zygote64.rc

《10分鐘剖析》系統啟動3——Zygote的使命

在做rc檔案解析時,Service解析過程中:

system/core/init/service.cpp

  • Service::ParseLine(每行的文字內容)
    • OptionParserMap::FindFunction(透傳) //通過socket找到ParseSocket函式指標
      • 構造SocketInfo到descriptors_中

待Service:Start()被呼叫時:

《10分鐘剖析》系統啟動3——Zygote的使命

  • DecriptorInfo::CreateAndPublish()
    • SocketInfo::Create()
      • util.cpp#CreateSocket()
    • setenv //對於SocketInfo而言設定的是環境變數 ANDROID_SOCKET_socket_name

即,在Service啟動時就同時建立了名為zygote的socket,具體的socket地址是:

/dev/socket/zygote

建立完成後,將socket控制程式碼資料以環境變數的方式,放置在了 ANDROID_SOCKET_zygote 的key下。

《10分鐘剖析》系統啟動3——Zygote的使命

回到引數解析。預設給了socketName = zygote,而之前我們記錄的引數中也並無socket配置,所以最後生效的socket名字就是zygote無疑

連線socket

《10分鐘剖析》系統啟動3——Zygote的使命

zygoteServer的連線也是後邊詳細說明,這裡直接說結論:這裡是將init所建立的名為zygote的socket給註冊到Java空間來。

LazyPreload 懶-預載入

《10分鐘剖析》系統啟動3——Zygote的使命

上邊的引數解析中,並沒有指定lazy-perload,所以這裡enableLazyPreload是false的,那麼會進行preload()。這裡的邏輯是:

  1. 如果是Zygote,也就是不需要進行懶載入,那麼就先行預載入一些東西
  2. 如果需要懶載入的話,會將執行緒的優先順序調低。可以理解為,需要懶載入,那麼必然就代表著不是很重要吧

preload的內容:

《10分鐘剖析》系統啟動3——Zygote的使命

其中和我們最息息相關的就是class的預載入了,這裡邊包含了眾多API的Java類、Android內部類,最常見的比如Activity、Fragment、Service等也在預載入之列,而預載入的方式就是通過Class.forName:

《10分鐘剖析》系統啟動3——Zygote的使命

以我的AOSP原始碼環境為例,體量巨大,高達6500+行:

《10分鐘剖析》系統啟動3——Zygote的使命

Zygote.nativeSecurityInit() 訪問安全

接下來,呼叫了Zygote.nativeSecurityInit();進行native的初始化,對應著:framworks/base/core/jni/com_android_internal_os_Zygote.cpp# com_android_internal_os_Zygote_nativeSecurityInit()

隨時複習,這個是在AndroidRuntime初始化時進行註冊的,就是在進入Java世界前的事情,詳細參見上一篇

具體的內容是對SELinux進行了操作,因為系統設計上,只有系統能做SELinux相關的操作,App程式是不允許的。作為Java世界的頭號玩家,Zygote在做fork(衍生出其他App前)自然要先做好安全這一步。

Zygote.nativeUnmountStorageOnInit() 隔離儲存

在這一步呼叫了com_android_internal_os_Zygote.cpp中的com_android_internal_os_Zygote_nativeUnmountStorageOnInit()方法。目的是將整體的儲存目錄/storage解除安裝,取而代之的是掛載臨時目錄,這個動作和Android9及10所說的 隔離儲存 有關,也可以叫做 沙盒儲存 。這一部分可以參照官方說明

ZygoteHooks.stopZygoteNoThreadCreation()

與上邊呼應,告訴ART虛擬機器,現在可以在zygote程式中建立執行緒了,下圖是虛擬機器中實際建立執行緒的入口方法,可以看到,會通過對應的標記為去判斷究竟是否可以建立執行緒,如果觸發紅線的話,會丟擲InternalError:

《10分鐘剖析》系統啟動3——Zygote的使命

在繼續跟進之前,需要理清楚一個關鍵的角色 ZygoteServer

ZygoteServer

其在ZygoteInit.java#main(),除了構造,其被呼叫到的方法和順序是:

  • registerServerSocketFromEnv(socketName) //zygote
  • runSelectLoop()
  • closeServerSocket()

registerServerSocketFromEnv

前面說到,在init執行zygote server時候,建立了名為zygote的socket,然後將對應的socket控制程式碼(int值)以環境變數的方式儲存在了key => ANDROID_SOCKET_zygote 中。

在此方法中,通過對應的環境變數獲取到了這個socket的控制程式碼資料,然後將其轉換為了Java空間的ServerSocket:

《10分鐘剖析》系統啟動3——Zygote的使命

LocalServerSocket 中,又透過 LocalSocketImpl 開始了真正的監聽。

這裡只是註冊,開始了監聽,但還沒有去獲取其中的資料

runSelectLoop

《10分鐘剖析》系統啟動3——Zygote的使命

ZygoteServer通過poll的方式輪訓檢查zygote socket。檢查時對兩類socket做區分處理:

  1. 剛剛建立的ServerSocket,當這個控制程式碼收到新資料時,代表的是有新的socket連線進入,此時會構造新的ZygoteConnection,放入下一輪的輪訓控制程式碼集合中(ServerSocket在集合中的位置永遠是0),並開啟新一輪輪訓
  2. 對於ZygoteConnection,也就是來自客戶端的連線。會呼叫ZygoteConnection.processOneCommand()解析命令,獲得對應的Runnable可執行命令,這個過程中也就發生了fork。fork後在父程式中關閉連線,而子程式中就返回了這個Runnable
《10分鐘剖析》系統啟動3——Zygote的使命

大概流程如上圖,忽略序號的順序,因為有fork產生了子程式,所以順序上看連線判定吧

closeServerSocket

最後,顧名思義,關閉ServerSocket,不再接受連線。也就是說,zygote程式的服務終止了,這對於Android系統來說是致命錯誤,正常執行情況下是絕無可能發生的。那為什麼還有這個方法呢?

上邊的時序圖中,有一個點:fork後,在父程式是不返回的,只是在ZygoteConnection中關閉了來自客戶端的連線(因為此時已經處理完了)。而在fork出來的子程式中是不需要在有zygote服務的,所以這裡的關閉理論上是為了在子程式中關閉無用的zygote服務所說。

總結

分析完ZygoteServer三個關鍵的方法後,發現其定位就是zygote服務和客戶端的聯結器、處理器。客戶端通過名為zygote的socket發來一些啟動請求,由zygote程式fork出來子程式,享用zygote在啟動初期所做好的一切(JVM初始化、JNI初始化、class預載入、資源預載入等),而後執行通過命令解析出的Runnable(下邊會直接列出解析的流程)。這就是一個新的App啟動過程中至關重要的一個部分,後邊會分析App啟動,也會碰觸到這一塊。

processOneCommand解析Runnable過程

  • ZygoteConnection.processOneCommand
    • readArgumentList //從socket中讀取引數列表
    • new Arguments //解析引數
    • fork
      • 父程式
        • return null
      • 子程式
        • ZygoteServer.setForkChild() //告知ZygoteServer當前在子程式
        • ZygoteServer.closeServerSocket() //如上所說,子程式中已經不在需要zygote服務
        • handleChildProc

fork SystemServer

經過了ZygoteServer的梳理,現在回到ZygoteHooks.stopZygoteNoThreadCreation()後的systemserver fork程式中

《10分鐘剖析》系統啟動3——Zygote的使命

Arguments

下面是用來啟動system_server的關鍵引數:

《10分鐘剖析》系統啟動3——Zygote的使命

uid、gid和組別資訊,niceName、runtime引數、以及最重要的 classPath=com.android.server.SystemServer

以上的引數會被ZygoteConnection的內部類Arguments解析,解析過程大體如下:

《10分鐘剖析》系統啟動3——Zygote的使命
  • --會被視為是要跳過的引數字元
  • 其他的引數會有重複解析的報錯

當所有已知的內容被解析過之後,會檢查剩餘是否還有引數:

《10分鐘剖析》系統啟動3——Zygote的使命

根據上述用於啟動system_server的引數,我們在這裡可以判定,會走到最後的case中,即剩餘的引數會被儲存在remainingArgs中,即com.android.server.SystemServer

Zygote.forkSystemServer()

引數解析完成後,ZygoteInit呼叫Zygote.forkSystemServer()著手進行fork。

此方執行呼叫後,會觸發兩次返回,先後順序不一定(同樣也不重要)。

  • 一次返回是帶著大於0的pid回來,此時runtime處於父程式,這個pid就是fork出來的子程式的pid
  • 另一次返回是帶著等於0的pid回來,此時runtime處於子程式

《10分鐘剖析》系統啟動3——Zygote的使命

ZygoteHooks.preFork()

這裡是做fork前的準備,主要是通過呼叫Daemon.stop()來操作守護執行緒的停止:

  • 堆守護
  • 引用佇列守護
  • 記憶體回收守護
  • 記憶體回收看門狗守護執行緒

resetNicePriority()

重置程式(主執行緒)優先順序為 5 ,這一點通過我們自己開發的App執行緒資訊可以檢視到

nativeForkSystemServer()

這裡進行了真正的fork,使得整個流程在這裡發起了兩次的返回。一個比較關鍵的細節是:

《10分鐘剖析》系統啟動3——Zygote的使命

在fork進行完成後,在 system_server 程式(子)中透過JNI觸發了Java空間的回撥(Zygote.java):

《10分鐘剖析》系統啟動3——Zygote的使命

這個呼叫最終在ART虛擬機器中生效,虛擬機器為從zygote衍生出來的子程式做了一系列的配置。當然,這個過程中,對於system_server有過很多特殊的待遇(比如,關閉JIT)。

《10分鐘剖析》系統啟動3——Zygote的使命

ZygoteHooks.postForkCommon()

在這裡又會啟動上邊所說的四個守護執行緒,並且重新設定執行緒優先順序。很關鍵的一點是,這裡會分別在父程式、子程式執行。zygote程式中,此時也是首次開啟這幾個守護執行緒。而system_server程式,以及後邊可能的其他三方App的守護執行緒也是在這裡開啟的。

至此,system_server程式的fork就完成了,後邊才開始在程式中做事

在sysetm_server程式中執行

《10分鐘剖析》系統啟動3——Zygote的使命

return null

這裡優先解釋下最後的return null,把上文呼應的問題說清楚。

在ZygoteInit#main()方法中,我們之前對最後一段做過分析,回頭複習一下:

《10分鐘剖析》系統啟動3——Zygote的使命

在fork完成system_server程式後,父程式中直接返回了null。這樣,在zygote的程式中就會執行到runSelectLoop,也就是上邊所解釋過的:處理zygote socket接收到的命令。

system_server子程式中

關於second zygote socket

如果ABI(arm64等架構)有多個,那麼會有第二個zygote socket,優先會去等待這第二個socket就緒。方法很傳統,等待20s:

《10分鐘剖析》系統啟動3——Zygote的使命

這部分不做過多討論,我們聚焦於主線。

zygoteServer.closeServerSocket()

關於zygoteServer的關閉,在上邊也解釋過,子程式並不需要保持zygote socket的連線了,所以進行斷開操作。這並不會影響主程式的server socket繼續工作。

handleSystemServerProcess()

很關鍵的一點,關於入參,上邊有一筆帶過,在parsedArgs中還存留著一個叫做remainingArgs的變數,其內容是com.android.server.SystemServer

  • 首先,通過呼叫熟悉的Process.setArgV0(parsedArgs.niceName)將system_server程式的名字真正命名為了system_server
  • 然後,通過SYSTEMSERVERCLASSPATH環境變數獲取到一些jar檔案(實際是最終機器上的/system/frameworks/下的jar檔案,是做system_server的classpath),提前做dex opt優化(關於installd服務部分不在這裡展開)
  • 而後,建立classloader(PathClassLoader),並設定給主執行緒
  • 最後,通過ZygoteInit構造可執行的Runnable(這裡傳入了我們關心的remainingArgs)

《10分鐘剖析》系統啟動3——Zygote的使命

最後,正是這個Runnable,被return到了ZygoteInit#main方法中,被其呼叫。最終呼叫了com.android.server.SystemServer#main()方法,具體內容留作下次分析。

相關文章