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

LKV_劉言發表於2019-12-26

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位為例):

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

其檔案位置為:system/core/rootdir/init.zygote64.rc

init.rc

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

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

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

init

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

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

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

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

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

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

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

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

而start所對應的:

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

一探do_start究竟:

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

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

在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()

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

首先需要補充一點:

Command怎麼和Function繫結在一起的就要回頭看一下ActionParser了。ActionParser在解析過程中,總會呼叫Action::AddCommand方法:

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

即,通過map找到對應命令的function,新增到 commands_ 中去。

看下ExpandArgsAndExecv的本體:

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

可以發現兩個問題:

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

image-20191225182557198

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

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

具體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):

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

下面深入原始碼剖析。

app_main.cpp

呼叫C/C++可執行程式,眾所周知要從main方法看起:

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

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

  1. AppRuntime這個runtime的存在
  2. 通過argc的減一和argv的指標位移,跳過了"app_process64"這個argv[0]
《10分鐘剖析》系統啟動2——啟動zygote

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

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

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

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

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

《10分鐘剖析》系統啟動2——啟動zygote 《10分鐘剖析》系統啟動2——啟動zygote
  • /system/bin是沒有用的
  • zygote = true
  • startSystemServer = true
  • niceName = "zygote64"

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

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

在組裝args後,真正發起了呼叫:

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

其中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更多的是針對於其父類所暴露的一些生命週期虛擬函式做了必要的實現,比如:

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

完整的類圖:

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

其父類的詳細程式碼可以線上檢視:AndroidRuntime

構造

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

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

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

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

start

再回頭看一下呼叫start的引數是什麼:

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

《10分鐘剖析》系統啟動2——啟動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虛擬機器

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

jni_invocation.Init(NULL)內部去載入了libart.so(通過dlopen),並將操作控制程式碼儲存在了JniInvocationhandler_ 中:

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

handle_作為後續做呼叫dlsym的引數使用。同時還對ART虛擬機器做了三個靈魂拷問:

《10分鐘剖析》系統啟動2——啟動zygote
  1. JNI_GetDefaultJavaVMInitArgs 你可不可以獲取預設JVM初始化引數?(從原始碼上看,這部分是沒有內容的,返回了-1,但方法存在就叫做支援)
  2. JNI_CreateJavaVM 你能不能建立JVM?
  3. JNI_GetCreatedJavaVMs 能不能把剛剛建立的JVM給我?

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

startVm()

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

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

這裡需要臨時插入另一個問題,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方法)
《10分鐘剖析》系統啟動2——啟動zygote

此時,虛擬機器的例項有了,用於JNI呼叫的Env也有了。

onVmCreated()

AppRuntime複寫了父類的虛擬函式:

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

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

至此,VM建立成功,也有了基礎的JNI呼叫環境。第一步結束。

2. Android JNI

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

startReg的實現:

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

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

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

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

以RuntimeInit為例:

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

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. 引數組裝

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

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

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

4. 呼叫main

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

如官方註釋所說,當前所在的這個執行緒將會是即將到達的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

相關文章