Android 設備啟動必須經歷 3 個階段,1 BootLoader、2 Linux Kernel、3 Android 系統服務,嚴格來說 Android 系統實際上是運行在 Linux 內核之上的 服務進程,並不算真正的 操作系統
Android 的第一個啟動進程,init 的 PID 為 1,這個進程會 解析 init.rc
來建構出系統的初始運作型態,其他的系統才會相繼啟動
啟動電源 & 系統啟動
當電源啟動後,會從預定的地方 (PC 讀取 ROM),開始加載程序(BootLoader)到 RAM 中,並開始執行
引導程序 BootLoader
引導程序 BootLoader 是在 Android 系統開始運行前的一個小程序,主要功能是把系統 OS 拉起 並 運行
Loader 分為兩個部分 (Android 引導程式)
第一階段,檢測外部的 RAM 以及加載第二階段的程式
第二階段,設定網路、內存 … 等等 必要功能
init.s
初始化堆棧,清空 BBS,調用 main() 函數
main.c
初始化硬體、創建 Linux 標籤
Linux 內核啟動
當內核啟動時,設定緩存、保護儲存器、計畫列表、加載驅動… 在內核完成系統設置後,它會 在系統中找 init.rc
文件,並 啟動 init 進程
init 進程啟動
該進程主要是作 初始化 & 啟動系統屬性服務,也用來啟動 Zygote 進程
Launcher App 應用(桌面應用程式)
-啟動流程圖-
先了解進程之間的關係,因為接下來會接觸到多個進程 (init
、zygote
、server manager
、service_manager
(Android DNS)、app
… 等等),每個進程之間 fork 的方案也不同
這個章節著重講解的是 init 進程,但了解它的來源也是相當重要的,請參考下圖
init 作為 Android 系統啟動的第一個進程,通過 解析 init.rc
來陸續啟動關鍵的系統服務進程,其有三個重點服務進程
ServiceManager (類似 DNS 功能,用來查找 SystemServer 中的服務控制碼)
Zygote (初始進程,加載必要 Anroid resource、啟動虛擬機… 等等)
SystemServer (啟動系統級別服務,像是 AMS、WMS… 服務)
init 進程會啟動 Zygote、SystemServer 兩個進程,而 Zygote 使用 Socket 監聽 AMS、ServiceManager 使用 Binder 通訊 (詳見下圖)
創建、掛載啟動所需要的文件目錄
初始化、啟動性服務
解析 init.rc
配置文件並 啟動 Zygote 進程
以下是每個進程使用的通訊方式,大部分都使用 binder 通訊
已往啟動入口是 init.cpp#main 函數,Android 10 後改為 main.cpp 啟動,接下來用 Android 10 並分為 2 個階段來說明
FirstStageMain (第一階段)
SecondStageMain (第二階段)
/init/main.cpp
檔案並呼叫 main
函數execv
函數 (再次載入 main.cpp,但帶入不同參數)掛載的文件都在記憶體中 (RAM),並實體文件
使用指令 | 掛載目錄/文件名、檔案管理 | 特性 | 說明 |
---|---|---|---|
mount | tmpfs | 存在 RAM 中,並不持久 (tmp 的取名) | 虛擬內存文件系統,它將所有的 文件存在虛擬內存中,將 tmpfs 卸載後,內部的資料就會全部消失 |
mount | /dev/devpts | 動態掛載 devpts | 為偽終端提供了一個標準,只要 pty 的主復合設備 /dev/ptmx 被開啟,就會去 /dev/pts 下動態創建一個新的 pty 設備文件 |
mount | proc | 可在運行時修改內核參數 | 虛擬文件系統,可以看做內核內部數據結構的接口~ |
mount | sysfs | Linux 2.6 內核引入 (主要針對 硬體相關參數,這就有關於 devpts 設備) | 虛擬文件系統,通常被掛載在 /sys 目錄下 |
CHECKCALL
宏
用來檢查執行函數的返回,若執行的函數有錯誤 (返回不為 0) 就會執行 errors.emplace_back
函數,並將錯誤記錄下來
掛載特定分區設備
SELinux 相關工作
透過 execv
啟動 init 進程的 SecondStageMain 方法
execv
函數
會覆蓋當前 ELF 檔案,執行制定 ELF 檔案。當前的狀況是掛載完後,並設定主要參數,再次啟動 main.cpp
在第二階段之前會先進行 SE 設定
在經過 first_stage_init#FirstStageMain 文件掛載後,會透過 execv
函數再次啟動 main.cpp 行程,再次進入 main.cpp#main 方法
SetupSelinux 主要是在加載 SE 策略,加載 SE 完成後,就會 設定新參數,再次透過 execv
啟動 main.cpp (如上一小節說明)
經過 first_stage_init 的初始化掛載設備後,就會執行到 main.cpp 類的 SecondStageMain 函數 (可以看做 init 進城的第二階段)
開始分析 init.cpp#SecondStageMain,分為 10 個階段,詳細請看註解
初始化屬性域,主要就是做 PropertyInit 函數
清空環境變量: 清除之前寫到系統屬性中的環境變數
SELinux 相關工作
在第一階段時也有處理 SELinux 的部分工作
第一階段主要是做載入安全策略 (SetInitAvbVersionInRecovery)
第二階段是為了註冊一些處理器
創建 epoll 事件通知
裝載子進程信號處理器: init
是一個 守護進程 (可理解為 守護線程),以該進程為主,若該 init 進程結束,其他子進程都會結束
殭屍進程 (zombie process)
簡單來說就是沒在運作,卻仍佔資源的進程
當 init 進程的子進程結束時會發送 Signal 通知,收到通知後就會移除該進程資料,若是沒有移除,則會造成資源浪費 & 到達進程限制上限時就無法再創建進程 (每個系統規定不同)
系統在 init 子進程暫停、終止的時候,讓 init 會收到 SIGCHLD
信號
啟動屬性服務: 主要函數 StartPropertyService,啟動一個 socket 監聽屬性更改
下圖透過 adb 取得目前裝置所有的屬性數量
匹配命令 & 函數之間對應關係: Cmd & Function Map,從這可以看出系統支援哪些 cmd 指令,還有指令實際運作的方法
GetBuiltinFunctionMap 截圖
LoadBootScripts: 解析 init.rc
檔案
添加觸發事件: 這邊可以看到 rc 檔的一些設定,其中常見的指令包括,1 early-init
、2 init
、3 late-init
進入循環: 不斷處理接收到的命令 & 執行 Service
.rc
檔案並進行分析,關於這個我有另外寫一篇 Android 啟動過程 - RC 檔案解析StartPropertyService 方法:
初始化 ro.property_service.version
屬性服務,將其值設定為 2
創建兩個 Socket
init.cpp
使用property_service.cpp
使用透過 CreateSocket 方法,創建 "property_service
" Socket,並監聽該文件,如果有事件就會呼叫 PropertyServiceThread
方法
CreateSocket 方法
在 /dev/socket
中創建 Socket,Socket 取名會用 ANDROID_SOCKET_{名稱}
InitPropertySet 初始化屬性服務
透過 adb getprop
讀取 ro.property
屬性
在創建出 Socket 後,會透過 epoll 來監聽 Socket 事件 (property_set_fd),當 epoll 收到事件後會執行 PropertyServiceThread 函數
檢查 epoll#Open 結果
透過 RegisterHandler
函數 設定事件的處理函數為 handle_property_set_fd
(該函數會處理用戶請求)
進入迴圈,處理等待事件
Linux 內核中,epoll 用來替換 select
方式 | 數據結構 | 特色 |
---|---|---|
epoll | 紅黑數 | 查找快 |
select | Array | 查找慢 |
epoll 概述:
是 Linux 用來處理大批量文件而改進的 poll,它能顯著提高程序在大量併發連接中,只有少量活躍的強況下的系統 CPU
在屬性啟動後,就可以透過 epoll 監聽 Socket 的改變,並進行相對應的處理,而這裡處理的函數就是 handle_property_set_fd
HandlePropertySet 函數:判斷使用者設定的屬性,並執行對應的行為,主要分為 兩種
控制屬性:屬性用 ctl
開頭
普通屬性
PropertySet 函數:首先檢查屬性名、數據是否合法,最後再設定屬性
ro.
也就是 read only,ro.
開頭屬性 只能設定一次,如果進入這是 .ro
屬性則會返回錯誤Android 系統
Android.bp