init 分析

分析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系統

2.解析/init.rc和/init.%hardware%.rc文件  

3. 執行 early-init action in the two files parsed in step 2.  

4. 設備初始化,例如:在 /dev 下面創建所有設備節點,下載 firmwares.  

5.初始化屬性服務器,Actually 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. 開啓 屬性服務。

8. 執行 early-boot and boot actions in the two files parsed in step 2.  

9. 執行 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 666
    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 = 1;   
    …
    for(;;) {
        int nr, timeout = -1;
    …
        drain_action_queue();
        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_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 666
    onrestart write /sys/android_power/request_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 *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[1], 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 1或者0
那麼將會開啓或者關閉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();
/* run all property triggers based on current state of the properties */
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)
{
        /* Starting a class does not start services
         * which are explicitly disabled.  They must
         * be started individually.
         */
    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 中的所有服務將會被執行。
參考文檔:
http://blog.chinaunix.net/u1/38994/showart_1775465.html
http://blog.chinaunix.net/u1/38994/showart_1168440.html
淺析kernel啓動的第1個用戶進程init如何解讀init.rc腳本
http://blog.chinaunix.net/u1/38994/showart_1168440.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章