Android4.4的init進程

Android4.4的init進程 java

侯 亮 node


1背景


前些日子須要在科室內作關於Android系統啓動流程的培訓。爲此,我在幾年前的技術手記的基礎上,從新改了一份培訓文檔。在從新整理文檔期間,我也重讀了一下Android 4.4的相關代碼,發現還有一些東西是我之前一直沒重視過的,因此打算寫下來總結一二。 linux

我之前之因此沒有把關於Android系統啓動方面的手記整理成博文,主要是由於網上已經有許多相似的文章了,再說一遍好像也沒什麼意思。但此次的培訓既然已迫使我重整了一份文檔,那麼倒也不妨貼出來供你們參考。文中的某些細節是我最近新補充的內容,這樣或許能和網上其餘文章有所區別吧。 android


2概述init進程

咱們先概述一下Android的init進程。init是Linux系統中,用戶空間的第一個進程。它負責建立系統中最關鍵的幾個子進程,尤爲是zygote。另外,init還提供了property service(屬性服務),相似於windows系統的註冊表服務。有關屬性服務的細節,你們可參考我寫的《Android Property機制》一文,本文就很少說了。 shell

在Android系統中,會有個init.rc腳本。Init進程一啓動就會讀取並解析這個腳本文件,把其中的元素整理成本身的數據結構(鏈表)。具體狀況可參考system\core\init\init.c文件,它的main()函數會先調用init_parse_config_file(「/init.rc」)來解析init.rc腳本,分析出應該執行的語義,而且把腳本中描述的action和service信息分別組織成雙向鏈表,而後執行之。示意圖以下: windows



3解析init.rc腳本


3.1介紹init.rc腳本


Init.rc腳本使用的是一種初始化語言,其中包含了4類聲明:
1)Action
2)Command
3)Service
4)Option
該語言規定,Action和Service是以一種「小節」(Section)的形式出現的,其中每一個Action小節能夠含有若干Command,而每一個Service小節能夠含有若干Option。小節只有起始標記,卻沒有明確的結束標記,也就是說,是用「後一個小節」的起始來結束「前一個小節」的。 數組

腳本中的Action大致上表示一個「行動」,它用一系列Command共同完成該「行動」。Action須要有一個觸發器(trigger)來觸發它,一旦知足了觸發條件,這個Action就會被加到執行隊列的末尾。Action的形式以下: 數據結構

on  <trigger>
 <command1>
 <command2>
    ......


Service表示一個服務程序,會在初始化時啓動。由於init.rc腳本中描述的服務每每都是核心服務,因此(基本上全部的)服務會在退出時自動重啓。Service的形式以下: app

service <name> <pathname> [<arguments>]*
 <option>
 <option>
  ......

Init.rc中的Service截選以下: dom

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

service vold /system/bin/vold
    class core
    socket vold stream 0660 root mount
    ioprio be 2

service netd /system/bin/netd
    class main
    socket netd stream 0660 root system
    socket dnsproxyd stream 0660 root inet
    socket mdns stream 0660 root system

請你們留心service裏的class選項,好比上面的class core和class main。它表示該service是屬於哪一種類型的服務。在後文的闡述boot子階段時,會用到這個概念。

其實,除了Action和Service,Init.rc中還有一種小節,就是Import小節。該小節表達的意思有點兒像java中的import,也就是說,Init.rc中還能夠導入其餘.rc腳本文件的內容。在早期的Android中,好像並不支持import語句,不過至少從Android4.0開始,添加了import語句。至於import最先出如今哪一個版本,我沒有考證過。import句子截選以下:

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.trace.rc

3.2解析

在init進程的main()函數裏,會調用init_parse_config_file("/init.rc")一句來解析init.rc腳本。init_parse_config_file()的代碼以下:
【system/core/init/Init_parser.c】

int init_parse_config_file(const char *fn)
{
    char *data;
    data = read_file(fn, 0);
    if (!data) return -1;

    parse_config(fn, data);
    DUMP();
    return 0;
}

先用read_file()把腳本內容讀入一塊內存,然後調用parse_config()解析這塊內存。

parse_config()的代碼截選以下:

static void parse_config(const char *fn, char *s)
{
    . . . . . .
    for (;;) {
        switch (next_token(&state)) {
        . . . . . .
        case T_NEWLINE:   // 遇到折行
            state.line++;
            if (nargs) {
                int kw = lookup_keyword(args[0]);
                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0);  // 不一樣section的parse_line也不一樣噢
                    parse_new_section(&state, kw, nargs, args);
                } else {
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        . . . . . .
    . . . . . .
}

它在逐行分析init.rc腳本,判斷每一行的第一個參數是什麼類型的,若是是action或service類型的,就表示要建立一個新的section節點了,此時它會設置一下解析後續行的解析函數,也就是給state->parse_line賦值啦。針對service類型,解析後續行的函數是parse_line_service(),而針對action類型,解析後續行的函數則是parse_line_action()。


這麼看來,parse_config()裏有3個地方值得咱們注意:

  • lookup_keyword()和kw_is()
  • parse_new_section()
  • state.parse_line()

3.2.1查詢腳本關鍵字

咱們先介紹關於關鍵字查找方面的知識,在這裏主要看lookup_keyword()和kw_is()。

lookup_keyword()的定義截選以下:
【system/core/init/Init_parser.c】

int lookup_keyword(const char *s)
{
    switch (*s++) {
    case 'c':
    if (!strcmp(s, "opy")) return K_copy;
        if (!strcmp(s, "apability")) return K_capability;
        if (!strcmp(s, "hdir")) return K_chdir;
        if (!strcmp(s, "hroot")) return K_chroot;
        if (!strcmp(s, "lass")) return K_class;
        if (!strcmp(s, "lass_start")) return K_class_start;
        if (!strcmp(s, "lass_stop")) return K_class_stop;
        if (!strcmp(s, "lass_reset")) return K_class_reset;
        if (!strcmp(s, "onsole")) return K_console;
        if (!strcmp(s, "hown")) return K_chown;
        if (!strcmp(s, "hmod")) return K_chmod;
        if (!strcmp(s, "ritical")) return K_critical;
        break;
    case 'd':
        if (!strcmp(s, "isabled")) return K_disabled;
        if (!strcmp(s, "omainname")) return K_domainname;
        break;
    . . . . . .
    . . . . . .

kw_is()宏的定義以下:

#define kw_is(kw, type) (keyword_info[kw].flags & (type))

基本上是查表的過程,而lookup_keyword()返回的那些K_copy、K_capability值,其實就是表項的索引號。這張關鍵字表的技術細節以下。


在init_parser.c文件中有下面這樣的代碼:

【system/core/init/Init_parser.c】

#include "keywords.h"

#define KEYWORD(symbol, flags, nargs, func) \
    [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },

struct {
    const char *name;
    int (*func)(int nargs, char **args);
    unsigned char nargs;
    unsigned char flags;
} keyword_info[KEYWORD_COUNT] = {
    [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
#include "keywords.h"
};
#undef KEYWORD

這裏用到了一點兒小技巧,兩次include了keywords.h頭文件,其實keywords.h中會先定義一次KEYWORD宏,其主要目的是爲了造成一個順序排列的enum,然後就#undef KEYWORD了。接着上面代碼中再次定義了KEYWORD宏,此次的主要目的是爲了造成一個struct數組,即keyword_info數組。


 keywords.h的部分截選以下:
【system/core/init/Keywords.h】

#ifndef KEYWORD
int do_chroot(int nargs, char **args);
int do_chdir(int nargs, char **args);
int do_class_start(int nargs, char **args);
. . . . . .
. . . . . .
#define __MAKE_KEYWORD_ENUM__
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum {
    K_UNKNOWN,
#endif
    KEYWORD(capability,  OPTION,  0, 0)
    KEYWORD(chdir,       COMMAND, 1, do_chdir)
    KEYWORD(chroot,      COMMAND, 1, do_chroot)
    KEYWORD(class,       OPTION,  0, 0)
    . . . . . .
    . . . . . .
#ifdef __MAKE_KEYWORD_ENUM__
    KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD
#endif

其中的#define KEYWORD是第一次定義KEYWORD,咱們比對一下這兩次定義:

// 第一次
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
// 第二次
#define KEYWORD(symbol, flags, nargs, func) \
    [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },

 總之,最後造成了以下數組:

表中只有3個表項的flag是SECTION,表示這是個小節,我用黃色框表示。


3.2.2解析section小節

一旦分析出某句腳本是以on或者service或者import開始,就說明一個新的小節要開始了。此時,會調用到parse_new_section(),該函數的代碼以下:


void parse_new_section(struct parse_state *state, int kw, int nargs, char **args)
{
    printf("[ %s %s ]\n", args[0],
           nargs > 1 ? args[1] : "");
    switch(kw) {
    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;
    case K_import:
        parse_import(state, nargs, args);
        break;
    }
    state->parse_line = parse_line_no_op;
}


很明顯,解析的小節就是那三類:action小節(以on開頭的),service小節和import小節。最核心的部分固然是service小節和action小節,具體解析的地方在上面代碼中的parse_service()和parse_action()函數裏。至於import小節,parse_import()函數只是把腳本中的全部import語句先彙總成一個鏈表,記入state結構中,待回到parse_config()後再作處理。


3.2.2.1解析service小節

parse_service()的代碼以下:
【system/core/init/Init_parser.c】
static void *parse_service(struct parse_state *state, int nargs, char **args)
{
    struct service *svc;
    . . . . . .
    svc = service_find_by_name(args[1]);
    if (svc) {
        parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
        return 0;
    }

    nargs -= 2;
    svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
    if (!svc) {
        parse_error(state, "out of memory\n");
        return 0;
    }
    svc->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);
    list_add_tail(&service_list, &svc->slist);
    return svc;
}

 解析service段時,會用calloc()申請一個service節點,填入service名等信息,並連入service_list總表中。注意,此時該service節點的onrestart.commands部分仍是個空鏈表,由於咱們尚未分析該service的後續腳本行呢。


parse_new_section()中爲service明確指定了解析後續行的函數parse_line_service()。該函數的代碼截選以下:
static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
    struct service *svc = state->context;
    struct command *cmd;
    . . . . . .
    kw = lookup_keyword(args[0]);   // 解析具體的service option也是要查關鍵字表的
    switch (kw) {
    case K_capability:
        break;
    case K_class:
        if (nargs != 2) {
            parse_error(state, "class option requires a classname\n");
        } else {
            svc->classname = args[1];
        }
        break;
    case K_console:
        svc->flags |= SVC_CONSOLE;
        break;
case K_disabled:
. . . . . .
. . . . . .

service的各個option會影響service節點的不一樣域,好比flags域、classname域、onrestart域等等。比較麻煩的是onrestart域,由於它自己又是個action節點,可攜帶若干個子command。


下面是service中常見的option:
1)K_capability
2)K_class
3)K_console
4)K_disabled
5)K_ioprio
6)K_group
7)K_user
8)K_keycodes
9)K_oneshot
10)K_onrestart
11)K_critical
12)K_setenv
13)K_socket
14)K_seclabel

在service小節解析完畢後,咱們應該能獲得相似下圖這樣的service節點:


3.2.2.2解析action小節

另外一方面,解析action小節時的動做也很簡單,會用calloc()申請一個action節點,填入action名等信息,而後連入action_list總表中。固然,此時action的commands部分也是空的。
static void *parse_action(struct parse_state *state, int nargs, char **args)
{
struct action *act;
    . . . . . .
    act = calloc(1, sizeof(*act));
    act->name = args[1];
    list_init(&act->commands);
    list_init(&act->qlist);
    list_add_tail(&action_list, &act->alist);
    return act;
}

對於action小節而言,咱們指定了不一樣的解析後續行的函數,也就是parse_line_action()。該函數的代碼截選以下:


static void parse_line_action(struct parse_state* state, int nargs, char **args)
{
    struct command *cmd;
    struct action *act = state->context;
    . . . . . .
    kw = lookup_keyword(args[0]);   // 解析具體的action command也是要查關鍵字表的
    if (!kw_is(kw, COMMAND)) {
        parse_error(state, "invalid command '%s'\n", args[0]);
        return;
    }

    n = kw_nargs(kw);
    if (nargs < n) {
        parse_error(state, "%s requires %d %s\n", args[0], n - 1,
            n > 2 ? "arguments" : "argument");
        return;
    }
    cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
    cmd->func = kw_func(kw);
    cmd->nargs = nargs;
    memcpy(cmd->args, args, sizeof(char*) * nargs);
    list_add_tail(&act->commands, &cmd->clist);
}

既然action的後續行能夠包含多條command,那麼parse_line_action()就必須先肯定出當前分析的是什麼command,這一點和parse_line_service()是一致的,都是經過調用lookup_keyword()來查詢關鍵字的。另外,command子行的全部參數其實已被記入傳進來的args參數,如今這些參數會記入command節點的args域中,並且這個command節點會鏈入action節點的commands鏈表尾部。


在action小節解析完畢後,咱們應該能獲得相似下圖這樣的action節點:

3.2.3主要造成兩個雙向鏈表

咱們畫了一張關於parse_config()的調用關係圖,以下:


init_parse_config_file()函數會將Init.rc腳本解析成兩個雙向鏈表,對應的表頭分別是service_list和action_list。雙向鏈表示意圖以下:

 


3.3具體執行那些action

通過解析一步,init.rc腳本中的actions被整理成雙向鏈表了,可是這些action並無被實際執行。如今咱們就來看下一步具體執行action的流程。
在init進程的main()函數中,咱們能夠看到以下句子:
int main(int argc, char **argv)
{
    . . . . . .
    . . . . . .
    init_parse_config_file("/init.rc");  // 內部將腳本內容轉換成action鏈表了

    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_linux_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);
    . . . . . .
    . . . . . .
}

首先,init_parse_config_file()已經把init.rc腳本里的內容轉換成action鏈表了,接着代碼運行到action_for_each_trigger(「early-init」...)一句,這一句會把action_list列表中匹配的action節點,連入action_queue隊列。


3.3.1整理action_queue隊列

init進程但願把系統初始化過程分割成若干「子階段」,action_for_each_trigger()的意思就是「觸發某個子階段裏的全部action」。在早期的Android中,大概就只有四、5個子階段,如今隨着Android的不斷升級,子階段也變得愈來愈多了。

action_for_each_trigger()的代碼以下:


void action_for_each_trigger(const char *trigger,
                             void (*func)(struct action *act))
{
    struct listnode *node;
    struct action *act;
    list_for_each(node, &action_list) {
        act = node_to_item(node, struct action, alist);
        if (!strcmp(act->name, trigger)) {
            func(act);  // 只要匹配,就回調func
        }
    }
}

能夠看到是在遍歷action_list鏈表,找尋全部「action名」和「參數trigger」匹配的節點,並回調「參數func所指的回調函數」。在前面的代碼中,回調函數就是action_add_queue_tail()。

void action_add_queue_tail(struct action *act)
{
    if (list_empty(&act->qlist)) {
        list_add_tail(&action_queue, &act->qlist);
    }
}

嗯,這裏又出現了個action_queue隊列!它和action_list列表有什麼關係?

其實很簡單,action_list能夠被理解成一個來自init.rc的「草稿列表」,列表中的節點順序基本上和init.rc腳本里編寫section時的順序一致,而這個順序不必定就是合適的「運行順序」,因此咱們須要另外一個按咱們的要求依次串接的隊列,那就是action_queue隊列。另外,有些新的action並無體如今init.rc腳本里,而是寫在具體代碼裏的,這些action能夠被稱爲「內建action」,咱們能夠經過調用queue_builtin_action()將「內建action」添加進action_list列表和action_queue隊列中。

queue_builtin_action()的代碼以下:


void queue_builtin_action(int (*func)(int nargs, char **args), char *name)
{
    struct action *act;
    struct command *cmd;

    act = calloc(1, sizeof(*act));
    act->name = name;
    list_init(&act->commands);
    list_init(&act->qlist);

    cmd = calloc(1, sizeof(*cmd));
    cmd->func = func;
    cmd->args[0] = name;
    list_add_tail(&act->commands, &cmd->clist);

    list_add_tail(&action_list, &act->alist);
    action_add_queue_tail(act);
}


init進程裏主要分割的「子階段」以下圖所示:

桔色方框表示的子階段,是比較重要的階段。

3.3.1.1early-init子階段

咱們先看early-init子階段,這部分在init.rc裏是這樣表達的:

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_adj -16

    # Set the security context for the init process.
    # This should occur before anything else (e.g. ueventd) is started.
    setcon u:r:init:s0
    start ueventd
    # create mountpoints
    mkdir /mnt 0775 root system

這個action包含4條command,分別是write、setcon、start和mkdir。不一樣command對應的func回調函數也是不一樣的,具體對應什麼,能夠查看Keywords.h。
【system/core/init/Keywords.h】


KEYWORD(service,  SECTION, 0, 0)
    KEYWORD(setcon,      COMMAND, 1, do_setcon)
    KEYWORD(setenforce,  COMMAND, 1, do_setenforce)
    KEYWORD(setenv,      OPTION,  2, 0)
    KEYWORD(setkey,      COMMAND, 0, do_setkey)
    KEYWORD(setprop,     COMMAND, 2, do_setprop)
    KEYWORD(setrlimit,   COMMAND, 3, do_setrlimit)
    KEYWORD(setsebool,   COMMAND, 2, do_setsebool)
    KEYWORD(socket,      OPTION,  0, 0)
    KEYWORD(start,       COMMAND, 1, do_start)
    KEYWORD(stop,        COMMAND, 1, do_stop)
    KEYWORD(swapon_all,  COMMAND, 1, do_swapon_all)
    KEYWORD(trigger,     COMMAND, 1, do_trigger)
    KEYWORD(symlink,     COMMAND, 1, do_symlink)
    KEYWORD(sysclktz,    COMMAND, 1, do_sysclktz)
    KEYWORD(user,        OPTION,  0, 0)
    KEYWORD(wait,        COMMAND, 1, do_wait)
    KEYWORD(write,       COMMAND, 2, do_write)
    KEYWORD(copy,        COMMAND, 2, do_copy)
    KEYWORD(chown,       COMMAND, 2, do_chown)
    KEYWORD(chmod,       COMMAND, 2, do_chmod)

好比說start命令對應的回調函數就是do_start():


int do_start(int nargs, char **args)
{
    struct service *svc;
    svc = service_find_by_name(args[1]);
    if (svc) {
        service_start(svc, NULL);
    }
    return 0;
}

啓動所指定的service。

 

3.3.1.2boot子階段

boot部分在init.rc裏是這樣表達的:

on boot
    ifup lo
    hostname localhost
    domainname localdomain
    setrlimit 13 40 40
    . . . . . .
    write /proc/sys/vm/overcommit_memory 1
    write /proc/sys/vm/min_free_order_shift 4
    chown root system /sys/module/lowmemorykiller/parameters/adj
    chmod 0664 /sys/module/lowmemorykiller/parameters/adj
    . . . . . .
    . . . . . .
    setprop net.tcp.default_init_rwnd 60
    class_start core
    class_start main

請注意最後的兩句,表示boot動做的最後,會自動先啓動全部類型爲「core」的服務,然後再啓動全部類型爲「main」的服務。咱們在前文闡述init.rc腳本中的service寫法時,特別讓你們留意service的class選項,好比class core和class main,如今要用到這個概念了。

class_start命令對應的回調函數是do_class_start(),該函數的代碼以下:
【system/core/init/Builtins.c】

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);    // 回調service_start_if_not_disabled()
        }
    }
}

其回調的func,就是service_start_if_not_disabled(),代碼以下:

static void service_start_if_not_disabled(struct service *svc)
{
    if (!(svc->flags & SVC_DISABLED)) {
        service_start(svc, NULL);
    }
}

代碼很簡單,service_for_each_class()會遍歷service_list鏈表,找到全部和classname匹配的service節點,若是這個節點沒有被disabled的話,那麼就啓動其對應的服務。
boot子階段先啓動的「core」類型的服務有:


core類型的服務 對應的可執行文件 說明
ueventd /sbin/ueventd  
healthd /sbin/healthd  
console /system/bin/sh  
adbd /sbin/adbd  
servicemanager /system/bin/servicemanager 大名鼎鼎的service manager service服務,Android的核心之一。
vold /system/bin/vold  

 

然後,boot子階段啓動的「main」類型的服務有:

main類型的服務 對應的可執行文件 說明
netd /system/bin/netd  
debuggerd /system/bin/debuggerd  
ril-daemon /system/bin/rild  
surfaceflinger /system/bin/surfaceflinger  
zygote /system/bin/app_process Android建立內部建立新進程的核心服務。
drm /system/bin/drmserver  
media /system/bin/mediaserver  
bootanim /system/bin/bootanimation  
installd /system/bin/installd  
flash_recovery /system/etc/install-recovery.sh  
racoon /system/bin/racoon  
mtpd /system/bin/mtpd  
keystore /system/bin/keystore  
dumpstate /system/bin/dumpstate  
sshd /system/bin/start-ssh  
mdnsd /system/bin/mdnsd  

 

3.3.2for循環中執行action_queue隊列

如今咱們繼續看,動做在編排進action_queue隊列以後,又是如何執行的呢?咱們知道,init進程最終會進入一個for(;;)循環,在這個循環中,每次都會嘗試執行一個command:

int main(int argc, char **argv)
{
    . . . . . .
. . . . . .
    // 這個for循環很是重要哦!
    for(;;) {
        int nr, i, timeout = -1;
        execute_one_command();
        restart_processes();
    . . . . . .
}

其中調用的execute_one_command()的代碼以下:

void execute_one_command(void)
{
    int ret;
    if (!cur_action || !cur_command || is_last_command(cur_action, cur_command))  
    {
        cur_action = action_remove_queue_head();
        cur_command = NULL;
        if (!cur_action)
            return;
        INFO("processing action %p (%s)\n", cur_action, cur_action->name);
        cur_command = get_first_command(cur_action);
    } else {
        cur_command = get_next_command(cur_action, cur_command);
    }

    if (!cur_command)
        return;

    ret = cur_command->func(cur_command->nargs, cur_command->args);
    INFO("command '%s' r=%d\n", cur_command->args[0], ret);
}

它的意思是說,執行「當前action」(cur_action)的「當前command」(cur_command)。若是執行時沒有「當前action」,就嘗試從action_queue隊列的頭部摘取一個節點。若是執行時沒有「當前command」,就從「當前action」中獲取下一個該執行的command。而一旦獲得了該執行的command,就回調其func函數指針。


在那幾個core類型的service中,有一個很是重要的service,叫作zygote,它是android內部建立新進程的核心服務,但本文就不對它細說了。

4補充說明幾個運做機理知識

下面咱們補充說明幾個init進程裏的運做機理。

4.1service是如何重啓的?

關於service的重啓方法,其實用到了linux的一點兒信號機制。在init進程的main()函數中,除了「early-init」、「init」等子階段外,還有個子階段叫做「signal_init」:

queue_builtin_action(signal_init_action, "signal_init");

當init進程執行到這個子階段時,會執行signal_init_action()回調函數:
【system/core/init/Init.c】

static int signal_init_action(int nargs, char **args)
{
    signal_init();
    return 0;
}

【system/core/init/Signal_handler.c】

void signal_init(void)
{
    int s[2];


    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = sigchld_handler;
    act.sa_flags = SA_NOCLDSTOP;
    sigaction(SIGCHLD, &act, 0);   // 向系統註冊一個系統回調

    /* create a signalling mechanism for the sigchld handler */
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
        signal_fd = s[0];  // 之後回調函數會向這個fd寫數據
        signal_recv_fd = s[1];
        fcntl(s[0], F_SETFD, FD_CLOEXEC);
        fcntl(s[0], F_SETFL, O_NONBLOCK);
        fcntl(s[1], F_SETFD, FD_CLOEXEC);
        fcntl(s[1], F_SETFL, O_NONBLOCK);
    }

    handle_signal();
}

請注意,signal_init()中調用了sigaction(SIGCHLD,...)一句。在linux系統中,當一個進程終止或者中止時,系統會向其父進程發送SIGCHLD信號。sigaction()動做能夠被理解爲向系統註冊一個系統回調函數。在本例中,每當有子進程終止時,系統就會回調sigchld_handler()回調函數,該函數的代碼以下:
【system/core/init/Signal_handler.c】

static void sigchld_handler(int s)
{
    write(signal_fd, &s, 1);
}

看到了嗎?無非是向signal_init()中建立的「socket對」裏的signal_fd寫數據,因而「socket對」的另外一個句柄signal_recv_fd就能夠獲得所寫的數據。


在init進程的main()函數中,最終進入那個無限for循環,監聽系統的風吹草動,其中就包括監聽這個signal_recv_fd:

int main(int argc, char **argv)
{
    . . . . . .
    . . . . . .
    for(;;) {
        . . . . . .
        if (!signal_fd_init && get_signal_fd() > 0) {
            ufds[fd_count].fd = get_signal_fd();  // 就是signal_recv_fd !
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            signal_fd_init = 1;
        }
        . . . . . .
        . . . . . .
        nr = poll(ufds, fd_count, timeout);
        . . . . . .
        for (i = 0; i < fd_count; i++) {
            if (ufds[i].revents == POLLIN) {
                if (ufds[i].fd == get_property_set_fd())
                    handle_property_set_fd();  // 處理設置屬性的命令
                else if (ufds[i].fd == get_keychord_fd())
                    handle_keychord();    // 處理相似混合按鍵的命令,相似同時按
                                             // 鋼琴上的若干鍵
                else if (ufds[i].fd == get_signal_fd())
                    handle_signal();    // 處理因子進程掛掉而發來的信號
            }
        }
    }
    . . . . . .
}

當監聽到signal_recv_fd有動靜時,會調用handle_signal()來處理:

void handle_signal(void)
{
    char tmp[32];

    /* we got a SIGCHLD - reap and restart as needed */
    read(signal_recv_fd, tmp, sizeof(tmp));
    while (!wait_for_one_process(0))
        ;
}


wait_for_one_process()的代碼截選以下:

static int wait_for_one_process(int block)
{
    . . . . . .
while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 
          && errno == EINTR );
    . . . . . .
    svc = service_find_by_pid(pid);   // 查詢出是哪一個service進程掛掉了
    . . . . . .

    svc->pid = 0;
    svc->flags &= (~SVC_RUNNING);

    if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
        svc->flags |= SVC_DISABLED;
    }
    if (svc->flags & (SVC_DISABLED | SVC_RESET) )  {
        notify_service_state(svc->name, "stopped");
        return 0;
    }
    . . . . . .

    svc->flags &= (~SVC_RESTART);
    svc->flags |= SVC_RESTARTING;

    /* Execute all onrestart commands for this service. */
    list_for_each(node, &svc->onrestart.commands) {
        cmd = node_to_item(node, struct command, clist);
        cmd->func(cmd->nargs, cmd->args);
    }
    notify_service_state(svc->name, "restarting");
    return 0;
}

該函數的代碼比較清晰,當init進程被通知某個子進程終止時,它會嘗試找到這個子進程對應的service節點,並展轉給該節點的flags域添加SVC_RESTARTING標記,而後又會立刻執行這個service節點中全部onrestart選項對應的動做。

代碼中處理SVC_ONESHOT的地方多判斷了SVC_RESTART標誌,這是爲何呢?我想理由是這樣的:SVC_ONESHOT表達的意思是「只打一槍」,也就是說以它裝飾的service進程,就算掛掉了,也不會從新啓動。然而必須兼顧到其餘進程restart的狀況。假若有另外一個進程會連鎖restart該service,此時就算該service有SVC_ONESHOT標誌,它仍是應該再次啓動的。

svc節點的onrestart域自己就是個action類型的域:

struct action onrestart;

如今開始遍歷onrestart域裏的commands列表:

list_for_each(node, &svc->onrestart.commands) {
    cmd = node_to_item(node, struct command, clist);
    cmd->func(cmd->nargs, cmd->args);
}

看來,service的那些onrestart子句是一次性完成的。咱們之前文說的zygote服務爲例,當它重啓時,會執行兩次do_write()以及兩次do_start(),分別啓動media服務和netd服務。

最後,wait_for_one_process()還會調用一下notify_service_state()。畢竟這是由於某個service掛掉了,纔會再走到這裏的,如今咱們立刻就要從新啓動那個剛死的service啦,因此最好仍是作一些必要的「通知動做」。請注意,這種關於重啓service的「通知」並非簡單發個事件什麼的,而是設置某個相應的系統屬性。具體的動做請看notify_service_state()的代碼:

void notify_service_state(const char *name, const char *state)
{
    char pname[PROP_NAME_MAX];
    int len = strlen(name);
    if ((len + 10) > PROP_NAME_MAX)
        return;
    snprintf(pname, sizeof(pname), "init.svc.%s", name);
    property_set(pname, state);
}


看到了嗎?會設置一個以「init.svc.」打頭的系統屬性。好比重啓zygote服務,此時就會把「init.svc.zygote」屬性值設爲「SVC_RESTARTING」。

你們有沒有注意到,wait_for_one_process()里根本沒有fork動做。這也就是說,wait_for_one_process()中並不會當即重啓新的service進程。你們都知道如今咱們正處於init進程的無限for循環中,因此程序從wait_for_one_process()返回後,總會再次走到for循環中的restart_processes():

int main(int argc, char **argv)
{
    . . . . .
    for(;;) {
        int nr, i, timeout = -1;
        execute_one_command();
        restart_processes(); 

此時纔會重啓新的進程:

static void restart_processes()
{
    process_needs_restart = 0;
    service_for_each_flags(SVC_RESTARTING, restart_service_if_needed);
}

遍歷service_list列表,找出那些flags中攜帶有SVC_RESTARTING標誌的service節點,並執行restart_service_if_needed()。

static void restart_service_if_needed(struct service *svc)
{
    time_t next_start_time = svc->time_started + 5;

    if (next_start_time <= gettime()) {
        svc->flags &= (~SVC_RESTARTING);
        service_start(svc, NULL);
        return;
    }

    if ((next_start_time < process_needs_restart) ||
        (process_needs_restart == 0)) {
        process_needs_restart = next_start_time;
    }
}

注意,爲了防止出現service連續緊密重啓的狀況,next_start_time會賦值爲svc->time_started + 5,也就是說,至少得喘息個5毫秒,而後才能進行下一次重啓。這就是Android中重啓service的具體流程。 

 

4.2混合按鍵是如何啓動service的?

如今咱們順便說一下用混合按鍵重啓service的技術,這部份內容如今已經不多用到了。至少在咱們常見的項目的init.rc腳本里是搜不到「keycodes」關鍵字的。這個關鍵字是個option,若是某個service裏含有keycodes選項的話,就說明設計者但願在用戶按下某種組合鍵時,init進程能重啓這個service。

這種能點擊出的組合鍵,很像同時按下幾個鋼琴鍵而發出和旋,所以被稱爲keychord。在init進程的啓動子過程當中,「keychord(初始化)子階段」甚至還要早於「init子階段」呢。

queue_builtin_action(keychord_init_action, "keychord_init");

其中keychord_init_action()的代碼以下:

【system/core/init/Init.c】

static int keychord_init_action(int nargs, char **args)
{
    keychord_init();
    return 0;
}

【system/core/init/Keychords.c】

void keychord_init()
{
    int fd, ret;
    service_for_each(add_service_keycodes);
    if (!keychords)
        return;
    fd = open("/dev/keychord", O_RDWR);
    if (fd < 0) {
        ERROR("could not open /dev/keychord\n");
        return;
    }
    fcntl(fd, F_SETFD, FD_CLOEXEC);


    ret = write(fd, keychords, keychords_length);
    if (ret != keychords_length) {
        ERROR("could not configure /dev/keychord %d (%d)\n", ret, errno);
        close(fd);
        fd = -1;
    }
    free(keychords);
    keychords = 0;
    keychord_fd = fd;
}

初始化時,利用service_for_each(),遍歷service_list列表,對每一個列表節點調用add_service_keycodes(),該函數代碼以下:
【system/core/init/Keychords.c】

void add_service_keycodes(struct service *svc)
{
    struct input_keychord *keychord;
    int i, size;


    if (svc->keycodes) {
        /* add a new keychord to the list */
        size = sizeof(*keychord) + 
                svc->nkeycodes * sizeof(keychord->keycodes[0]);
        keychords = realloc(keychords, keychords_length + size);
        if (!keychords) {
            ERROR("could not allocate keychords\n");
            keychords_length = 0;
            keychords_count = 0;
            return;
        }

        keychord = (struct input_keychord *)
                                       ((char *)keychords + keychords_length);
        keychord->version = KEYCHORD_VERSION;
        keychord->id = keychords_count + 1;
        keychord->count = svc->nkeycodes;
        svc->keychord_id = keychord->id;

        for (i = 0; i < svc->nkeycodes; i++) {
            keychord->keycodes[i] = svc->keycodes[i];
        }
        keychords_count++;
        keychords_length += size;
    }
}
其中用到的keychords是個靜態變量:
static struct input_keychord *keychords = 0;

它實質上指向了一塊buffer,該buffer最終會存下全部keychord信息。當咱們遍歷service_list列表時,一旦發現某個service節點攜帶有keycodes,就會從這個buffer中劃分出一塊,並在其中寫入從service節點讀取到的keycodes信息。由於不一樣service攜帶的keycode部分可能不同,因此每次分出的那塊內存的大小也不太同樣。不過大致上每一小塊記錄的都是input_keychord結構,該結構的定義以下:

【kernel/include/linux/Keychord.h】

struct input_keychord {
 __u16 version;
 __u16 id;
 __u16 count;
 __u16 keycodes[];
};

另外,請注意上面代碼中的這幾句:

keychord->id = keychords_count + 1;
keychord->count = svc->nkeycodes;
svc->keychord_id = keychord->id;

keychord信息裏有個惟一的id號,並且這個id號還會回寫到service節點的keychord_id域。 通過此次遍歷,咱們大致上能夠畫出下面這樣的示意圖:


在整理好keychords這塊buffer後,keychord_init()會把它寫入「/dev/keychord」設備文件。

fd = open("/dev/keychord", O_RDWR);
. . . . . . 
ret = write(fd, keychords, keychords_length);


這應該是向驅動層通知重要信息了。並且請注意,這個fd文件描述符會被記錄下來:

keychord_fd = fd;

記錄下fd有什麼用呢?很簡單,init進程在最後那個for循環裏,會監聽這個fd,從而感知到從驅動層發來的混合按鍵,代碼以下:

if (!keychord_fd_init && get_keychord_fd() > 0) {
    ufds[fd_count].fd = get_keychord_fd(); // 獲得的就是那個keychord文件描述符
    ufds[fd_count].events = POLLIN;
    ufds[fd_count].revents = 0;
    fd_count++;
    keychord_fd_init = 1;
}
一旦監聽到有混合按鍵發生了,就會走到下面的handle_keychord():
for (i = 0; i < fd_count; i++) {
            if (ufds[i].revents == POLLIN) {
                if (ufds[i].fd == get_property_set_fd())
                    handle_property_set_fd();
                else if (ufds[i].fd == get_keychord_fd())
                    handle_keychord();   // 處理混合按鍵
                else if (ufds[i].fd == get_signal_fd())
                    handle_signal();
            }
        }

【system/core/init/Keychords.c】

void handle_keychord()
{
    struct service *svc;
    char adb_enabled[PROP_VALUE_MAX];
    int ret;
    __u16 id;


    // Only handle keychords if adb is enabled.
    property_get("init.svc.adbd", adb_enabled);
    ret = read(keychord_fd, &id, sizeof(id));
    if (ret != sizeof(id)) {
        ERROR("could not read keychord id\n");
        return;
    }

    if (!strcmp(adb_enabled, "running")) {
        svc = service_find_by_keychord(id);
        if (svc) {
            INFO("starting service %s from keychord\n", svc->name);
            service_start(svc, NULL);
        } else {
            ERROR("service for keychord %d not found\n", id);
        }
    }
}

此時會從/dev/keychord設備文件裏讀取一個id號,還記得前文說到的「id號會回寫到service節點的keychord_id域」嗎,如今會再次遍歷service_list列表,找到那個keychord_id和讀到的id匹配的service節點,而後調用service_start(svc, NULL)啓動這個service。

5小結

關於init進程,咱們就先說這麼多吧。限於篇幅,咱們不得不把不少不那麼重要的細節省去,有興趣的同窗能夠自行深刻研究。

相關文章
相關標籤/搜索