在 Android中使用啟動腳本init.rc,可以在系統的初始化過程中進行一些簡單的初始化操作。這個腳本被直接安裝到目标系統的根檔案系統中,被 init可執行程式解析。 init.rc是在init啟動後被執行的啟動腳本.
(1)android啟動檔案系統後調用的第一個應用程式是/init,此檔案的很重要的内容是解析了init.rc和init.xxx.rc
兩個配置檔案,然後執行解析出來的任務。相關代碼在android源代碼/system/core/init/init.c檔案中,如下:
parse_config_file("/init.rc");
/* pull the kernel commandline and ramdisk properties file in */
qemu_init();
import_kernel_cmdline();
get_hardware_name();
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
parse_config_file(tmp);
(2)從上面代碼可以看到,第一個配置檔案名稱固定為init.rc,而第二個配置檔案格式為init.xxx.rc,其中xxx部分的内容
是從核心讀取的,具體是讀取檔案/proc/cpuinfo中的Hardware部分,然後截取其部分内容。
(3)從上面看init.xxx.rc中的xxx内容是取決平台的定義,例如:
parse_config_file(“init.qcom.rc”);
(4)配置檔案的文法如下:
(a)配置檔案的内容包含有4種:
動作(Action)
指令(Commands)
服務(Services)
選項(Options)
(b)動作和指令一起使用,形式如下:
on
其中trigger是觸發條件,也就是說在滿足觸發條件的情況下執行1個或多個相應的指令,舉例如下:
on property:persist.service.adb.enable=1
start adbd
(c)服務和選項一起使用,形式如下:
service [ ]*
…
上面内容解釋為:
service 服務名稱 服務對應的指令的路徑 指令的參數
選項
選項
…
舉例如下:
service vold /system/bin/vold
socket vold stream root mount
service bootsound /system/bin/playmp3
user media
group audio
oneshot
vold和bootsound分别是兩個服務的名稱,/system/bin/vold和/system /bin/playmp3分别是他們所對應的可執行程式。
socket、user、group、oneshot就是配合服務使用的選項。其中oneshot選項表示該服務隻啟動一次,而如果沒有oneshot選項,
這個可執行程式會一直存在–如果可執行程式被殺死,則會重新啟動。
(d)選項是影響服務啟動和運作的參數,主要的選項如下:
disabled 禁用服務,此服務開機時不會自動啟動,但是可以在應用程式中手動啟動它。
socket [ [ ] ]
套接字 類型 名稱 權限 使用者 組
建立一個名為/dev/socket/,然後把它的fd傳給啟動程式
類型type的值為dgram或者stream
perm表示該套接字的通路權限,user和group表示改套接字所屬的使用者群組,這兩個參數預設都是0,是以可以不設定。
user
執行服務前切換到使用者,此選項預設是root,是以可以不設定。
group [ ]*
執行服務前切換到組,此選項預設是root,是以可以不設定
capability [ ]+
執行服務前設定linux capability,沒什麼用。
oneshot
服務隻啟動一次,一旦關閉就不能再啟動。
class
為服務指定一個類别,預設為”default”,同一類别的服務必須一起啟動和停止
(e)動作觸發條件
boot 首個觸發條件,初始化開始(載入配置檔案)的時候觸發
=
當名為的屬性(property)的值為的時候觸發
device-added-
路徑為的設定添加的時候觸發
device-removed-
路徑為的設定移除的時候觸發
service-exited-
名為的服務關閉的時候觸發
(f)指令(Command)的形式
exec [ ]*
複制(fork)和執行路徑為的應用程式,為該應用程式的參數,在該應用程式執行完前,此指令會屏蔽,
export
聲明名為的環境變量的值為,聲明的環境變量是系統環境變量,啟動後一直有效。
ifup
啟動名為的網絡接口
import
加入新的位置檔案,擴充目前的配置。
hostname
設定主機名
sysclktz
設定系統時區(GMT為0)
class_start
啟動指定類别的所有服務
class_stop
停止指定類别的所有服務
domainname
設定域名
insmod
加載路徑為的核心子產品
mkdir
建立路徑為目錄
mount
[ ]*
挂載類型為的裝置到目錄 ,為挂載參數,距離如下:
mount ubifs ubi1_0 /data nosuid nodev
setkey
暫時未定義
setprop
設定名為的系統屬性的值為
setrlimit
設定資源限制,
start
啟動服務(如果服務未運作)
stop
停止服務(如果服務正在運作)
symlink
建立一個從指向的符号連結,舉例:
symlink /system/etc /etc
write [ ]*
打開路徑為的檔案并将一個多這多個字元串寫入到該檔案中。
(g)系統屬性(Property)
android初始化過程中會修改一些屬性,通過getprop指令我們可以看到屬性值,這些屬性訓示了某些動作或者服務的狀态,主要如下:
init.action 如果目前某個動作正在執行則init.action屬性的值等于該動作的名稱,否則為””
init.command 如果目前某個指令正在執行則init.command屬性的值等于該指令的名稱,否則為””
init.svc. 此屬性訓示個名為的服務的狀态(“stopped”, “running”, 或者 “restarting”).
init的源代碼在檔案:./system/core/init/init.c 中,init會一步步完成下面的任務:
1.初始化log系統
2.解析/init.rc和/init.%hardware%.rc檔案
- 執行 early-init action in the two files parsed in step 2.
- 裝置初始化,例如:在 /dev 下面建立所有裝置節點,下載下傳 firmwares.
5.初始化屬性伺服器,Actually the property system is working as a share memory.Logically it looks like a registry under Windows system.
- 執行 init action in the two files parsed in step 2.
- 開啟 屬性服務。
- 執行 early-boot and boot actions in the two files parsed in step 2.
- 執行 Execute property action in the two files parsed in step 2.
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
onrestart write /sys/android_power/request_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_queue_tail);
drain_action_queue();
…
/* 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_queue_tail);
drain_action_queue();
…
/* 執行 early-boot and boot actions in the two files parsed in step 2 */
action_for_each_trigger(”early-boot”, action_add_queue_tail);
action_for_each_trigger(”boot”, action_add_queue_tail);
drain_action_queue();
/* run all property triggers based on current state of the properties */
queue_all_property_triggers();
drain_action_queue();
/* enable property triggers */
property_triggers_enabled = ;
…
for(;;) {
int nr, timeout = -;
…
drain_action_queue();
restart_processes();
if (process_needs_restart) {
timeout = (process_needs_restart – gettime()) * ;
if (timeout
重要的資料結構兩個清單,一個隊列。
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);
*.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(struct 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(struct parse_state *state, int nargs, char **args)
{
struct 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
onrestart write /sys/android_power/request_state wake
服務名稱為: zygote
啟動該服務執行的指令: /system/bin/app_process
指令的參數: -Xzygote /system/bin –zygote –start-system-server
socket zygote stream : 建立一個名為:/dev/socket/zygote 的 socket ,類型為:stream
當*.rc 檔案解析完成以後:
action_list 清單項目如下:
on init
on boot
on property:ro.kernel.qemu=
on property:persist.service.adb.enable=
on property:persist.service.adb.enable=
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 *value, const char *default_value)
狀态設定函數:
Property_service.c (system/core/init)
int property_set(const char *name, const char *value)
Properties.c (system/core/libcutils)
int property_set(const char *key, const char *value)
在終端模式下我們可以通過執行指令 setprop
setprop 工具源代碼所在檔案: Setprop.c (system/core/toolbox)
Getprop.c (system/core/toolbox): property_get(argv[], value, default_value);
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, value) //Init.c (system/core/init)
queue_property_triggers(name, value)
drain_action_queue()
隻要屬性一改變就會被觸發,然後執行相應的指令:
例如:
在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 或者
那麼将會開啟或者關閉adbd 程式。
執行action_list 中的指令:
從action_list 中取出 act->name 為 early-init 的清單項,再調用 action_add_queue_tail(act)将其插入到 隊列 action_queue 尾部。drain_action_queue() 從action_list隊列中取出隊列項 ,然後執行act->commands
清單中的所有指令。
是以從 ./system/core/init/init.c mian()函數的程式片段:
action_for_each_trigger(”early-init”, action_add_queue_tail);
drain_action_queue();
action_for_each_trigger(”init”, action_add_queue_tail);
drain_action_queue();
action_for_each_trigger(”early-boot”, action_add_queue_tail);
action_for_each_trigger(”boot”, action_add_queue_tail);
drain_action_queue();
queue_all_property_triggers();
drain_action_queue();
可以看出,在解析完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)
{
service_for_each_class(args[1], service_start_if_not_disabled);
return 0;
}
void service_for_each_class(const char *classname,
void (*func)(struct service *svc))
{
struct listnode *node;
struct service *svc;
list_for_each(node, &service_list) {
svc = node_to_item(node, struct service, slist);
if (!strcmp(svc->classname, classname)) {
func(svc);
}
}
}
“`
因為在調用 parse_service() 添加服務清單的時候,所有服務 svc->classname 預設取值:”default”,
是以 service_list 中的所有服務将會被執行。