android init進程分析 init指令碼解析和處理
(懶人最近想起我還有csdn好久沒打理了,這個android init躺在我的草稿箱中快5年了,稍微改改發出來吧)RC檔案格式
rc檔案是linux中常見的啟動載入階段執行的檔案,rc是run commands的縮寫,基本上可以理解為在啟動階段執行的一些列命令。android init進程啟動時,也會執行此啟動指令檔,init.rc。init.rc的寫法稍有點複雜,詳細可參考 /system/core/init下的readme檔案。指令碼基本組成是由四類,為:
commands: 命令action: 動作services: 服務options: 選項
該語言的文法包括下列約定:
所有類型的語句都是基於行(line-oriented)的,一個語句包含若干個tokens,token之間通過空白字元分隔。如果一個token中需要包含空白字元,則需要通過C語言風格的反斜線('')來轉義,或者使用雙引號把整個token引起來。反斜線還可以出現在一行的末尾,表示下一行的內容仍然屬於當前語句。以'#'開始的行是注釋行(注釋只能是行的第一個非Null 字元,這個千萬要注意)。動作(Actions)和服務(Services)語句隱含表示一個新的段落(section)的開始。所有的指令(commands)和選項(options)歸屬於上方最近的一個段落。在第一個段落之前的指令(commands)和選項(options)是無效的。動作(Actions)和服務(Services)擁有唯一性的名字。如果出現重名,那麼後出現的定義將被作為錯誤忽略掉【目前還是忽略,不是覆蓋!】。
其實,嚴格來說,應該就只有兩種類別Action和Service:
Action是由特定trigger下的一堆command組成 Service是由一堆options描述的daemon服務組成
動作(Action)
動作(Action)是一個有名字的指令(commands)序列。每個動作(Action)都定義一個觸發條件(trigger),用於指示什麼時候執行這個動作。在實際處理中,當與動作的觸發器匹配的事件發生時,該動作下的命令,將被添加到一個即將被執行的隊列的隊尾(除非它已經在隊列中)。
隊列中的每一個命令被依次取出執行。
一個動作定義的形式如下:
on
服務(Services)
服務是初始化程式需要啟動的一些程式,初始化程式還有可能會在這些程式退出之後重啟它們。Services take一個服務定義的形式如下:
service [ ]* ...
選項(Options)
選項將影響控制初始化程式運行服務的時機和方法。可能的選項如下表(注意,options只是作為服務的附屬部分的)..
說明:
disabled
服務在啟動所屬的class的時候,不會自動隨之啟動,只有明確指明要啟動此服務時才會啟動(通常每個服務都會屬於一個class中,由class關鍵字描述)
socket [ [ ] ]
Create a unix domain socket named/dev/socket/ and pass its fd to the launched process. Valid values includedgram andstream.user andgroup default to 0.
user
在exec啟動此服務之前,改變進程的user,預設user是root
group [ ]*
周期exec啟動服務之前,改變此服務進程的group. 預設group是root。將服務加入特定的group,可以擷取一些特定的許可權。
capability [ ]+
Set linux capability before exec'ing this service
oneshot
服務只執行一次。對於非daemon程式,必須要使用onshot模式。
class
設定服務的class名字,所有的服務都必須設定一個,如果不設定,預設是屬於default class的。同一個class中的服務,可以通過命令class_start統一啟動。
觸發器(Triggers)
觸發器是一個字串,用於匹配特定的事件,這些事件將觸發觸發器所屬動作(Action)的執行。
指令(Commands)
支援哪些指令,各個android版本不盡相同,詳情可參考/system/core/init下的readme或最好看代碼
屬性(Properties)
初始化程式(Init)可以根據需要修改一些系統的屬性。readme中說的三個:
init.action
Equal to the name of the action currently being executed or if none.
init.command
Equal to the command being executed or if none.
init.svc.
State of a named service (stopped, running, or restarting).
實際init進程只看到更新 init.svc., 每個服務都會建立一個這樣的property,可以通過 getprop | grep init.svc 看到每個服務的狀態,狀態切換的時候,會同時更新。
void notify_service_state(const char *name, const char *state){ char pname[PROP_NAME_MAX]; int len = strlen(name); if ((len + 10) > PROP_NAME_MAX) return; snprintf(pname, sizeof(pname), init.svc.%s, name); property_set(pname, state);}
其他兩個沒意義,實際也沒用,可能當初設計有吧,但最後也沒用(comman/action都是開機時執行,且期間都很短,沒太大用處)。init 的parser
Init的parser是比較簡單的自動機,代碼也不算複雜,主要parser的是三部分:
service: 每個service的描述及屬性,parser好了,放在service_list中,以便以後通過actions的命令啟動。actions: parser每個動作, on XXX及後繼的command,就是一個action,這個parser好後,也是放在action_list中,以便在init中啟動。import: init.rc 可以支援像c中的include一樣,包含其他init.rc,這個支援比較簡單,並不是相當於在import位置插入被import的代碼,而是在主init.rc都parser好後,再依次parser被包含的rc檔案。
init.rc指令碼命令的執行
指令碼解析好後,會將各個Actions按照時間順序,分別推到action_queue中,再一個一個執行。
actions按照trigger的時間順序,依次執行的是,預設trigger的順序如下:
action_for_each_trigger(early-init, action_add_queue_tail); action_for_each_trigger(init, action_add_queue_tail); action_for_each_trigger(early-fs, action_add_queue_tail); action_for_each_trigger(fs, action_add_queue_tail); action_for_each_trigger(post-fs, action_add_queue_tail); action_for_each_trigger(post-fs-data, action_add_queue_tail); action_for_each_trigger(early-boot, action_add_queue_tail); action_for_each_trigger(boot, action_add_queue_tail);
因此,各個命令需要特別注意處理好依賴。比如在init階段,是不可能訪問data分區的。另外還要注意一下, 同一個階段中,import的rc檔案,是在後面一些執行的(根據import檔案的parser順序)。
service的啟動時間,需要注意一下,也是通過action中的command執行的, 比如在on boot階段:
on boot ... class_start core
這樣,在boot階段執行到這個命令時,所有屬於core這一類的服務,都會被逐個啟動。
?
?? ?? ??