《10分鐘剖析》系統啟動(1)——從核心到使用者

LKV_劉言發表於2019-12-26

要看什麼?

Android系統總體是圍繞著Linux核心而建立的。從加電到系統啟動,大體上經過:

  • bootloader
    • kernel
      • 使用者空間(init、zygote、framework、launcher等)

bootloader,距離App開發者太遠了,是廠商最為關心的東西。某種角度上講,其特定性太高,比如加電後我就要從記憶體晶片的第多少個offset偏移指標開始跑程式。很多是底層協議的東西,理解起來晦澀難懂且幫助不大。所以這裡選擇直接從Linux核心作為第一起點,以更加通用的邏輯切入系統的啟動。

在系統啟動系列文章中,所確立的主線是從加電啟動到第一個app(也就是launcher)啟動。所以,有過一定了解的朋友應該能夠get到,核心是zygote。像binder會另闢系列文章加以剖析。

Let's go!

Linux系統的啟動

head.S

《10分鐘剖析》系統啟動(1)——從核心到使用者
  • 原始碼位置:kernel/common/arch/arm64/kernel/head.S

    機器加電後,不同的CPU根據不同的記憶體偏移設定,開始制定對應的彙編指令。以arm64架構為例,在插電後執行到了這裡,指向了start_kernel

main.c

《10分鐘剖析》系統啟動(1)——從核心到使用者
  • 原始碼位置:kernel/common/init.main.c

    main.cstart_kernel暴露給彙編空間得以呼叫。被呼叫時,這時是處於核心態的,經過一些列核心的初始化,最後呼叫了arch_call_rest_init()

    《10分鐘剖析》系統啟動(1)——從核心到使用者

    在rest_init中,通過kernel_thread開啟了pid為1的程式(0已經在sched_init中初始化的idle thread佔用走了,有興趣自行研習下吧)

    《10分鐘剖析》系統啟動(1)——從核心到使用者

kernel_thread很簡單粗暴,就是用來在核心態中開啟一個執行緒的(也可以理解為程式,在核心態中並沒有特殊的資料結構來區分執行緒和程式,大意可以理解為一個東西)

image-20191223120032256

kernel_init這個function指標是重要的

《10分鐘剖析》系統啟動(1)——從核心到使用者

經過裁剪,核心程式碼如圖。其中兩個execute_command是兩個全域性靜態變數,接收外部指定:

《10分鐘剖析》系統啟動(1)——從核心到使用者

《10分鐘剖析》系統啟動(1)——從核心到使用者

arm結構沒有設定,但結合其他架構配置檔案的設定可以看到,基本就是指向了init

《10分鐘剖析》系統啟動(1)——從核心到使用者

再結合最後的兜底程式碼

《10分鐘剖析》系統啟動(1)——從核心到使用者

可以確定,核心的1號程式(pid)的執行內容是使用者空間的init

init 使用者空間的第一個應用程式

通過對Soong編譯系統的基本學習,不難發現,name為init的cc_binary就再system/core/init下:

《10分鐘剖析》系統啟動(1)——從核心到使用者

同時,還可以發現,ueventd和watchdogd其實也是init這個應用程式所做的

main.cpp

通過Android.bp的指引,看下main.cpp做了什麼:

《10分鐘剖析》系統啟動(1)——從核心到使用者

通過namespace的限定,可以定位到init.cpp

init.cpp

  • 原始碼位置:system/core/init/init.cpp

init 作為第一個使用者態的應用程式,做了非常多的工作,包括設定環境變數、掛載FS、許可權設定等。基於探索Android系統啟動的核心,我們挑最關心的部分看

在看原始碼之前,可以先去了解下Android初始化語言的規範文件

初始化語言中,最重要的是兩個角色:

Action

可以理解為當XXX發生時候,怎麼怎麼樣:

《10分鐘剖析》系統啟動(1)——從核心到使用者

比如截圖中:當到達early-init時,向某裝置寫入什麼值。這裡write這一行是command(命令),是具體的行為內容

Service

而服務是宣告、定義。

《10分鐘剖析》系統啟動(1)——從核心到使用者

如圖中所示,宣告瞭名為ueventd的服務,內容在下邊描述,具體的執行檔案是 /sbin/ueventd

基於對於Action和Service的初步瞭解,下邊看原始碼:

main()

epoll

在開始解析配置檔案init.rc等之前,建立了一個epoll控制程式碼:

《10分鐘剖析》系統啟動(1)——從核心到使用者

在建立epoll控制程式碼後,馬上註冊了關於SIGCHLD的訊號處理回撥。

epoll是Linux中的一項多路複用技術,自2.5版本引入。

這裡對於epoll的使用是說,init應用程式響應其所啟動的各種子程式的SIGCHLD訊號(即子程式退出、終止),根據相關資料,會有需要對一些子程式做重啟操作。在這,是將SIGCHLD訊號打到了socket上,讓epoll監聽此socket。在後邊還有幾處使用epoll來監聽事件的操作,這裡不做過多展開了

解析

《10分鐘剖析》系統啟動(1)——從核心到使用者

緊接著,去解析了Action和Service。解析後所構造出來的Action和Service例項儲存在各自Manager、List的Vector中(詳見service.h & action_manager.h)。

解析的過程:

《10分鐘剖析》系統啟動(1)——從核心到使用者

通過原始碼可以看到,可以指定初始化配置檔案的地方非常多,甚至可以用環境變數。翻遍AOSP原始碼,也沒有人去設定ro.boot.init_rc這個環境變數,所以都會是init.rc。

init.rc在於檔案系統的根目錄,而其攜帶者是boot.img,也就是說他被編譯打包在boot.img中。boot.img和system.img以及recovery.img,相信有同學會很熟悉,都是需要通過BootLoader刷機才可以進行更新的。所以常規渠道來講,只要手機出廠,init.rc中相關資訊就是不可修改的

而解析的動作,是由Parser執行的:

《10分鐘剖析》系統啟動(1)——從核心到使用者

結合前邊給出的一些init.rc的配置片段,確定service開頭的line被ServiceParser去解析,而on被ActionParser去解析,import最後可以理解為遞迴的解析每一個所引入的其他rc檔案。

具體對文字內容逐行解析的過程在system/core/init/parser.cpp#ParseData()中,本文不做具體解析

解析結束後:

  • actions儲存於ActionManager中
  • services儲存於ServiceList中
觸發

上邊說了,Service是靜態的宣告、定義。那麼事件的驅動,一定是要靠Action推動的,所以觸發的源頭就是Action的流轉:

《10分鐘剖析》系統啟動(1)——從核心到使用者

這裡ActionManager是把各種事件內容都放到了內部的佇列中,並沒有進行觸發:

《10分鐘剖析》系統啟動(1)——從核心到使用者

而其所能夠接受的型別有三種:

《10分鐘剖析》系統啟動(1)——從核心到使用者

其分別對應ActionManager的三個操作:

《10分鐘剖析》系統啟動(1)——從核心到使用者 《10分鐘剖析》系統啟動(1)——從核心到使用者

而init的main()中只用了1和3兩種方式,很多都是通過內建Action的方式對已經解析出的**actiions_**做了擴充。

下邊就是真正會去觸發action的地方:

《10分鐘剖析》系統啟動(1)——從核心到使用者

while-true (A)

  • 進入了無限迴圈,沒有退出的case。這裡的設計思路是:

    1. 通過-1超時時間的設計,在沒有事件發生的時候,通過epoll可做到完全休眠,還不浪費系統資源
    2. 如果真的有了epoll時間,那麼每次喚醒後的動作也是在這個while-true中進行處理的
  • 關機或重啟事件是一個優先需要處理的,其對應著property服務的處理過程(題外話):

    《10分鐘剖析》系統啟動(1)——從核心到使用者

    handle_property_set_fd內部最終就會對應著以下內容:

《10分鐘剖析》系統啟動(1)——從核心到使用者

這樣,也就不難理解,關機、重啟的邏輯是怎麼來的了。

  • 再然後,正常的,會得到一個純淨的可執行環境,通過AM去執行一條命令
《10分鐘剖析》系統啟動(1)——從核心到使用者

while-true (B)

細節的內容通過圖中註釋已經做了說明。可以發現,只要AM的action佇列中還有存貨,就會按照順序流水賬一樣的執行下去。

總結

  • Action繫結在事件上,被事件驅動。內部包含了一系列的Command,當被驅動時,會去執行command
  • Service是靜態宣告,下邊包含了許多Options用來形容這個Service是什麼,要做什麼
  • init.rc及其import的各種rc檔案會被init解析,化為ActionManager & ServiiceList中的資料集合
  • AM中存在一個事件佇列event_queue,佇列中依次包含著如early-init/init/late-init等狀態,以及也會從中插入一些特殊的時序敏感的Action,他們都會按順序依次執行
  • 當事件被驅動起來,繫結(依賴、監聽)在其上的Action動了起來,Action中的Commands動了起來,包括一些setprop、chown等命令操作,同樣也包含了對Service的start操作

Next

zygote孵化器程式

相關文章