天天看點

android啟動之init啟動main函數.rc腳本語言淺析.rc檔案的解析

整個Android系統的啟動分為Linux kernel的啟動和Android系統的啟動。Linux kernel啟動起來後,然後就運作第一個使用者程式,在Android中,就是init程式,上一博文 android啟動之linux核心啟動已經介紹。

Init程序始終是第一個程序。Init程序的對應的代碼的main函數在目錄system/core/init/init.c,先來總體看一下這個main函數。

main函數

int main(int argc, char **argv)
{
    //首先聲明一些局部變量
    int fd_count = 0;
    struct pollfd ufds[4];
    char *tmpdev;
    char* debuggable;
    char tmp[32];
    int property_set_fd_init = 0;
    int signal_fd_init = 0;
    int keychord_fd_init = 0;
    bool is_charger = false;

    //對傳入的argv[0]進行判斷,決定程式的執行分支
    if (!strcmp(basename(argv[0]), "ueventd"))
        return ueventd_main(argc, argv);

    if (!strcmp(basename(argv[0]), "watchdogd"))
        return watchdogd_main(argc, argv);

    /* clear the umask */
    umask(0);

        /* Get the basic filesystem setup we need put
         * together in the initramdisk on / and then we'll
         * let the rc file figure out the rest.
         */
    //建立各種使用者空間的目錄,挂載與核心空間互動的檔案
    mkdir("/dev", 0755);
    mkdir("/proc", 0755);
    mkdir("/sys", 0755);

    mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
    mkdir("/dev/pts", 0755);
    mkdir("/dev/socket", 0755);
    mount("devpts", "/dev/pts", "devpts", 0, NULL);
    mount("proc", "/proc", "proc", 0, NULL);
    mount("sysfs", "/sys", "sysfs", 0, NULL);

    /* indicate that booting is in progress to background fw loaders, etc */
    //建立打開/dev/.booting檔案,然後關閉
    close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));

        /* We must have some place other than / to create the
         * device nodes for kmsg and null, otherwise we won't
         * be able to remount / read-only later on.
         * Now that tmpfs is mounted on /dev, we can actually
         * talk to the outside world.  
         */
    //system/core/init/util.c。建立裝置節點/dev/__null__,利用dup2函數把标準輸入、标準輸出、标準錯誤輸出重定向到這個裝置檔案中(0标準輸入、1标準輸出、2标準錯誤輸出)。重定向操作完成後,就關閉掉fd。
    open_devnull_stdio();
    //system/core/libcutils/klog.c。初始化log系統/dev/__kmsg__
    klog_init();
    //system/core/init/property_service.c。初始化資源/dev/__properties__,主要是映射一些記憶體空間
    property_init();
    //取得硬體名,hardware通過核心指令行提供或/proc/cpuinfo檔案中提供。import /init.${ro.hardware}.rc
    get_hardware_name(hardware, &revision);
    //使用import_kernel_cmdline函數導入核心變量,調用export_kernel_boot_props函數通過屬性設定核心變量。就是來回設定一些屬性值,包括hardware變量,在此處又通過ro.boot.hardware屬性設定了一次值。
    process_kernel_cmdline();

#ifdef HAVE_SELINUX
    //SELinux(Security-Enhanced Linux) 是美國國家安全局(NSA)對于強制通路控制的實作,它是個經過安全強化的Linux作業系統,很多時候是被關閉的。
    union selinux_callback cb;
    cb.func_log = klog_write;
    selinux_set_callback(SELINUX_CB_LOG, cb);

    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    INFO("loading selinux policy\n");
    if (selinux_enabled) {
        if (selinux_android_load_policy() < 0) {
            selinux_enabled = 0;
            INFO("SELinux: Disabled due to failed policy load\n");
        } else {
            selinux_init_all_handles();
        }
    } else {
        INFO("SELinux:  Disabled by command line option\n");
    }
    /* These directories were necessarily created before initial policy load
     * and therefore need their security context restored to the proper value.
     * This must happen before /dev is populated by ueventd.
     */
    restorecon("/dev");
    restorecon("/dev/socket");
#endif

    is_charger = !strcmp(bootmode, "charger");
    INFO("property init\n");
    if (!is_charger)
        property_load_boot_defaults();

    INFO("reading config file\n");
    //system/core/init/init_parser.c。讀取并且解析init.rc檔案
    init_parse_config_file("/init.rc");

    //system/core/init/init_parser.c。觸發在init腳本檔案中名字為early-init的action,并且執行其commands,其實是.rc檔案中的: on early-init
    action_for_each_trigger("early-init", action_add_queue_tail);

    //system/core/init/init_parser.c。内建action并添加到action_queue中,到execute_one_command()中檢測執行。
    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    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);

    /* skip mounting filesystems in charger mode */
    if (!is_charger) {
        action_for_each_trigger("early-fs", action_add_queue_tail);
        action_for_each_trigger("fs", action_add_queue_tail);
        action_for_each_trigger("post-fs", action_add_queue_tail);
        action_for_each_trigger("post-fs-data", action_add_queue_tail);
    }

    queue_builtin_action(property_service_init_action, "property_service_init");
    queue_builtin_action(signal_init_action, "signal_init");
    queue_builtin_action(check_startup_action, "check_startup");

    if (is_charger) {
        action_for_each_trigger("charger", action_add_queue_tail);
    } else {
        action_for_each_trigger("early-boot", action_add_queue_tail);
        action_for_each_trigger("boot", action_add_queue_tail);
    }

     /* run all property triggers based on current state of the properties */
    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

#if BOOTCHART
    queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif

//進入一個無限循環 to wait for device/property set/child process exit events.例如, 如果SD卡被插入,init會收到一個裝置插入事件,它會為這個裝置建立節點。系統中比較重要的程序都是由init來fork的,是以如果他們誰崩潰 了,那麼init 将會收到一個 SIGCHLD 信号,把這個信号轉化為子程序退出事件, 是以在loop中,init 會操作程序退出事件并且執行 *.rc 檔案中定義的指令。
    for(;;) {
        int nr, i, timeout = -1;
        //執行action_queue中的action,并将此action移除
        execute_one_command();
        //檢查service_list是否有程序需要重新開機
        restart_processes();

        if (!property_set_fd_init && get_property_set_fd() > 0) {
            ufds[fd_count].fd = get_property_set_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            property_set_fd_init = 1;
        }
        if (!signal_fd_init && get_signal_fd() > 0) {
            ufds[fd_count].fd = get_signal_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            signal_fd_init = 1;
        }
        if (!keychord_fd_init && get_keychord_fd() > 0) {
            ufds[fd_count].fd = get_keychord_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            keychord_fd_init = 1;
        }
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }
        if (!action_queue_empty() || cur_action)
            timeout = 0;

//  bootchart是一個性能統計工具,用于搜集硬體和系統的資訊,并将其寫入磁盤,以便其他程式使用
#if BOOTCHART
        if (bootchart_count > 0) {
            if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
                timeout = BOOTCHART_POLLING_MS;
            if (bootchart_step() < 0 || --bootchart_count == 0) {
                bootchart_finish();
                bootchart_count = 0;
            }
        }
#endif
       //  等待下一個指令的送出
        nr = poll(ufds , fd_count, timeout);
        if (nr <= 0)
            continue;

        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();
            }
        }
    }
           

.rc腳本語言淺析

init.rc檔案在core/rootdir/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
......
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
......
           

init腳本語言的規範:

A、Android init.rc檔案由系統第一個啟動的init程式解析,此檔案由語句組成;

B、在init.rc檔案中一條語句通常是占據一行;

C、單詞之間是通過空格符來相隔的,如果需要在單詞内使用空格,那麼得使用轉義字元"\";

D、如果在一行的末尾有一個反斜杠,那麼是換行折疊符号,應該和下一行合并成一起來處理,這樣做主要是為了避免一行的字元太長,與C語言中的含義是一緻的;

E、注釋是以#号開頭(允許以空格開頭)。

首先我們先了解一個概念:Section(語句塊),相當于C語言中大括号内的一個塊。

一個Section語句塊以Service或On開頭。而以On開頭的叫做動作(Action),以Service開頭的Section叫做服務。Actions和Services有唯一的名字。如果有重名的情況,第二個申明的将會被作為錯誤忽略。上面列出的正好一個是action, 一個是service。

另外還有兩個概念:Commands(指令)和Options(選項)。

Action和services顯式聲明了一個語句塊,而commands和options屬于最近聲明的語句塊。在第一個語句塊之前的commands和options會被忽略。

Actions(動作)

Actions(動作)形式如下: 

        on <trigger>

           <command1>

           <command2>

           <command3>

Actions其實就是用on關鍵字開頭的一序列的Commands(指令)。on後面有一個trigger(觸發器),它被用于決定action的執行時間。當一個符合action觸發條件的事件發生時,action會被加入到執行隊列的末尾,除非它已經在隊列裡 了。隊列中的每一個action都被依次提取出,而這個action中的每個command(指令)都将被依次執行,直到下一個Action或下一個Service。

Triggers(觸發器)是一個用于比對特定事件類型的字元串,用于使Actions發生,有如下形式:

   簡單的字元串如early-init:

        這是init執行後的第一個被觸發的Triggers(觸發器)。(在 /init.conf (啟動配置檔案)被裝載之後)

   <name>=<value>:

        這種形式的Triggers(觸發器)會在屬性<name>被設定為指定的<value>時被觸發。

   device-added-<path>:

   device-removed-<path>:

        這種形式的Triggers(觸發器)會在一個裝置節點檔案被增删時觸發。

   service-exited-<name>:

        這種形式的Triggers(觸發器)會在一個特定的服務退出時觸發。

Services(服務)

Services(服務)是一個程式,它在初始化時啟動,并在退出時可選擇讓其重新開機。

Services(服務)的形式如下: 

        service <name> <pathname> [ <argument> ]*

           <option>

           <option>

           ...

name:服務名

pathname:目前服務對應的程式位置

option:目前服務設定的選項

Options(選項)

    Options(選項)是一個Services(服務)的修正者。他們影響Services(服務)在何時,并以何種方式運作。

     critical:說明這是一個對于裝置關鍵的服務。如果他四分鐘内退出大于四次,系統将會重新開機并進入recovery(恢複)模式。

     disabled:說明這個服務不會同與他同trigger(觸發器)下的服務自動啟動。他必須被明确的按名啟動。

     setenv <name> <value> (設定環境變量)

   在程序啟動時将環境變量<name>設定為<value>。

     socket <name> <type> <perm> [ <user> [ <group> ] ]

            建立一個Uinx域的名為/dev/socket/<name> 的套接字,并傳遞它的檔案描述符給已啟動的程序。<type> 必須是 "dgram"或"stream"。User 和 group預設為0。

     user <username>

            在啟動這個服務前改變該服務的使用者名。此時預設為root。(???有可能的話應該預設為nobody)。目前,如果你的程序要求Linux capabilities(能力),你無法使用這個指令。即使你是root,你也必須在程式中請求

capabilities(能力)。然後降到你想要的 uid。

     group <groupname> [ <groupname> ]*

            在啟動這個服務前改變該服務的組名。除了(必需的)第一個組名,附加的組名通常被用于設定程序的補充組(通過setgroups())。此時預設為root。(???有可能的話應該預設為nobody)。

     oneshot

            服務退出時不重新開機。

     class <name>

            指定一個服務類。所有同一類的服務可以同時啟動和停止。如果不通過class選項指定一個類,則預設為"default"類服務。

     onrestart

            當服務重新開機,執行一個指令(下詳)。

Commands(指令)

    exec <path> [ <argument> ]*

         建立和執行一個程式(<path>)。在程式完全執行前,init将會阻塞。由于它不是内置指令,應盡量避免使用exec,它可能會引起init卡死。(??? 是否需要一個逾時設定?)

    export <name> <value>

        在全局環境變量中設在環境變量 <name>為<value>。(這将會被所有在這指令之後運作的程序所繼承)

    ifup <interface>

        啟動網絡接口<interface>

    import <filename>

           解析一個init配置檔案,擴充目前配置。

    hostname <name>

           設定主機名。

    chmod <octal-mode> <path>

           更改檔案通路權限。

    chown <owner> <group> <path>

           更改檔案的所有者群組。

    class_start <serviceclass>

           啟動所有指定服務類下的未運作服務。

    class_stop <serviceclass>

        停止指定服務類下的所有已運作的服務。

    domainname <name>

           設定域名。

    insmod <path>

           加載<path>中的子產品。

    mkdir <path> [mode] [owner] [group]

           建立一個目錄<path>,可以選擇性地指定mode、owner以及group。如果沒有指定,預設的權限為755,并屬于root使用者和root組。

    mount <type> <device> <dir> [ <mountoption> ]*

        試圖在目錄<dir>挂載指定的裝置。<device> 可以是以 [email protected] 的形式指定一個mtd塊裝置。<mountoption>包括 "ro"、"rw"、"remount"、"noatime"、 ...

    setprop <name> <value>

           設定系統屬性 <name> 為 <value>值。 

    setrlimit <resource> <cur> <max>

        設定<resource>的rlimit(資源限制)。

    start <service>

        啟動指定服務(如果此服務還未運作)。

    stop <service>

        停止指定服務(如果此服務在運作中)。

    symlink <target> <path>

        建立一個指向<path>的軟連接配接<target>。

    sysclktz <mins_west_of_gmt>

        設定系統時鐘基準(0代表時鐘滴答以格林威治平均時(GMT)為準)

    trigger <event>

           觸發一個事件。用于将一個action與另一個 action排列。

    write <path> <string> [ <string> ]*

           打開路徑為<path>的一個檔案,并寫入一個或多個字元串。

Properties(屬性)

    Init更新一些系統屬性以提供對正在發生的事件的監控能力: 

        init.action

               此屬性值為正在被執行的action的名字,如果沒有則為""。

        init.command

               此屬性值為正在被執行的command的名字,如果沒有則為""。

        init.svc.<name>

               名為<name>的service的狀态("stopped"(停止), "running"(運作), "restarting"(重新開機))

.rc檔案的解析

重要的資料結構有兩個清單,一個隊列。 

static list_declare(service_list); 

static list_declare(action_list); 

static list_declare(action_queue); 

*.rc 腳本中所有 service關鍵字定義的服務将會添加到 service_list 清單中。 

*.rc 腳本中所有 on     關鍵開頭的動作将會被會添加到 action_list 清單中。 

main函數中解析init.rc檔案的代碼是:

init_parse_config_file("/init.rc");

該函數在system/core/init/init_parser.c中,如下:

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; 
} 
           

其中的核心方法是parse_config(fn, data);,如下:

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 ,就是上一子產品中說的Section(語句塊),那麼将會執行 parse_new_section() 。

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 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