要看什麼?
Android系統總體是圍繞著Linux核心而建立的。從加電到系統啟動,大體上經過:
- bootloader
- kernel
- 使用者空間(init、zygote、framework、launcher等)
- kernel
bootloader,距離App開發者太遠了,是廠商最為關心的東西。某種角度上講,其特定性太高,比如加電後我就要從記憶體晶片的第多少個offset偏移指標開始跑程式。很多是底層協議的東西,理解起來晦澀難懂且幫助不大。所以這裡選擇直接從Linux核心作為第一起點,以更加通用的邏輯切入系統的啟動。
在系統啟動系列文章中,所確立的主線是從加電啟動到第一個app(也就是launcher)啟動。所以,有過一定了解的朋友應該能夠get到,核心是zygote。像binder會另闢系列文章加以剖析。
Let's go!
Linux系統的啟動
head.S
-
原始碼位置:kernel/common/arch/arm64/kernel/head.S
機器加電後,不同的CPU根據不同的記憶體偏移設定,開始制定對應的彙編指令。以arm64架構為例,在插電後執行到了這裡,指向了start_kernel
main.c
-
原始碼位置:kernel/common/init.main.c
main.c將start_kernel暴露給彙編空間得以呼叫。被呼叫時,這時是處於核心態的,經過一些列核心的初始化,最後呼叫了arch_call_rest_init()
在rest_init中,通過kernel_thread開啟了pid為1的程式(0已經在sched_init中初始化的idle thread佔用走了,有興趣自行研習下吧)
kernel_thread很簡單粗暴,就是用來在核心態中開啟一個執行緒的(也可以理解為程式,在核心態中並沒有特殊的資料結構來區分執行緒和程式,大意可以理解為一個東西)
kernel_init這個function指標是重要的
經過裁剪,核心程式碼如圖。其中兩個execute_command是兩個全域性靜態變數,接收外部指定:
arm結構沒有設定,但結合其他架構配置檔案的設定可以看到,基本就是指向了init
再結合最後的兜底程式碼
可以確定,核心的1號程式(pid)的執行內容是使用者空間的init
init 使用者空間的第一個應用程式
通過對Soong編譯系統的基本學習,不難發現,name為init的cc_binary就再system/core/init下:
同時,還可以發現,ueventd和watchdogd其實也是init這個應用程式所做的
main.cpp
通過Android.bp的指引,看下main.cpp做了什麼:
通過namespace的限定,可以定位到init.cpp
init.cpp
- 原始碼位置:system/core/init/init.cpp
init 作為第一個使用者態的應用程式,做了非常多的工作,包括設定環境變數、掛載FS、許可權設定等。基於探索Android系統啟動的核心,我們挑最關心的部分看
在看原始碼之前,可以先去了解下Android初始化語言的規範文件。
初始化語言中,最重要的是兩個角色:
Action
可以理解為當XXX發生時候,怎麼怎麼樣:
比如截圖中:當到達early-init時,向某裝置寫入什麼值。這裡write這一行是command(命令),是具體的行為內容
Service
而服務是宣告、定義。
如圖中所示,宣告瞭名為ueventd的服務,內容在下邊描述,具體的執行檔案是 /sbin/ueventd
基於對於Action和Service的初步瞭解,下邊看原始碼:
main()
epoll
在開始解析配置檔案init.rc等之前,建立了一個epoll控制程式碼:
在建立epoll控制程式碼後,馬上註冊了關於SIGCHLD的訊號處理回撥。
epoll是Linux中的一項多路複用技術,自2.5版本引入。
這裡對於epoll的使用是說,init應用程式響應其所啟動的各種子程式的SIGCHLD訊號(即子程式退出、終止),根據相關資料,會有需要對一些子程式做重啟操作。在這,是將SIGCHLD訊號打到了socket上,讓epoll監聽此socket。在後邊還有幾處使用epoll來監聽事件的操作,這裡不做過多展開了
解析
緊接著,去解析了Action和Service。解析後所構造出來的Action和Service例項儲存在各自Manager、List的Vector中(詳見service.h & action_manager.h)。
解析的過程:
通過原始碼可以看到,可以指定初始化配置檔案的地方非常多,甚至可以用環境變數。翻遍AOSP原始碼,也沒有人去設定ro.boot.init_rc這個環境變數,所以都會是init.rc。
init.rc在於檔案系統的根目錄,而其攜帶者是boot.img,也就是說他被編譯打包在boot.img中。boot.img和system.img以及recovery.img,相信有同學會很熟悉,都是需要通過BootLoader刷機才可以進行更新的。所以常規渠道來講,只要手機出廠,init.rc中相關資訊就是不可修改的
而解析的動作,是由Parser執行的:
結合前邊給出的一些init.rc的配置片段,確定service開頭的line被ServiceParser去解析,而on被ActionParser去解析,import最後可以理解為遞迴的解析每一個所引入的其他rc檔案。
具體對文字內容逐行解析的過程在system/core/init/parser.cpp#ParseData()中,本文不做具體解析
解析結束後:
- actions儲存於ActionManager中
- services儲存於ServiceList中
觸發
上邊說了,Service是靜態的宣告、定義。那麼事件的驅動,一定是要靠Action推動的,所以觸發的源頭就是Action的流轉:
這裡ActionManager是把各種事件內容都放到了內部的佇列中,並沒有進行觸發:
而其所能夠接受的型別有三種:
其分別對應ActionManager的三個操作:
而init的main()中只用了1和3兩種方式,很多都是通過內建Action的方式對已經解析出的**actiions_**做了擴充。
下邊就是真正會去觸發action的地方:
while-true (A)
-
進入了無限迴圈,沒有退出的case。這裡的設計思路是:
- 通過-1超時時間的設計,在沒有事件發生的時候,通過epoll可做到完全休眠,還不浪費系統資源
- 如果真的有了epoll時間,那麼每次喚醒後的動作也是在這個while-true中進行處理的
-
關機或重啟事件是一個優先需要處理的,其對應著property服務的處理過程(題外話):
而handle_property_set_fd內部最終就會對應著以下內容:
這樣,也就不難理解,關機、重啟的邏輯是怎麼來的了。
- 再然後,正常的,會得到一個純淨的可執行環境,通過AM去執行一條命令
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孵化器程式