Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android init進程——解析配置文件

Android init進程——解析配置文件

編輯:關於Android編程

目錄

目錄 init解析配置文件 關鍵字定義 kw_is 解析 K_import K_on command執行 K_service service service結構體 parse_service parse_line_service init控制service


init解析配置文件

在解析service服務是如何啟動之前,讓我們先來學習一下init進程是如何解析init.rc等配置文件的。

init進程解析配置文件的代碼如下(/system/core/init/init.c&&/system/core/init/init_parser.c):

init_parse_config_file("/init.rc");

void *read_file(const char *fn, unsigned *_sz)
{
    char *data;
    int sz;
    int fd;
    struct stat sb;

    data = 0;
    fd = open(fn, O_RDONLY);
    if (fd < 0) return 0;

    // 獲取init.rc文件字符總數
    sz = lseek(fd, 0, SEEK_END);
    if (sz < 0) goto oops;

    if (lseek(fd, 0, SEEK_SET) != 0) goto oops;

    // 多余兩個字符存儲'\n'和'\0'
    data = (char*)malloc(sz + 2);
    if (data == 0) goto oops;

    // 讀取init.rc文件內容到data數組中
    if (read(fd, data, sz) != sz) goto oops;
    close(fd);
    data[sz] = '\n';
    data[sz + 1] = 0;
    if(_sz) *_sz = sz;
    return data;
oops:
    close(fd);
    if (data != 0) free(data);
    return 0;
}

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

讀完init.rc文件內容到data數組中後,將調用parse_config函數進行解析,具體函數源碼如下:

struct parse_state
{
    char *ptr;
    char *text;
    int line;
    int nexttoken;
    void *context;
    void (*parse_line)(struct parse_state *state, int nargs, char **args);
    const char *filename;
    void *priv;
};

#define T_EOF 0
#define T_TEXT 1
#define T_NEWLINE 2

int next_token(struct parse_state *state)
{
    char *x = state->ptr;
    char *s;

    if (state->nexttoken) {
        int t = state->nexttoken;
        state->nexttoken = 0;
        return t;
    }

    for (;;) {
        switch(*x) {
        case 0:
            state->ptr = x;
            return T_EOF;
        case '\n':
            x++;
            state->ptr = x;
            return T_NEWLINE;
        case ' ':
        case '\t':
        case '\r':
            x++;
            continue;
        case '#':
            while (*x && (*x != '\n')) x ++;
            if (*x == '\n') {
                state->ptr = x + 1;
                return T_NEWLINE;
            } else {
                state->ptr = x;
                return T_EOF;
            }
        default:
            goto text;
        }
    }

text:
    state->text = s = x;
}

static void parse_config(const char *fn, char *s)
{
    struct parse_state state;
    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];
    int nargs;

    nargs = 0;
    state.filename = fn;
    state.line = 0;
    state.ptr = s;
    state.nexttoken = 0;
    state.parse_line = parse_line_no_op;

    list_init(&import_list);
    state.priv = &import_list;

    for(;;) {
        switch(next_token(&state)) {
        case T_EOF:
            state.parse_line(&state, 0, 0);
            goto parser_done;
        case T_NEWLINE:
            state.line ++;
            if (nargs) {
                int kw = lookup_keyword(args[0]);
                if (ks_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;
            }
            break;
        case T_TEXT:
            if (nargs < INIT_PARSER_MAXARGS) {
                args[nargs ++] = state.text;
            }
            break;
        }
    }

parser_done:
    // 解析import關鍵字導入的rc文件
    list_for_each(node, &import_list) {
        struct import *import = node_to_item(node, struct import, list);
        int ret;

        INFO("importing '%s'", import->filename);
        ret = init_parse_config_file(import->filename);
        if (ret) {
            ERROR("could not import file '%s' from '%s'\n", import->filename, fn);
        }
    }
}

上面就是parse_config函數的實現。函數通過調用next_token函數來解析data數組內容,當有新的一行有效數據時,則把有效數據存入到args字符指針數組中,然後在T_NEWLINE分支中進行真正的解析,這也是解析過程中最復雜的地方。讓我們來深入的學習一下T_NEWLINE分支的解析過程。


關鍵字定義

我們先來看一下loopup_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;
    case 'e':
        if (!strcmp(s, "xec")) return K_exec;
        if (!strcmp(s, "xport")) return K_export;
        break;
    case 'g':
        if (!strcmp(s, "roup")) return K_group;
        break;
    case 'h':
        if (!strcmp(s, "ostname")) return K_hostname;
        break;
    case 'i':
        if (!strcmp(s, "oprio")) return K_ioprio;
        if (!strcmp(s, "fup")) return K_ifup;
        if (!strcmp(s, "nsmod")) return K_insmod;
        if (!strcmp(s, "mport")) return K_import;
        break;
    case 'k':
        if (!strcmp(s, "eycodes")) return K_keycodes;
        break;
    case 'l':
        if (!strcmp(s, "oglevel")) return K_loglevel;
        if (!strcmp(s, "oad_persist_props")) return K_load_persist_props;
        break;
    case 'm':
        if (!strcmp(s, "kdir")) return K_mkdir;
        if (!strcmp(s, "ount_all")) return K_mount_all;
        if (!strcmp(s, "ount")) return K_mount;
        break;
    case 'o':
        if (!strcmp(s, "n")) return K_on;
        if (!strcmp(s, "neshot")) return K_oneshot;
        if (!strcmp(s, "nrestart")) return K_onrestart;
        break;
    case 'p':
        if (!strcmp(s, "owerctl")) return K_powerctl;
        break;
    case 'r':
        if (!strcmp(s, "estart")) return K_restart;
        if (!strcmp(s, "estorecon")) return K_restorecon;
        if (!strcmp(s, "mdir")) return K_rmdir;
        if (!strcmp(s, "m")) return K_rm;
        break;
    case 's':
        if (!strcmp(s, "eclabel")) return K_seclabel;
        if (!strcmp(s, "ervice")) return K_service;
        if (!strcmp(s, "etcon")) return K_setcon;
        if (!strcmp(s, "etenforce")) return K_setenforce;
        if (!strcmp(s, "etenv")) return K_setenv;
        if (!strcmp(s, "etkey")) return K_setkey;
        if (!strcmp(s, "etprop")) return K_setprop;
        if (!strcmp(s, "etrlimit")) return K_setrlimit;
        if (!strcmp(s, "etsebool")) return K_setsebool;
        if (!strcmp(s, "ocket")) return K_socket;
        if (!strcmp(s, "tart")) return K_start;
        if (!strcmp(s, "top")) return K_stop;
        if (!strcmp(s, "wapon_all")) return K_swapon_all;
        if (!strcmp(s, "ymlink")) return K_symlink;
        if (!strcmp(s, "ysclktz")) return K_sysclktz;
        break;
    case 't':
        if (!strcmp(s, "rigger")) return K_trigger;
        break;
    case 'u':
        if (!strcmp(s, "ser")) return K_user;
        break;
    case 'w':
        if (!strcmp(s, "rite")) return K_write;
        if (!strcmp(s, "ait")) return K_wait;
        break;
    }

    return K_UNKNOWN;
}

lookup_keyword函數源碼是非常簡單的,羅列了一堆strcmp語句。而且源碼在匹配case ‘p’的時候有一個明顯的問題,大家感興趣的可以自行查看Android源碼。

接下來,判斷關鍵字類型是不是SECTION,我們繼續來看一下kw_is的源碼。


kw_is

kw_is函數的源碼實現如下:

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

從上述代碼中我們還不能搞清楚道理什麼類型算是SECTION類型,這裡直接告訴大家:只有當關鍵字為on、service和import的時候,關鍵字才被認為是SECTION


解析

雖然關鍵字的類型很多,但是大部分關鍵字對應的解析函數都是parse_line_no_op。而parse_line_no_op的實現卻是空的,源碼如下:

void parse_line_no_op(struct parse_state *state, int nargs, char **args)
{
}

為什麼會這樣呢?

是因為,仔細分析最初的init_parse_config_file函數要解析的init.rc文件,雖然init.rc的內容很多,但是其中只有三種有效模式:

第一種trigger觸發
on xxxx
    ......
第二種service
service servicename
    ......
第三種import
import *.rc

而這三種模式的關鍵字都是屬於SECTION類型的,所以我們需要看一下parse_new_section函數裡對這三種關鍵字的解析函數指針做了賦值。

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

果然,針對這三種關鍵字,其解析函數parse_line也被賦予了不同的值。我們挨個分析一下。


K_import

import模式主要是導入新的rc配置文件,所以我們看一下parse_import函數的具體實現:

struct import {
    struct listnode list;
    const char *filename;
};

void list_add_tail(struct listnode *head, struct listnode *item)
{
    item->next = head;
    item->prev = head->prev;
    head->prev->next = item;
    head->prev = item;
}

void parse_import(struct parse_state *state, int nargs, char **args)
{
    struct listnode *import_list = state->priv;
    struct import *import;
    char conf_file[PATH_MAX];
    int ret;

    if (nargs != 2) {
        ERROR("single argument needed for import\n");
        return;
    }

    ret = expand_props(conf_file, args[1], sizeof(conf_file));
    if (ret) {
        ERROR("error while handing import on line '%d' in '%s'\n", state->line, state->filename);
        return;
    }

    import = calloc(1, sizeof(struct import));
    import->filename = strdup(conf_file);
    list_add_tail(import_list, &import->list);
    INFO("found import '%s', adding to import list", import->filename);
}

代碼還是比較簡單的,一句話概括就是將import導入的rc文件鏈接到state->priv鏈表中。state->priv鏈表鏈接了所有的import文件,最後會在parse_config函數的parser_done標簽中進行處理。


K_on

K_on關鍵字代表了trigger模式,跟trigger模式相關的函數只要是parse_action和parse_line_action。

action結構體源碼如下(/system/core/init/init.h):

struct action {
    /* node in list of all actions */
    struct listnode alist;
    /* node in the queue of pending actions */
    struct listnode qlist;
    /* node in the list of actions for a trigger */
    struct listnode tlist;

    unsigned hash;
    const char *name;

    // command鏈表,以zygote為例,它有4個onrestart option,所以它對應會創建4個command結構體
    struct listnode commands;
    struct command *current;
};

struct command
{
    /* list of commands in an action */
    struct listnode clist;

    int (*func)(int nargs, char **args);
    int nargs;
    char *args[1];
};

接下來,看一下parse_action和parse_line_action函數的具體實現。

void list_init(struct listnode *node)
{
    node->next = node;
    node->prev = node;
}

void list_add_tail(struct listnode *head, struct listnode *item)
{
    item->next = head;
    item->prev = head->prev;
    head->prev->next = item;
    head->prev = item;
}

static void *parse_action(struct parse_state *state, int nargs, char **args)
{
    struct action *act;
    if (nargs < 2) {
        parse_error(state, "actions must have a trigger\n");
        return 0;
    }

    if (nargs > 2) {
        parse_error(state, "actions may not have extra parameters\n");
        return 0;
    }

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

static void parse_line_action(struct parse_state *state, int nargs, char **args)
{
    struct command *cmd;
    struct action *act = state->context;
    int (*func)(int nargs, char **args);
    int kw, n;

    if (nargs == 0) {
        return;
    }

    kw = loopup_keyword(args[0]);
    if (!kw_is(kw, COMMAND)) {
        parse_error(state, "invalid command '%s'\n", args[0]);
        return;
    }

    n = kw_nargs(kw);
    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);
}

在parse_line_action構造command結構體的時候,有一個關鍵函數kw_func,它返回函數指針決定了command的具體實現。我們來看一下kw_func的函數源碼:

#define kw_nargs(kw) (keyword_info[kw].nargs)
#define kw_func(kw) (keyword_info[kw].func)
struct {
    const char *name;
    int (*func)(int nargs, char **args);
    unsigned char nargs;
    unsigned char flags;
} keyword_info[KEYWORK_COUNT] = {
    [K_UNKNOWN] = {"unknown", 0, 0, 0},
    #include "keywords.h"
};

這keyword_info結構體的初始化是很神奇的,希望大家也能感受到它的精妙之處。可以看到,代碼裡初始化了一個成員K_UNKNOWN之後,後面跟了一句#include “keywords.h”。這就是神奇的地方,因為代碼編譯過程中,include引用的內容會在當前位置進行展開,也就是說keyword_info其它成員的初始化是在”keywords.h”文件中完成的。我們來看一下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);
int do_class_stop(int nargs, char **args);
int do_class_reset(int nargs, char **args);
int do_domainname(int nargs, char **args);
int do_exec(int nargs, char **args);
int do_export(int nargs, char **args);
int do_hostname(int nargs, char **args);
int do_ifup(int nargs, char **args);
int do_insmod(int nargs, char **args);
int do_mkdir(int nargs, char **args);
int do_mount_all(int nargs, char **args);
int do_mount(int nargs, char **args);
int do_powerctl(int nargs, char **args);
int do_restart(int nargs, char **args);
int do_restorecon(int nargs, char **args);
int do_rm(int nargs, char **args);
int do_rmdir(int nargs, char **args);
int do_setcon(int nargs, char **args);
int do_setenforce(int nargs, char **args);
int do_setkey(int nargs, char **args);
int do_setprop(int nargs, char **args);
int do_setrlimit(int nargs, char **args);
int do_setsebool(int nargs, char **args);
int do_start(int nargs, char **args);
int do_stop(int nargs, char **args);
int do_swapon_all(int nargs, char **args);
int do_trigger(int nargs, char **args);
int do_symlink(int nargs, char **args);
int do_sysclktz(int nargs, char **args);
int do_write(int nargs, char **args);
int do_copy(int nargs, char **args);
int do_chown(int nargs, char **args);
int do_chmod(int nargs, char **args);
int do_loglevel(int nargs, char **args);
int do_load_persist_props(int nargs, char **args);
int do_wait(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)
    KEYWORD(class_start, COMMAND, 1, do_class_start)
    KEYWORD(class_stop,  COMMAND, 1, do_class_stop)
    KEYWORD(class_reset, COMMAND, 1, do_class_reset)
    KEYWORD(console,     OPTION,  0, 0)
    KEYWORD(critical,    OPTION,  0, 0)
    KEYWORD(disabled,    OPTION,  0, 0)
    KEYWORD(domainname,  COMMAND, 1, do_domainname)
    KEYWORD(exec,        COMMAND, 1, do_exec)
    KEYWORD(export,      COMMAND, 2, do_export)
    KEYWORD(group,       OPTION,  0, 0)
    KEYWORD(hostname,    COMMAND, 1, do_hostname)
    KEYWORD(ifup,        COMMAND, 1, do_ifup)
    KEYWORD(insmod,      COMMAND, 1, do_insmod)
    KEYWORD(import,      SECTION, 1, 0)
    KEYWORD(keycodes,    OPTION,  0, 0)
    KEYWORD(mkdir,       COMMAND, 1, do_mkdir)
    KEYWORD(mount_all,   COMMAND, 1, do_mount_all)
    KEYWORD(mount,       COMMAND, 3, do_mount)
    KEYWORD(on,          SECTION, 0, 0)
    KEYWORD(oneshot,     OPTION,  0, 0)
    KEYWORD(onrestart,   OPTION,  0, 0)
    KEYWORD(powerctl,    COMMAND, 1, do_powerctl)
    KEYWORD(restart,     COMMAND, 1, do_restart)
    KEYWORD(restorecon,  COMMAND, 1, do_restorecon)
    KEYWORD(rm,          COMMAND, 1, do_rm)
    KEYWORD(rmdir,       COMMAND, 1, do_rmdir)
    KEYWORD(seclabel,    OPTION,  0, 0)
    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)
    KEYWORD(loglevel,    COMMAND, 1, do_loglevel)
    KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)
    KEYWORD(ioprio,      OPTION,  0, 0)
#ifdef __MAKE_KEYWORD_ENUM__
    KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD
#endif

因為KEYWORD已經在init_parser.c文件中定義了,所以從#ifndef KEYWORD到#endif之間的代碼是不需要care的。KEYWORD的定義如下(/system/core/init/init_parser.c):

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

這裡結合KEYWORD(class_start, COMMAND, 1, do_class_start)來講解一下這個宏的作用。

預處理運算符: #(單井號)——字符串化運算符。##(雙井號)——連接運算符。 K_##symbol == K_class_start。#symbol == class_start KEYWORD(class_start, COMMAND, 1, do_class_start) == [K_class_start] = {class_start, do_class_start, 2, COMMAND}

經過上述分析,我們經已經清楚了keyword_info數組的初始化過程。同時,也知道了相應的cmd的func函數指針指向的具體函數。func函數的具體實現在/system/core/init/builtins.c文件裡,感興趣的同學可以自行閱讀源碼。

command執行

那最終這些command是如何執行的呢?

因為on ×××是基於某個事件觸發,init.rc中列舉的事件包括:

on early-init on init on fs on post-fs on post-fs-data on boot on nonencrypted on charger on property:key=value

那init.rc這些trigger是在什麼時間點觸發呢?其實這些trigger的觸發順序是由init.rc裡面添加它們到action queue的順序決定的。相關代碼如下:

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_add_queue_tail
void action_add_queue_tail(struct action *act)
{
    if (list_empty(&act->qlist)) {
        list_add_tail(&action_queue, &act->qlist);
    }
}

action_for_each_trigger("early-init", action_add_queue_tail);
action_for_each_trigger("init", action_add_queue_tail);
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);
}
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);
}

在init.c中以合適的順序添加到action queue裡面之後,在init.c main函數的最後會依次從action queue中取出這些action,順序執行。

for(;;) {
    int nr, i, timeout = -1;

    execute_one_command();
}

K_service

下文會重點介紹service的解析,這裡就略過了。


service

上述重點在於SECTION類型中的K_on和K_import關鍵字解析。接下來,我們要看一下K_service關鍵字的解析實現了,以zygote service為例。

zygote對應的service section內容是(/system/core/rootdir/init.rc):

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

解析service時,用到了parse_service和parse_line_service這兩個函數,在分別介紹它們之前,我們先來學習一下service的數據結構。


service結構體

init進程中使用了一個叫做service的結構體來保存與service section相關的信息,結構體的定義如下(system/core/init/init.h):

struct service {
    /* list of all service */
    struct listnode slist;

    const char *name;   /* service的名字,比如"zygote" */
    const char *classname;  /* service所屬的class的名字,默認是"default" */

    unsigned flags; /* service的屬性 */
    pid_t pid;  /* 進程號 */
    time_t time_started; /* time of last start */
    time_t time_crashed; /* first crash within inspection window */
    int nr_crashed; /* number of times crashed within window */

    uid_t uid;
    gid_t gid;
    gid_t supp_gids[NR_SVC_SUPP_GIDS];
    size_t nr_supp_gids;

    char *seclabel;

    struct socketinfo *sockets;
    // service一般運行在一個獨立的進程中,envvars用來描述創建這個進程時所需的環境變量信息
    struct svcenvinfo *envvars;

    struct action onrestart;

    int *keycodes;
    int nkeycodes;
    int keychord_id;

    int ioprio_class;
    int ioprio_pri;

    int nargs;  // 參數個數
    char *args[1];  // 用於存儲參數
}

service一共有5個屬性,分別為:

SVC_DISABLED:不隨class自動啟動。 SVC_ONESHOT:退出後不需要重啟,也就是說這個service只啟動一次就可以了。 SVC_RUNNING:正在運行,這是service的狀態。 SVC_CONSOLE:該service需要使用控制台。 SVC_CRITICAL:如果在規定的時間內該service不斷重啟,則系統會重啟並進入恢復模式。

service的結構體雖然成員很多,但是比較好理解。但是,例如像zygote這種service,其中的onrestart該怎麼表示呢?其實是用了action這個結構體。
了解了這些基本的結構體之後,我們接下來看一下parse_service和parse_line_service的具體實現。


parse_service

parse_service的源碼位置是:/system/core/init/init_parser.c,源碼如下:

static int valid_name(const char *name)
{
    if (strlen(name) > 16) {
        return 0;
    }

    while (*name) {
        if (!isalnum(*name) && (*name != '_') && (*name != '-')) {
            return 0;
        }
        name++;
    }

    return 1;
}

#define list_for_each(node, list) \
    for(node = (list)->next; node != (list); node = node->next)

struct service *service_find_by_name(const char *name)
{
    struct listnode *node;
    struct service *svc;

    list_for_each(node, &service_list) {
        svc = node_to_item(node, struct service, slist);
        if (!strcmp(svc->name, name)) {
            return svc;
        }
    }
    return 0;
}

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

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

parse_service函數只是搭建了一個service的架子,具體的內容尚需由後面的解析函數來填充。接下來看一下另外一個解析函數parse_line_service。

parse_line_service

parse_line_service的源碼位置:/system/core/init/init_parser.c,源碼內容如下:

static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
    struct service *svc = state->context;
    struct command *cmd;
    int i, kw, kw_nargs;

    if (nargs == 0) {
        return;
    }

    svc->ioprio_class = IoSchedClass_NONE;

    kw = loopup_keyword(args[0]);
    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:
        svc->flags |= SVC_DISABLED;
        svc->flags |= SVC_RC_DISABLED;
        break;
    case K_ioprio:
        if (nargs != 3) {
            parse_error(state, "opprio optin usage: ioprio  \n");
        } else {
            svc->ioprio_pri = strtoul(args[2], 0, 8);

            if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {
                parse_error(state, "priority value must be range 0 - 7\n");
                break;
            }

            if (!strcmp(args[1], "rt")) {
                svc->ioprio_class = IoSchedClass_RT; 
            } else if (!strcmp(args[1], "be")) {
                svc->ioprio_class = IoSchedClass_BE;
            } else if (!strcmp(args[1], "idle")) {
                svc->ioprio_class = IoSchedClass_IDLE;
            } else {
                parse_error(state, "ioprio option usage: ioprio  <0-7>\n");
            }
        }
        break;
    case K_group:
        if (nargs < 2) {
            parse_error(state, "group option requires a group id\n");
        } else if (nargs > NR_SVC_SUPP_GIDS + 2) {
            parse_error(state, "group option accepts at most %d supp. groups\n", NR_SVC_SUPP_GIDS);
        } else {
            int n;
            svc->gid = decode_uid(args[1]);
            for (n = 2; n < nargs; n ++) {
                svc->supp_gids[n-2] = decode_uid(args[n]);
            }
            svc->nr_supp_gids = n - 2;
        }
        break;
    case K_keycodes:
        if (nargs < 2) {
            parse_error(state, "keycodes option requires atleast one keycode\n");
        } else {
            svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));
            if (!svc->keycodes) {
                parse_error(state, "could not allocate keycodes\n");
            } else {
                svc->nkeycodes = nargs - 1;
                for (i = 1; i < nargs; i ++) {
                    svc->keycodes[i - 1] = atoi(args[i]);
                }
            }
        }
        break;
    case K_oneshot:
        svc->flags |= SVC_ONESHOT;
        break;
    case K_onrestart:
        nagrs --;
        args ++;
        kw = loopup_keyword(args[0]);
        if (!kw_is(kw, COMMAND)) {
            parse_error(state, "invalid command '%s' \n", args[0]);
            break;
        }
        kw_nargs = kw_nargs(kw);
        if (nargs < kw_nargs) {
            parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1, kw_nargs > 2 ? "arguments" : "argument");
            break;
        }

        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(&svc->onrestart.commands, &cmd->clist);
        break;
    case K_critical:
        svc->flags != SVC_CRITICAL;
        break;
    case K_setenv:
        struct svcenvinfo *ei;
        if (nargs < 2) {
            parse_error(state, "setenv option requires name and value arguments\n");
            break;
        }
        ei = calloc(1, sizeof(*ei));
        if (! ei) {
            parse_error(state, "out of memory\n");
            break;
        }
        ei->name = args[1];
        ei->value = args[2];
        ei->next = svc->envvars;
        svc->envvars = ei;
        break;
    case K_socket:
        struct socketinfo *si;
        if (nargs < 4) {
            parse_error(state, "socket option requires name, type, perm arguments\n");
        }
        if (strcmp(args[2], "dgram") && strcmp(args[2], "stream") && strcmp(args[2], "seqpacket")) {
            parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n");
            break;
        }
        si = calloc(1, sizeof(*si));
        if (!si) {
            parse_error(state, "out of memory\n");
            break;
        }
        si->name = args[1];
        si->type = args[2];
        si->perm = strtoul(args[3], 0, 8);
        if (nargs > 4) {
            si->uid = decode_uid(args[4]);
        }
        if (nargs > 5) {
            si->gid = decode_uid(args[5]);
        }
        si->next = svc->sockets;
        svc->sockets = si;
        break;
    case K_user:
        if (nargs != 2) {
            parse_error(state, "user option requires a user id\n");
        } else {
            svc->uid = decode_uid(args[1]);
        }
        break;
    case K_seckable:
        if (nargs != 2) {
            parse_error(state, "seclable option requires a label string\n");
        } else {
            svc->seclable = args[1];
        }
        break;
    default:
        parse_error(state, "invalid option '%s'\n", args[0]);
    }

}

parse_line_service將根據配置文件的內容填充service結構體,那麼,zygote解析完後的結果為:

TODO:需要畫圖


init控制service

了解了service的數據結構和組裝,接下來,我們需要了解init是如何控制service的。

init.rc中有這樣一條命令:

class_start default
class_start main
class_start core

class_start標識一個COMMAND,對應的處理函數為do_class_start,它位於on boot的范圍內。當init進程執行到下面幾句話時,do_class_start就會被執行了。

action_for_each_trigger("boot", action_add_queue_tail);

下面我們來看一下do_class_start函數,函數位置:/system/core/init/builtins.c:

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

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

zygote這個service的classname值是main,flags標志位不是disable,所以會調用service_start來啟動zygote。
service_start的函數實現如下(/system/core/init/init.c):

static const char *ENV[32];
int add_environment(const char *key, const char *val)
{
    int n;

    for (n = 0; n < 31; n ++) {
        if (!ENV[n]) {
            size_t len = strlen(key) + strlen(val) + 2;
            char *entry = malloc(len);
            snprintf(entry, len, "%s=%s", key, val);
            ENV[n] = entry;
            return 0;
        }
    }
}

#define ANDROID_SOCKET_ENV_PREFIX "ANDROID_SOCKET_"
#define ANDROID_SOCKET_DIR "/dev/socket"

static void publish_socket(const char *name, int fd)
{
    char key[64] = ANDROID_SOCKET_ENV_PREFIX;
    char val[64];

    strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1, name, sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX));
    snprintf(val, sizeof(val), "%d", fd);
    add_environment(key, val);

    /* make sure we don't close-on-exec */
    fcntl(fd, F_SETFD, 0);
}

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

void service_start(struct service *svc, const char *dynamic_args)
{
    struct stat s;
    pid_t pid;
    int needs_console;
    int n;
    char *scon = NULL;
    int rc;

    svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART));
    svc->time_started = 0;

    if (svc->flags & SVC_RUNNING) {
        return;
    }

    needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0;
    if (needs_console && (!have_console)) {
        ERROR("service '%s' requires console\n", svc->name);
        svc->flags |= SVC_DISABLED;
        return;
    }

    /**
     * service一般運行於另外一個進程中,這個進程也是init的子進程
     * 所以,啟動service前需要判斷對應的可執行文件是否存在
     */
    if (stat(svc->args[0], &s) != 0) {
        ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
        svc->flags |= SVC_DISABLED;
        return;
    }

    NOTICE("starting '%s'\n", svc->name);

    pid = fork();

    if (pid == 0) {
        // pid為0,表示運行在子線程中
        struct socketinfo *si;
        struct svcenvinfo *ei;
        char tmp[32];
        int fd, sz;

        umask(077);
        if (properties_inited()) {
            // 得到屬性存儲空間的信息並加到環境變量中
            get_property_workspace(&fd, &sz);
            sprintf(tmp, "%d,%d", dup(fd), sz);
            add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
        }

        // 添加環境變量信息
        for (ei = svc->envvars; ei; ei = ei->next)
            add_environment(ei->name, ei->value);

        // 根據socketinfo創建socket
        for (si = svc->sockets; si; si = si->next) {
            int socket_type = (
                    !strcmp(si->type, "stream") ? SOCK_STREAM :
                        (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET);
            int s = create_socket(si->name, socket_type, si->perm, si->uid, si->gid);
            if (s >= 0) {
                publish_socket(si->name, s);
            }
        }
    }

    // ......省略設置uid,gid代碼
    if (!dynamic_args) {
        if (execve(svc->args[0], (char **) svc->args, (char **) ENV) < 0) {
            ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
        }
    } else {
        // ......省略動態參數的execve執行代碼
    }

    if (pid < 0) {
        ERROR("failed to start '%s'\n", svc->name);
        svc->pid = 0;
        return;
    }

    svc->time_started = gettime();
    svc->pid = pid;
    svc->flags != SVC_RUNNING;

    /**
     * 每一個service都有一個屬性
     * 例如:zygote的屬性為init.svc.zygote,設置當前屬性值為running
     * /
    if (properties_inited()) {
        notify_service_state(svc->name, "running");
    }
}

通過上述代碼,我們可以知道,原來service的啟動是通過fork和execve共同實現的。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved