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

Previous

  • 用戶態1字號(pid=1)應用程序 init 透過 app_process 發起zygote啓動動做
  • app_process 經過操做 AppRuntimeAndroidRuntime 的派生類)初始化並啓動JVM
  • ART虛擬機獲得啓動,JNI調用環境獲得初始化,衆多Android API相關JNI得以註冊
  • 經過 env->CallStaticVoidMethod() 發起對**ZygoteInit.java#main()**的調用,世界切換至Java
砸瓦魯多

總的層次調用爲:html

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

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

源碼位置:frameworks/base/core/java/com/android/internal/os/ZygoteInit.javalinux

new ZygoteServer、ZygoteHooks、setpgid

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

  • 然後經過ZygoteHooks和虛擬機進行交互,告知虛擬機在zygote建立期間不要開啓新的線程,不然會報錯安全

  • Os.setpgid(0,0)並非真的能將本身的pid設置成0,詳見其底層方法的官方解釋架構

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

使能DDMS

  • RuntimeInit.enableDdms()socket

    • android.ddm.DdmRegister.registerHandlers()函數

咱們在App中經常使用來調試查看線程、內存、UI佈局等方法都是在這裏註冊的。oop

若是作性能收集專項,學習下系統是如何作的,能夠從這裏入手。

解析參數

還記得傳入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

在作rc文件解析時,Service解析過程當中:

system/core/init/service.cpp

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

待Service:Start()被調用時:

  • 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下。

回到參數解析。默認給了socketName = zygote,而以前咱們記錄的參數中也並沒有socket配置,因此最後生效的socket名字就是zygote無疑

鏈接socket

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

LazyPreload 懶-預加載

上邊的參數解析中,並無指定lazy-perload,因此這裏enableLazyPreload是false的,那麼會進行preload()。這裏的邏輯是:

  1. 若是是Zygote,也就是不須要進行懶加載,那麼就先行預加載一些東西
  2. 若是須要懶加載的話,會將線程的優先級調低。能夠理解爲,須要懶加載,那麼必然就表明着不是很重要吧

preload的內容:

其中和咱們最息息相關的就是class的預加載了,這裏邊包含了衆多API的Java類、Android內部類,最多見的好比Activity、Fragment、Service等也在預加載之列,而預加載的方式就是經過Class.forName:

以個人AOSP源碼環境爲例,體量巨大,高達6500+行:

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:

在繼續跟進以前,須要理清楚一個關鍵的角色 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:

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

這裏只是註冊,開始了監聽,但尚未去獲取其中的數據

runSelectLoop

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

  1. 剛剛建立的ServerSocket,當這個句柄收到新數據時,表明的是有新的socket鏈接進入,此時會構造新的ZygoteConnection,放入下一輪的輪訓句柄集合中(ServerSocket在集合中的位置永遠是0),並開啓新一輪輪訓
  2. 對於ZygoteConnection,也就是來自客戶端的鏈接。會調用ZygoteConnection.processOneCommand()解析命令,得到對應的Runnable可執行命令,這個過程當中也就發生了fork。fork後在父進程中關閉鏈接,而子進程中就返回了這個Runnable

大概流程如上圖,忽略序號的順序,由於有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進程中

Arguments

下面是用來啓動system_server的關鍵參數:

uid、gid和組別信息,niceName、runtime參數、以及最重要的 classPath=com.android.server.SystemServer

以上的參數會被ZygoteConnection的內部類Arguments解析,解析過程大致以下:

  • --會被視爲是要跳過的參數字符
  • 其餘的參數會有重複解析的報錯

當全部已知的內容被解析過以後,會檢查剩餘是否還有參數:

根據上述用於啓動system_server的參數,咱們在這裏能夠斷定,會走到最後的case中,即剩餘的參數會被保存在remainingArgs中,即com.android.server.SystemServer

Zygote.forkSystemServer()

參數解析完成後,ZygoteInit調用Zygote.forkSystemServer()着手進行fork。

此方運行調用後,會觸發兩次返回,前後順序不必定(一樣也不重要)。

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

ZygoteHooks.preFork()

這裏是作fork前的準備,主要是經過調用Daemon.stop()來操做守護線程的中止:

  • 堆守護
  • 引用隊列守護
  • 內存回收守護
  • 內存回收看門狗守護線程

resetNicePriority()

重置進程(主線程)優先級爲 5 ,這一點經過咱們本身開發的App線程信息能夠查看到

nativeForkSystemServer()

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

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

這個調用最終在ART虛擬機中生效,虛擬機爲從zygote衍生出來的子進程作了一系列的配置。固然,這個過程當中,對於system_server有過不少特殊的待遇(好比,關閉JIT)。

ZygoteHooks.postForkCommon()

在這裏又會啓動上邊所說的四個守護線程,而且從新設置線程優先級。很關鍵的一點是,這裏會分別在父進程、子進程執行。zygote進程中,此時也是首次開啓這幾個守護線程。而system_server進程,以及後邊可能的其餘三方App的守護線程也是在這裏開啓的。

至此,system_server進程的fork就完成了,後邊纔開始在進程中作事

在sysetm_server進程中運行

return null

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

在ZygoteInit#main()方法中,咱們以前對最後一段作過度析,回頭複習一下:

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

system_server子進程中

關於second zygote socket

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

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

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調用執行(這裏傳入了咱們關心的remainingArgs)

在這裏,最終調用了com.android.server.SystemServer#main()方法,具體內容留做下次分析。

相關文章
相關標籤/搜索