《10分鐘剖析》系統啓動2——啓動zygote

Previous

  • 機器上電,開始初始化內核
  • 內核啓動了第一個用戶態的app——init
  • init通過解析init.rc及其所import的各個rc文件收集action和service
  • init通過epoll的循環機制,依次觸發early-init、init、late-init等狀態下的各種action,間接觸發關聯的service

zygote服務

zygote作爲App開發者所熟知的孵化器進程,在App界的地位是絕對的龍頭老大。而zygote服務也是通過init作爲服務啓動起來的,其服務聲明如下(以64位爲例):

其文件位置爲:system/core/rootdir/init.zygote64.rc

init.rc

在init.rc中,可以看到相關的啓動動作:

而start zygote最後真是發生了什麼呢?又要把目光挪回到init中。

init

在前文中,說到過init解析了一系列的*.rc文件,收集了大量的Action進入到ActionManager。

在init逐行解析rc文件時,Action的解析如上圖,觸發了action的**AddCommand()**方法。

這個function就是後邊會被調用的真正內容,這個內容可以通過FindFunction()找到:

對於Action,大體上內建支持的集合有如這樣:

而start所對應的:

一探do_start究竟:

到這裏可以做一下簡單的梳理:

在init解析出Actions給ActionManager的同時,也解析了Service給ServiceList。

再次強化一下整體理解:

  1. on XXX叫做Action,而Action下的叫做Command。Command的全集可以通過system/core/init/builtins.cpp中的**BuiltinFunctionMap::map()**進行檢索
  2. service XXX叫做Service,Service下的東西叫做Option。Option的全集可以通過system/core/init/service.cpp中的**Service::OptionParserMap::map()**進行檢索

start zygote就是一條Action,而Action執行的路徑:

  • am.ExecuteOneCommand()
    • action->ExecuteOneCommand()
      • Action::ExecuteCommand()
        • command.InvokeFunc()
          • function(builtin_arguments) //指針函數調用
            • do_start()
              • service.cpp#ExpandArgsAndExecv()

首先需要補充一點:

Command怎麼和Function綁定在一起的就要回頭看一下ActionParser了。ActionParser在解析過程中,總會調用Action::AddCommand方法:

即,通過map找到對應命令的function,添加到**commands_**中去。

看下ExpandArgsAndExecv的本體:

可以發現兩個問題:

  1. 執行ExpandArgsAndExecv後,正常來講,service就一直處於running狀態了,即對於init這個父進程而言會是阻塞的,按常理不會走到exit -127(Command not found)
  2. 最終執行的命令就是通過args拼裝出來的,而args是在Service解析過程中就被確定的:

image-20191225182557198

而Service的構造是通過ServiceParser::ParseSection()來的:

具體rc文件的解析過程有興趣的同學可以圍觀system/core/init/parser.cpp,這裏就不做詳細分析了,過於枯燥。

結論是,結合之前zygote的service聲明,最終會執行的命令就是:

/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server

至此,init進程關於zygote的生涯結束。

app_process

源碼位置:frameworks/base/cmds/app_process

通過Android.mk可以確定app_process64可執行文件就在此生成(新版本已改爲Android.bp):

下面深入源碼剖析。

app_main.cpp

調用C/C++可執行程序,衆所周知要從main方法看起:

這是一個標準的main入口方法,值得注意的有兩點:

  1. AppRuntime這個runtime的存在
  2. 通過argc的減一和argv的指針位移,跳過了"app_process64"這個argv[0]

至於AndroidRuntime,這是個非常重要的角色,是Android運行時真正的起點,整個framework的奠基人,後邊做詳細分析。

此時argv指針所指向的完整字符串是:-Xzygote /system/bin --zygote --start-system-server

在解析之前會有一個預檢查:

不難發現,在經過這個預檢查後,i作爲字符串指針,所代表的參數變成了:/system/bin --zygote --start-system-server

  • /system/bin是沒有用的
  • zygote = true
  • startSystemServer = true
  • niceName = “zygote64”

下面就爲真正啓動runtime開始籌備新的args了:

在組裝args後,真正發起了調用:

其中setArgv0是爲當前線程(fork出來的進程的默認線程)設置名稱(pthread_setname_np)。

當前args中包含的內容只有兩個:

  • start-system-server
  • –abi-list=arm64-v8a (或armeabi-v7a armeabi)

關於app_process還有什麼場景使用,後續會拓展衍生篇

AppRuntime

AppRuntime這個類存在於app_main.cpp中,它其實本身很薄。它的父類AndroidRuntime是背後的集大成者,AppRuntime更多的是針對於其父類所暴露的一些生命週期虛函數做了必要的實現,比如:

完整的類圖:

其父類的詳細代碼可以在線查看:AndroidRuntime

構造

mClass是jclass類型。這裏做了NULL初始化,其他傳入了父類:

zygote並不是通過init所啓動的第一個service、第一個進程。通過源碼可以看到,init.rc中early-init第一個啓動其實是ueventd(實際上還是init),甚至在之後的late-init環節中,binder相關啓動時機比zygote還要早

start

再回頭看一下調用start的參數是什麼:

runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

  • className = com.android.internal.os.ZygoteInit
  • options(同上邊的args)
    • start-system-server
    • –abi-list=arm64-v8a (或armeabi-v7a armeabi)
  • zygote = true

AndroidRuntime的start過程略長,分四部分看:

1. 啓動ART虛擬機

JniInvocation

jni_invocation.Init(NULL)內部去加載了libart.so(通過dlopen),並將操作句柄保存在了JniInvocation的**handler_**中:

handle_作爲後續做調用dlsym的參數使用。同時還對ART虛擬機做了三個靈魂拷問:

  1. JNI_GetDefaultJavaVMInitArgs 你可不可以獲取默認JVM初始化參數?(從源碼上看,這部分是沒有內容的,返回了-1,但方法存在就叫做支持)
  2. JNI_CreateJavaVM 你能不能創建JVM?
  3. JNI_GetCreatedJavaVMs 能不能把剛剛創建的JVM給我?

這三個方法都存在,那麼說明這是一個合格的libart.so

startVm()

此方法內部是大量的配置參數組裝過程,目標是爲了創建一個JVM出來,也就是實踐上一步的第二個問題。zygote參數影響的是JVM的JDWP配置,其他兩個就是透過ART Runtime賦值的了:

這裏需要臨時插入另一個問題,JniInvocation作爲另一個so——libnativehelper.so中的方法,爲什麼在AndroidRuntime.cpp中可以直接使用?並沒有見到先dlopen它的地方。這個可以通過frameworks/base/core/jni/Android.bp找到答案,AndroidRuntime.cpp就屬於這裏。在這個(Makefile)文件中,可以發現,libnativehelper是作爲其shared_libs使用的,所以可以直接調用。

  • JniInvocation.cpp#JNI_CreateJavaVM()
    • JniInvication::JNI_CreateJavaVM()
      • JniInvication::JNI_CreateJavaVM_函數指針(指向ART虛擬機的JNI_CreateJavaVM方法)

此時,虛擬機的實例有了,用於JNI調用的Env也有了。

onVmCreated()

AppRuntime複寫了父類的虛函數:

zygote的case下,這裏是不會做任何事情的。

至此,VM創建成功,也有了基礎的JNI調用環境。第一步結束。

2. Android JNI

startReg的實現:

gRegJNI是非常大體量的JNI註冊集合體:

以我的源碼數下來,總共有155個註冊JNI的方法,都是與Android系統各個API息息相關的內容

以RuntimeInit爲例:

startReg中所傳入的env參數會被作爲入參傳入到register*方法中,而最終會觸發env->RegisterNatives方法(涉及虛擬機的部分不再深入了)。

而關於LocalFrame,官方解釋如下:

Every “register” function calls one or more things that return
a local reference (e.g. FindClass). Because we haven’t really
started the VM yet, they’re all getting stored in the base frame
and never released. Use Push/Pop to manage the storage.

之前我們啓動了VM,只是我們沒有在這個VM中做真正的方法調用。每一個register函數都會通過env執行起來,而他們都會產生本地引用,如果不加操作,那麼這些本地引用不會被自動銷燬,造成無用數據的累積,所以這裏使用LocalFrame收集他們,並最後釋放。

總結:這一步就是將衆多Android系統相關功能的JNI註冊到JVM中去,便於後續Java環境中進行調用。

3. 參數組裝

strArray會用來存儲所組裝出來的參數,如圖中註解所寫,參數的內容會是:

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

4. 調用main

如官方註釋所說,當前所在的這個線程將會是即將到達的Java世界的主線程。

當前線程的特點:

  • C++程序
  • app_process64 可執行程序
  • 相對於Linux內核空間,當前處於用戶空間
  • 是被用戶空間第一個app——init(pid=1)啓動起來的

在發起JVM調用前,結合上邊的各種梳理,各種參數爲:

  • startClass = com/android/internal/os/ZygoteInit
  • startMeth 是這樣的main方法
  • (strArray在上一小節)

接下來,隨着env->CallStaticVoidMethod()的調用,啓動流程進入到以ZygoteInit爲起點的Java領域。

Next

ZygoteInit.java

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章