Android init 啓動過程分析1

分析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 < 0)
                timeout = 0;
        }
     ...
        nr = poll(ufds, 3, timeout);
        if (nr <= 0)
            continue;

        if (ufds[2].revents == POLLIN) {
            /* we got a SIGCHLD - reap and restart as needed */
            read(signal_recv_fd, tmp, sizeof(tmp));
            while (!wait_for_one_process(0))
                ;
            continue;
        }

        if (ufds[0].revents == POLLIN)
            handle_device_fd(device_fd);

        if (ufds[1].revents == POLLIN)
    {
            handle_property_set_fd(property_set_fd);
    }
    }

    return 0;
}


2.解析init.rc腳本
init.rc 腳本的具體語法可以參考下面文檔
http://www.kandroid.org/android_pdk/bring_up.html
名詞解釋:
Android初始化語言由四大類聲明組成:行爲類(Actions),命令類(Commands),服務類(Services),選項類(Options).
初始化語言以行爲單位,由以空格間隔的語言符號組成。C風格的反斜槓轉義符可以用來插入空白到語言符號。雙引號也可以用來防止文本被空格分成多個語言符號。當反斜槓在行末時,作爲換行符。
* 以#開始(前面允許空格)的行爲註釋。
* Actions和Services隱含聲明一個新的段落。所有該段落下Commands或Options的聲明屬於該段落。第一段落前的Commands或Options被忽略。
* Actions和Services擁有唯一的命名。在他們之後聲明相同命名的類將被當作錯誤並忽略。
Actions是一系列命令的命名。Actions擁有一個觸發器(trigger)用來決定action何時執行。當一個action在符合觸發條件被執行時,如果它還沒被加入到待執行隊列中的話,則加入到隊列最後。
隊列中的action依次執行,action中的命令也依次執行。Init在執行命令的中間處理其他活動(設備創建/銷燬,property 設置,進程重啓)。

Actions的表現形式:
on <trigger>
  <command>
  <command>
  <command>
重要的數據結構
兩個列表,一個隊列。
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 0x01
#define COMMAND 0x02
#define OPTION  0x04
關鍵字        屬性      
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 < 3) {
        parse_error(state, "services must have a name and a program\n");
        return 0;
    }
    if (!valid_name(args[1])) {
        parse_error(state, "invalid service name '%s'\n", args[1]);
        return 0;
    }
    //如果服務已經存在service_list列表中將會被忽略
    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);
    //添加該服務到 service_list 列表
    list_add_tail(&service_list, &svc->slist);
    return svc;
}
服務的表現形式:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...

申請一個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 <key> <value>
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 程序。


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