代碼如下
INFO("reading config file\n");
init_parse_config_file("/init.rc")
init_parse_config_file("/init.rc")
在這個函數中將會加載Android系統/init.rc文件。
這個函數將init.rc讀入內存,解析其內容(遇到import關鍵字,則加載對應的文件),
1)Actions
針對Actions,創建struct action數據結構。為Actions的每個command創建一個struct comand數據結構,struct actions存儲struct command的鏈表。
相關數據結構代碼如下
init_parser.c
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);
service_list是全局service鏈表,解析啟動腳本過程中,service對應數據結構struct service將會掛載到這裡。
action_list是全局Actions鏈表,Actions對應數據結構struct action將會掛載到這裡。
至於action_queue在解析完畢之後,實際執行Actions時才會用到。
init.h
struct command
{
/* list of commands in an action */
struct listnode clist;
int (*func)(int nargs, char **args);
int line;
const char *filename;
int nargs;
char *args[1];
};
struct action {
/* node in list of all actions */
struct listnode alist;
/* node in the queue of pending actions */
struct listnode qlist;
/* node in list of actions for a trigger */
struct listnode tlist;
unsigned hash;
const char *name;
struct listnode commands;
struct command *current;
};
struct command各字段含義如下:
clist是“掛載”於鏈表的鉤子
line變量存放對應的command語句在init腳本文件(可能是init.rc、init.usb.rc等)行號。
filename存放所在init腳本文件的名字
nargs存放command語句所表示的命令對應的函數的參數個數,
args是struct command的最後一個變量,看起來是一個單元素的數組,但是在init.rc解析過程中,確定nargs後,使用malloc申請內存時,args所表示的數組長度將是 n+1個。(與0長度數組的實現類似)。
func字段存放command語句所對應的函數指針。
struct actions各字段含義如下:
alist變量是action_list鏈表鉤子
qlist是action_queue鏈表鉤子
類似,tlist的也是一個特殊鏈表的鉤子
hash在目前init實現中未使用
name存放Actions的名字(也就是Actions的trigger)
commands存放該Actions所有struct command鏈表
current在Actions執行時,存儲當前正在被執行的struct command指針。
actions_list是一個雙向鏈表,其每一個節點都是struct action結構的alist對象,當init.rc解析完畢之後,二者效果如下所示。為了簡單,鏈表只繪制了兩個節點。
init_parse_config_file執行完畢後,解析出啟動腳本中所有的Actions,並構造出上圖所示的數據結構。可見利用action_list,可以遍歷啟動腳本中所有Actions,並能遍歷出單個Actions中的所有command。
2)service
init在解析init啟動腳本,為每個service字段創建一個struct service數據結構
struct service {
/* list of all services */
struct listnode slist; //用於掛載於service_list的鉤子
const char *name; //存放service的名稱
const char *classname; //用於存放該service所隸屬的class的名稱
unsigned flags; //位圖變量,其各個位代表不同的servcie的屬性(對應service中的option字段)
pid_t pid; //當service對應的程序執行時,存放其進程號
time_t time_started; /* time of last start */ //存放進程啟動時間
time_t time_crashed; /* first crash within inspection window */ //存放第一次進程崩潰時間
int nr_crashed; /* number of times crashed within window */ //存放進程崩潰次數
uid_t uid; //該servcie對應進程的uid
gid_t gid; //該service對應進程的gidinit_parse_config_file("/init.rc");
gid_t supp_gids[NR_SVC_SUPP_GIDS];//該service對應進程的附加群組id
size_t nr_supp_gids; //該service所隸屬的附件組的數目
char *seclabel; //存放selinux所需要的security context
struct socketinfo *sockets; struct svcenvinfo *envvars;
struct action onrestart; /* Actions to execute on restart. */
/* keycodes for triggering this service via /dev/keychord */
int *keycodes;"queue_property_triggers"
int nkeycodes;
int keychord_id;
int ioprio_class;
int ioprio_pri;
int nargs; //對應service語句傳入的參數數目
/* "MUST BE AT THE END OF THE STRUCT" */
char *args[1]; //存放service語句實際傳入的參數,其長度將會被修正為nargs+1
}; /* ^-------'args' MUST be at the end of this struct! */
解析完畢後,構造出如下所示的數據結構(示意圖僅畫鏈表僅繪制兩個節點)"queue_property_triggers"
一個比較奇怪的地方在於,為什麼在struct service中為什麼出現了struct action?這與service的onrestart屬性有關,該屬性比較特殊,在init.rc存在如下service:
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart healthd
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm
onrestart屬性後必須跟restart關鍵字,隨後必須再跟一個service名稱。
這段含義是定義一個名為servicemanager的service,對應的可執行程序為/system/bin/servicemanager,其多個option則設定servicemanager進程退出時,重啟healthd、zygote、media、surfaceflinger、drm等service。
每個service均對應一個可執行程序,類似與一個Actions中的command,多條onrestart語句即多個command,可以認為一個service似乎隱含一個Actions,因此在struct service中包含了一個struct action,從而代碼復用。
每個屬性(option)由struct service中flags的一位表示,flags各個位含義如下(由於英文比較直觀,不具體翻譯了)
//init.h
#define SVC_DISABLED 0x01 /* do not autostart with class */
#define SVC_ONESHOT 0x02 /* do not restart on exit */
#define SVC_RUNNING 0x04 /* currently active */
#define SVC_RESTARTING 0x08 /* waiting to restart */
#define SVC_CONSOLE 0x10 /* requires console */
#define SVC_CRITICAL 0x20 /* will reboot into recovery if keeps crashing */
#define SVC_RESET 0x40 /* Use when stopping a process, but not disabling
so it can be restarted with its class */
#define SVC_RC_DISABLED 0x80 /* Remember if the disabled flag was set in the rc script */
#define SVC_RESTART 0x100 /* Use to safely restart (stop, wait, start) a service */
#define SVC_DISABLED_START 0x200 /* a start was requested but it was disabled at the time */
至此函數init_parse_config_file("/init.rc")核心功能介紹完畢,其具體代碼就不贅述了。感興趣的代碼可以深入分析init.rc解析引擎的實現,非常有意思。
再強調一下,該函數執行在解析init啟動腳本後,只是針對Actions和service創建數據結構,並分別添加到全局鏈表action_list與service_list中,僅此而已,並沒有執行任何一條Actions command,或是啟動任何一個service進程。此時action_queue還未添加任何有效節點。
繼續分析代碼init.c隨後的代碼。
init.c main函數代碼
action_for_each_trigger("early-init", action_add_queue_tail);
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_lin一部分在ux_rng");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail);
/* skip mounting filesystems in charger mode */
if (!is_charger) {
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);
}
/* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
* wasn't ready immediately after wait_for_coldboot_done
*/
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");
if (is_charger) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
}
/* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
void action_for_each_trigger(const char *trigger, void (*func)(struct action *act))
該函數利用字符串trigger從從action_list中查找特定Actions,為每個符合條件的Actions執行action_add_queue_tail函數,也就是說將action_list中所有觸發器為trigger的Actions添加到action_queue中。
void queue_builtin_action(int (*func)(int nargs, char **args), char *name)
該函數完成兩個操作:
1)構造一個觸發器為name的struct action結構體,並創建一個struct command,對應函數為行參func
2)將struct action添加到action_queue鏈表末尾。
從名字上來看action_queue就是Actions隊列,而隊列具有FIFO特性。事實也是如此,action_queue可以用於控制Actions執行的順序。
上述代碼結束後,action_queue鏈表最終具有如下形式,每個鏈表都是struct action數據結構,各節點通過struct action中的qlist變量彼此連接。
白色節點來自init啟動腳本,橘紅色節點則有函數queue_builtin_action創建。
現在萬事具備,只欠東風。接下來就是如何執行這些Actions了。
init.c main
for(;;) {
execute_one_command();
...
}
execute_one_command函數分析
概括的說,這個函數每次從action_queue開頭取出一個Actions,同時移除對應節點,依次執行每個command。由與main函數中for循環語句的存在,最終所有的action_queue中所有Actions都會被執行。大致執行流程如此。
簡要總結下:init進程的邏輯時,任何一個Actions想要被執行,只要把自己放到action_queue中,然後在init進程的for循環中就會在之後的某個時間被執行。
細心的讀者已經發現,為什麼action_queue中的Actions如此之少(請參考本文前面介紹Actions時),至少“post-fs”、“post-fs-data”、“boot”,以及"property:=”等Actions怎麼沒有?
這是因為init進程耍了花招,它把這部分Actions的觸發放到了init.rc裡,由Actions的一條特殊command: "trigger"觸發。請注意,該trigger與Actions的名字(也稱為trigger)不同。
見init.rc
on late-init
trigger early-fs
trigger fs
trigger post-fs
trigger post-fs-data
# Load properties from /system/ + /factory after fs mount. Place
# this in another action so that the load will be scheduled after the prior
# issued fs triggers have completed.
trigger load_all_props_action
# Remove a file to wake up anything waiting for firmware.
trigger firmware_mounts_complete
trigger early-boot
trigger boot
看到這裡,先大膽猜測當init進程執行到“late-init” Actions時,對於每條trigger語句,取出後面所跟的字符串,從action_list(“媽蛋,終於想起我來了”)中找出該Actions對應的struct action結構,添加到action_queue中。快去看看代碼確定一下是否如此。怎麼找到command對應代碼?前面提到init啟動腳本所有關鍵字都keyword.h中,
//keyword.h
//-------------
KEYWORD(trigger, COMMAND, 1, do_trigger)
//builtins.c
//-------------
int do_trigger(int nargs, char **args)
{
action_for_each_trigger(args[1], action_add_queue_tail);
return 0;
}
bingo!原來trigger命令對應的函數do_trigger中再次調用了action_for_each_trigger,可見之前的猜測正確!
至於"property:=”類型的Actions由於需要在屬性條件滿足時被執行,在android4.4的init的實現中對這類Actions的處理比較特殊,有兩種方式:
(1) 通過action_queue,配合execute_one_command處理
參考上面action_queue鏈表圖,在main函數中使用queue_builtin_action創建了一個特殊的Actions:"queue_property_triggers",該Actions內只有一個command,對應函數為queue_property_triggers_action,代碼如下:
static int queue_property_triggers_action(int nargs, char **args)
{
queue_all_property_triggers();
/* enable property triggers */
property_triggers_enabled = 1;
return 0;
}
void queue_all_property_triggers()
{
struct listnode *node;
struct action *act;
list_for_each(node, &action_list) {
act = node_to_item(node, struct action, alist);
if (!strncmp(act->name, "property:", strlen("property:"))) {
/* parse property name and value
syntax is property:= */
const char* name = act->name + strlen("property:");
const char* equals = strchr(name, '=');
if (equals) {
char prop_name[PROP_NAME_MAX + 1];
char value[PROP_VALUE_MAX];新浪微博
int length = equals - name;
if (length > PROP_NAME_MAX) {
ERROR("property name too long in trigger %s", act->name);
} else {
int ret;
memcpy(prop_name, name, length);
prop_name[length] = 0;
/* does the property exist, and match the trigger value? */
ret = property_get(prop_name, value);
if (ret > 0 && (!strcmp(equals + 1, value) ||
!strcmp(equals + 1, "*"))) {
action_add_queue_tail(act);
}
}
}
}
}
}
可見queue_all_property_triggers函數中會檢查從action_list中查找所有property型的Actions,並判斷其屬性條件是否滿足,若滿足則使用action_add_queue_tail(act)將其添加到action_queue中。
仔細考慮一下,這種方式並不能保證所有property型的Actions都能在屬性滿足後被觸發執行,參考action_queue鏈表圖,這種方式僅僅能保證在"queue_property_triggers"之前的Actions執行中所設置的屬性。設想這種情況,在init.rc添加某個Actions,希望可以在Android命令行終端中設置屬性(android的set_property命令可以設置android屬性),從而觸發某個動作。僅僅通過action_queue配合execute_one_command是難以優雅實現的,所以init進程使用了第二種方式。
思考:非優雅的實現最簡單方式,只要在for循環中定時輪詢屬性,但是延時間隔多少合適?若是間隔太短,則將會相當浪費CPU,若是間隔太長,則會導致設置某個屬性到執行其對應Actions的時間過長。優雅的方式可以利用進程見通信機制避免CPU被浪費,並能保證響應的及時性。
(2) 借助進程間通信方式實現
首先細化init.c main函數中for循環代碼,大致如下:
int main(int argc, char* argv[])
{
....
for(;;) {
execute_one_command();
....
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {這部分內容請閱讀
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
...
}
}
}
}
當某個程序(進程)調用property_set(libcutils庫)來自設置屬性,就會給init進程發送一個消息(通過Unix domain socket),最終init接收到整個消息後,會調用
handle_property_set_fd() -> property_set(來自init/property_service.c)-> property_changed() -> queue_property_triggers()->action_add_queue_tail
在queue_property_triggers函數中從全局action_list中匹配 property:=類型的Actions,如果屬性數值滿足,則將該Actions加入到action-queue中,這樣該Actions中的command將會在之後的execute_one_command()被調用。
在本文中涉及很多Android屬性的操作,筆者將在文章《Android init源代碼分析(3)屬性系統》進一步分析Android的屬性系統。
此外還有些問題需要解決:
(1). service將如何執行,並且service存在多種屬性,當包含restart屬性時,init進程是如何重啟的?
(2). for循環中execute_one_command之後的大量代碼的含義?
第一個問題將在文章《Android init源代碼分析(4)service的處理》中具體分析。至於for循環中其他代碼,將分散在init源代碼分析(3)與(4)這兩篇文章中。
本文由prife原創,轉載請注明出處。博客地址:blog.csdn.net/prife ,豆瓣prife,weibo
prife