天天看點

Android系統init.rc分析define SECTION 0×01define COMMAND 0×02define OPTION 0×04

在 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檔案

  1. 執行 early-init action in the two files parsed in step 2.
  2. 裝置初始化,例如:在 /dev 下面建立所有裝置節點,下載下傳 firmwares.

5.初始化屬性伺服器,Actually the property system is working as a share memory.Logically it looks like a registry under Windows system.

  1. 執行 init action in the two files parsed in step 2.
  2. 開啟 屬性服務。
  3. 執行 early-boot and boot actions in the two files parsed in step 2.
  4. 執行 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 中的所有服務将會被執行。

繼續閱讀