天天看點

Android啟動過程分析(1)

從系統的角度看,Android的啟動過程可以分為bootloader引導、裝載和啟動Linux核心,啟動Andorid系統的3個大的階段。其中Android系統的啟動還可以細分為啟動Init程序,啟動Zygote,啟動SytemServer,啟動各項服務等多個階段。
(1) Bootloader的引導
    Bootloader的主要的作用是初始化基本的硬體裝置(如CPU、記憶體、Flash等)并且通過建立記憶體空間映射,為裝載Linux核心準備好合适的運作環境。
(2) 裝載和啟動Linux核心
    Android的boot.img存放的就是Linux核心和一個根檔案系統(ramdisk.img)。Bootloader會把boot.img裝載進記憶體。然後Linux核心會執行整個系統的初始化。完成後裝載的跟檔案系統,這裡的跟檔案系統就是ramdisk.img,它是out目錄下的root目錄打包生成的,在裡面可以看到init程序的執行檔案和一些需要解析的檔案。而init程序,就是核心加載完畢後第一個執行的使用者程序。
(3) 啟動Init程序
    在init程序的啟動過程中,會解析Linux的配置腳本init.rc。根據init.rc檔案的内容,init程序會裝載Android的檔案系統,建立系統目錄,啟動Android系統重要的守護程序(服務)。同時還會初始化屬性系統,解析屬性檔案,配置系統屬性。設定selinux安全上下文。
    最後init程序也會作為守護程序來執行修改屬性請求,重新開機崩潰的程序的操作。
1.init程序的初始化過程
    init程序的源碼位于system/core/init目錄下。在M的代碼中,init的源檔案從C變為了C++。雖然在實作方式上有所改變,但大體流程上還是和前面的版本一緻的。
    (1) 進入main函數後,首先檢查啟動程式的檔案名。如果檔案名是“uevented”或者“watchdogd”,就會執行對應的main函數。然後清楚系統掩碼,添加環境變量,判斷目前是否是first_stage,也就是從核心來的第一次啟動。在下面的代碼中,init會再次啟動自己一次。從注釋上看,在第一次啟動的時候,目前處于kernel domain,将會去加載selinux policy。
    接下來會建立目錄,并挂載檔案系統。将标準輸入,标準輸出和标準錯誤重定向到空檔案中。初始化在init中的log系統,在init程序中可以用kernel的log系統來輸出log. 建立.booting檔案,通過該檔案是否存在判斷目前init程序啟動是否完成,該檔案将在init.rc的boot行為中被删除。初始化屬性系統,從dt或者kernel cmdline讀取屬性進行設定。重新設定安全上下文,注冊信号處理機制,用于接受子程序的死亡信号。讀取default.prop,設定屬性。在屬性中設定目前oem的狀态,啟動屬性服務,監聽屬性的修改。接下來就是對init.rc的解析了。
    init.rc是以塊(section)為機關組織的,一個“塊”可以包含多行。“塊”分為兩大類:一類稱為行為“行為(action)”;另一類稱為“服務(service)”。“行為”塊以關鍵字“on”開始,表示一堆指令的集合,“服務”塊以關鍵字“service”開始。
    無論是“行為”塊還是“服務”塊,并不是按照檔案中的編排的順序逐一執行的。它們隻是一份放在這裡的定義,至于執行與否以及何時執行都是由init程序在運作時決定的。
    “行為(action)”的關鍵字後面跟的字元串稱為“觸發器(trigger)”。“觸發器”後面是指令清單。指令清單中的每一行都是一條指令,指令的種類非常多,比如使用start可以啟動一個服務。“觸發器”有幾種格式,一般常見的有兩種,一種是一個單純的字元串。這種簡單的格式可以使用指令“trigger”來觸發。另一種常見的格式是“on prpperty:<屬性>=“<值>”,如果屬性值在運作時設定成了這裡的指定的值,則“塊”中的指令清單就會執行。接下來,我們看幾個具體的"行為"塊:
    “服務(service)”塊的關鍵字“service”後面是服務名稱。我們可以使用"start"指令加“服務名稱”來啟動一個服務。服務名稱後面的是程序的可執行檔案的路徑和啟動參數。關鍵字“service”以下的行稱為“選項”,每個選項占一行,選項也有很多種,接下來,我們也看幾個具體的“服務”塊。
    由于時間有限,init_prase_config_file()就不具體講解,在init子產品中定義了三個全局的清單service_list、action_list和action_queue.service_list清單包括了啟動腳本中所有的“service”,action_list清單包括了腳本中所有的“action”,Init腳本解析的結果就是生成這兩張清單。action_queue清單則儲存了正在執行中的“action”,Init的main()函數會把需要執行的action插入到action_queue中。
    接下來的action_for_each_trigger()函數就是用來将制定的action插入到action_queue中 。如果系統不在充電模式下,則把“late_init” action 添加到執行清單中; 如果在充電模式, 将“charger”加入到執行清單中。
    queuebuiltin_action()函數用來動态的生成一個action并插入到action_queue中。插入的action由一個函數指針和一個表示名字的字元串組成。Android以前的版本中直接調用這兒函數來完成某些初始化的工作,但是,這些函數可能會依賴init.rc裡定義的一些指令和服務的執行情況。現在把這些這些初始化函數也通過action插入到執行清單中,這樣就能控制它們的執行順序了。
    main()腳本中所有的“action”,Init腳本解析的結果就是生成這兩張清單。action_queue清單則儲存了正在執行中的“action”,Init的main()函數會把需要執行的action插入到action_queue中。
    接下來的action_for_each_trigger()函數就是用來将制定的action插入到action_queue中 。如果系統不在充電模式下,則把“late_init” action 添加到執行清單中; 如果在充電模式, 将“charger”加入到執行清單中。
    queue_builtin_action()函數用來動态的生成一個action并插入到action_queue中。插入的action由一個函數指針和一個表示名字的字元串組成。Android以前的版本中直接調用這兒函數來完成某些初始化的工作,但是,這些函數可能會依賴init.rc裡定義的一些指令和服務的執行情況。現在把這些這些初始化函數也通過action插入到執行清單中,這樣就能控制它們的執行順序了。
    main()函數最後會進入一個無限for循環,每次循環開始都會調用execute_one_command()函數來執行指令清單中的一條指令,同時調用restart_processes來重新開機需要重新開機的服務程序。
    init程序初始化系統後,會化身為守護程序來處理來處理子進的死亡信号,修改屬性的請求群組合鍵盤事件。
    2.啟動service程序
    在前面介紹action中,講到會使用start的可以啟動服務,最終會調到service_start()函數。
    1.重置service結構中的标志
    2.如果服務需要控制台,但是還沒有啟動控制台就退出
    3.檢查Service的二進制檔案是否存在
    4.檢查SVC_ONESHOT參數
    5.設定安全上下文
    6.fork子程序
    7.準備環境變量
    8.建立socket
    9.處理标準輸入、标準輸入、标準錯誤3個檔案描述符。
    10.執行exec
    執行完exec後,init程序的記憶體映像就被被替換成新檔案的映像。上面的代碼中調用的是execve()h函數,它可以同時設定環境變量。s