分 析android的啓動過程,從內核之上,咱們首先應該從文件系統的init開始,由於 init 是內核進入文件系統後第一個運行的程序,一般咱們能夠在linux的命令行中指定內核第一個調用誰,若是沒指定那麼內核將會到/sbin/,/bin/ 等目錄下查找默認的init,若是沒有找到那麼就報告出錯。
下面是曾經用過的幾種開發板的命令行參數:
S3C2410 啓動參數:
noinitrd
root=/dev/nfs nfsroot=192.168.2.56:/nfsroot/rootfs
ip=192.168.2.188:192.168.2.56:192.168.2.56:255.255.255.0::eth0:on
console=ttySAC0
S3C2440 啓動參數:
setenv bootargs console=ttySAC0
root=/dev/nfs nfsroot=192.168.2.56:/nfsroot/rootfs
ip=192.168.2.175:192.168.2.56:192.168.2.201:255.255.255.0::eth0:on
mem=64M init=/init
marvell 310 啓動參數:
boot root=/dev/nfs
nfsroot=192.168.2.56:/nfsroot/rootfs,rsize=1024,wsize=1024
ip=192.168.2.176:192.168.2.201:192.168.2.201:255.255.255.0::eth0:-On
console=ttyS2,115200 mem=64M init=/init
init的源代碼在文件:./system/core/init/init.c 中,init會一步步完成下面的任務:
1.初始化log系統html
2.解析/init.rc和/init.%hardware%.rc文件 node
3. 執行 early-init action in the two files parsed in step 2. linux
4. 設備初始化,例如:在 /dev 下面建立全部設備節點,下載 firmwares. android
5.初始化屬性服務器,Act lly the property system is working as a share memory.Logically it looks like a registry under Windows system. 服務器
6. 執行 init action in the two files parsed in step 2. 數據結構
7. 開啓 屬性服務。app
8. 執行 early-boot and boot actions in the two files parsed in step 2. dom
9. 執行 Execute property action in the two files parsed in step 2. socket
10.進入一個無限循環 to wait for device/property set/child process exit events.例如,若是SD卡被插入,init會收到一個設備插入事件,它會爲這個設備建立節點。系統中比較重要的進程都是由init來fork的,所 以若是他們他誰崩潰了,那麼init 將會收到一個 SIGCHLD 信號,把這個信號轉化爲子進程退出事件, 因此在loop中,init 會操做進程退出事件而且執行*.rc 文件中定義的命令。
例如,在init.rc中,由於有:
service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server
socket zygote stream 666
onrestart write /sys/android_power/req st_state wake
onrestart write /sys/power/state on
因此,若是zygote由於啓動某些服務致使異常退出後,init將會從新去啓動它。
int main(int argc, char **argv)
{
…
//須要在後面的程序中看打印信息的話,須要屏蔽open_devnull_stdio()函數
open_devnull_stdio();
…
//初始化log系統
log_init();
//解析/init.rc和/init.%hardware%.rc文件
parse_config_file(」/init.rc」);
…
snprintf(tmp, sizeof(tmp), 「/init.%s.rc」, hardware);
parse_config_file(tmp);
…
//執行 early-init action in the two files parsed in step 2.
action_for_each_trigger(」early-init」, action_add_q _tail);
drain_action_q ();
…
/* execute all the boot actions to get us started */
/* 執行 init action in the two files parsed in step 2 */
action_for_each_trigger(」init」, action_add_q _tail);
drain_action_q ();
…
/* 執行 early-boot and boot actions in the two files parsed in step 2 */
action_for_each_trigger(」early-boot」, action_add_q _tail);
action_for_each_trigger(」boot」, action_add_q _tail);
drain_action_q ();
/* run all property triggers based on current state of the properties */
q _all_property_triggers();
drain_action_q ();
/* enable property triggers */
property_triggers_enabled = 1;
…
for(;;) {
int nr, timeout = -1;
…
drain_action_q ();
restart_processes();
if (process_needs_restart) {
timeout = (process_needs_restart – gettime()) * 1000;
if (timeout
重要的數據結構兩個列表,一個隊列。
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_q );
*.rc 腳本中全部 service關鍵字定義的服務將會添加到 service_list 列表中。
*.rc 腳本中全部 on 關鍵開頭的項將會被會添加到 action_list 列表中。
每一個action列表項都有一個列表,此列表用來保存該段落下的 Commands腳本解析過程:
parse_config_file(」/init.rc」)
int parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;
parse_config(fn, data);
DUMP();
return 0;
}
static void parse_config(const char *fn, char *s)
{
…
case T_NEWLINE:
if (nargs) {
int kw = lookup_keyword(args[0]);
if (kw_is(kw, SECTION)) {
state.parse_line(&state, 0, 0);
parse_new_section(&state, kw, nargs, args);
} else {
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
…
}
parse_config 會逐行對腳本進行解析,若是關鍵字類型爲 SECTION ,那麼將會執行 parse_new_section() 類型爲 SECTION 的關鍵字有: on 和 sevice 關鍵字類型定義在 Parser.c (system\core\init) 文件中
Parser.c (system\core\init)
#define SECTION 0×01
#define COMMAND 0×02
#define OPTION 0×04
關鍵字 屬性
capability, OPTION, 0, 0)
class, OPTION, 0, 0)
class_start, COMMAND, 1, do_class_start)
class_stop, COMMAND, 1, do_class_stop)
console, OPTION, 0, 0)
critical, OPTION, 0, 0)
disabled, OPTION, 0, 0)
domainname, COMMAND, 1, do_domainname)
exec, COMMAND, 1, do_exec)
export, COMMAND, 2, do_export)
group, OPTION, 0, 0)
hostname, COMMAND, 1, do_hostname)
ifup, COMMAND, 1, do_ifup)
insmod, COMMAND, 1, do_insmod)
import, COMMAND, 1, do_import)
keycodes, OPTION, 0, 0)
mkdir, COMMAND, 1, do_mkdir)
mount, COMMAND, 3, do_mount)
on, SECTION, 0, 0)
oneshot, OPTION, 0, 0)
onrestart, OPTION, 0, 0)
restart, COMMAND, 1, do_restart)
service, SECTION, 0, 0)
setenv, OPTION, 2, 0)
setkey, COMMAND, 0, do_setkey)
setprop, COMMAND, 2, do_setprop)
setrlimit, COMMAND, 3, do_setrlimit)
socket, OPTION, 0, 0)
start, COMMAND, 1, do_start)
stop, COMMAND, 1, do_stop)
trigger, COMMAND, 1, do_trigger)
symlink, COMMAND, 1, do_symlink)
sysclktz, COMMAND, 1, do_sysclktz)
user, OPTION, 0, 0)
write, COMMAND, 2, do_write)
chown, COMMAND, 2, do_chown)
chmod, COMMAND, 2, do_chmod)
loglevel, COMMAND, 1, do_loglevel)
device, COMMAND, 4, do_device)
parse_new_section()中再分別對 service 或者 on 關鍵字開頭的內容進行解析。
…
case K_service:
state->context = parse_service(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_service;
return;
}
break;
case K_on:
state->context = parse_action(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_action;
return;
}
break;
}
…
對 on 關鍵字開頭的內容進行解析
static void *parse_action(str t parse_state *state, int nargs, char **args)
{
…
act = calloc(1, sizeof(*act));
act->name = args[1];
list_init(&act->commands);
list_add_tail(&action_list, &act->alist);
…
}
對 service 關鍵字開頭的內容進行解析
static void *parse_service(str t parse_state *state, int nargs, char **args)
{
str t service *svc;
if (nargs name = args[1];
svc->classname = 「default」;
memcpy(svc->args, args + 2, sizeof(char*) * nargs);
svc->args[nargs] = 0;
svc->nargs = nargs;
svc->onrestart.name = 「onrestart」;
list_init(&svc->onrestart.commands);
//添加該服務到 service_list 列表
list_add_tail(&service_list, &svc->slist);
return svc;
}
服務的表現形式:
service [ ]*
…
申 請一個service結構體,而後掛接到service_list鏈表上,name 爲服務的名稱 pathname 爲執行的命令 argument 爲命令的參數。以後的 option 用來控制這個service結構體的屬性,parse_line_service 會對 service關鍵字後的 內容進行解析並填充到 service 結構中 ,當遇到下一個service或者on關鍵字的時候此service選項解析結束。
例如:
service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server
socket zygote stream 666
onrestart write /sys/android_power/req st_state wake
服務名稱爲: zygote
啓動該服務執行的命令: /system/bin/app_process
命令的參數: -Xzygote /system/bin –zygote –start-system-server
socket zygote stream 666: 建立一個名爲:/dev/socket/zygote 的 socket ,類型爲:stream
當*.rc 文件解析完成之後:
action_list 列表項目以下:
on init
on boot
on property:ro.kernel.qemu=1
on property:persist.service.adb.enable=1
on property:persist.service.adb.enable=0
init.marvell.rc 文件
on early-init
on init
on early-boot
on boot
service_list 列表中的項有:
service console
service adbd
service servicemanager
service mountd
service debuggerd
service ril-daemon
service zygote
service media
service bootsound
service dbus
service hcid
service hfag
service hsag
service installd
service flash_recovery
狀態服務器相關:
在init.c 的main函數中啓動狀態服務器。
property_set_fd = start_property_service();
狀態讀取函數:
Property_service.c (system\core\init)
const char* property_get(const char *name)
Properties.c (system\core\libcutils)
int property_get(const char *key, char *val , const char *default_val )
狀態設置函數:
Property_service.c (system\core\init)
int property_set(const char *name, const char *val )
Properties.c (system\core\libcutils)
int property_set(const char *key, const char *val )
在終端模式下咱們能夠經過執行命令 setprop
setprop 工具源代碼所在文件: Setprop.c (system\core\toolbox)
Getprop.c (system\core\toolbox): property_get(argv[1], val , default_val );
Property_service.c (system\core\init)
中定義的狀態讀取和設置函數僅供init進程調用,
handle_property_set_fd(property_set_fd);
property_set() //Property_service.c (system\core\init)
property_changed(name, val ) //Init.c (system\core\init)
q _property_triggers(name, val )
drain_action_q ()
只要屬性一改變就會被觸發,而後執行相應的命令:
例如:
在init.rc 文件中有
on property:persist.service.adb.enable=1
start adbd
on property:persist.service.adb.enable=0
stop adbd
因此若是在終端下輸入:
setprop property:persist.service.adb.enable 1或者0
那麼將會開啓或者關閉adbd 程序。
執行action_list 中的命令:
從action_list 中取出 act->name 爲 early-init 的列表項,再調用 action_add_q _tail(act)將其插入到 隊列 action_q 尾部。drain_action_q () 從action_list隊列中取出隊列項 ,而後執行act->commands
列表中的全部命令。
因此從 ./system/core/init/init.c mian()函數的程序片斷:
action_for_each_trigger(」early-init」, action_add_q _tail);
drain_action_q ();
action_for_each_trigger(」init」, action_add_q _tail);
drain_action_q ();
action_for_each_trigger(」early-boot」, action_add_q _tail);
action_for_each_trigger(」boot」, action_add_q _tail);
drain_action_q ();
/* run all property triggers based on current state of the properties */
q _all_property_triggers();
drain_action_q ();
能夠看出,在解析完init.rc init.marvell.rc 文件後,action 命令執行順序爲:
執行act->name 爲 early-init,act->commands列表中的全部命令
執行act->name 爲 init, act->commands列表中的全部命令
執行act->name 爲 early-boot,act->commands列表中的全部命令
執行act->name 爲 boot, act->commands列表中的全部命令
關鍵的幾個命令:
class_start default 啓動全部service 關鍵字定義的服務。
class_start 在act->name爲boot的 act->commands列表中,因此當 class_start 被觸發後,實際上調用的是函數 do_class_start()
int do_class_start(int nargs, char **args)
{
/* Starting a class does not start services
* which are explicitly disabled. They must
* be started individ lly.
*/
service_for_each_class(args[1], service_start_if_not_disabled);
return 0;
}
void service_for_each_class(const char *classname,
void (*func)(str t service *svc))
{
str t listnode *node;
str t service *svc;
list_for_each(node, &service_list) {
svc = node_to_item(node, str t service, slist);
if (!strcmp(svc->classname, classname)) {
func(svc);
}
}
}
由於在調用 parse_service() 添加服務列表的時候,全部服務 svc->classname 默認取值:」default」,
因此 service_list 中的全部服務將會被執行。
參考文檔:
http://blog.chinaunix.net/ /38994/showart_1775465.html
http://blog.chinaunix.net/ /38994/showart_1168440.html
淺析kernel啓動的第1個用戶進程init如何解讀init.rc腳本
http://blog.chinaunix.net/ /38994/showart_1168440.htmlide