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

目录

  • 目录
  • 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的内容很多,但是其中只有三种有效模式:

  1. 第一种trigger触发
on xxxx
    ......
  1. 第二种service
service servicename
    ......
  1. 第三种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)来讲解一下这个宏的作用。

  1. 预处理运算符: #(单井号)——字符串化运算符。##(双井号)——连接运算符。
  2. K_##symbol == K_class_start。#symbol == class_start
  3. 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个属性,分别为:

  1. SVC_DISABLED:不随class自动启动。
  2. SVC_ONESHOT:退出后不需要重启,也就是说这个service只启动一次就可以了。
  3. SVC_RUNNING:正在运行,这是service的状态。
  4. SVC_CONSOLE:该service需要使用控制台。
  5. 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 <rt|be|idle> <ioprio 0-7>\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 <rt|be|dle> <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共同实现的。

时间: 2024-08-06 14:02:23

Android init进程——解析配置文件的相关文章

android init进程分析 init脚本解析和处理

(懒人近期想起我还有csdn好久没打理了.这个android init躺在我的草稿箱中快5年了.略微改改发出来吧) RC文件格式 rc文件是linux中常见的启动载入阶段运行的文件.rc是run commands的缩写.基本上能够理解为在启动阶段运行的一些列命令.android init进程启动时,也会运行此启动脚本文件,init.rc.init.rc的写法稍有点复杂,具体可參考 /system/core/init下的readme文件.脚本基本组成是由四类,为: commands: 命令 act

Android init.rc解析【转】

转自:http://www.linuxidc.com/Linux/2014-10/108438.htm 本文主要来自$Android_SOURCE/system/init/readme.txt的翻译. 1 简述 Android init.rc文件由系统第一个启动的init程序解析,此文件由语句组成,主要包含了四种类型的语句:Action,Commands,Services,Options.在init.rc文件中一条语句通常是占据一行.单词之间是通过空格符来相隔的.如果需要在单词内使用空格,那么得

Android Init进程命令的执行和服务的启动

这里开始分析init进程中配置文件的解析,在配置文件中的命令的执行和服务的启动. 首先init是一个可执行文件,它的对应的Makfile是init/Android.mk. Android.mk定义了init程序在编译的时候,使用了哪些源码,以及生成方式.当init程序生成之后,最终会放到/init,即根目录的init文件.通常所说的init进程就是执行这个init程序. 执行这个init程序的代码是在KERNEL/init/main.c文件中的kernel_init()函数里,当kernel把一

Android -- Init进程对信号的处理流程

Android -- Init进程对信号的处理流程 在Android中,当一个进程退出(exit())时,会向它的父进程发送一个SIGCHLD信号.父进程收到该信号后,会释放分配给该子进程的系统资源:并且父进程需要调用wait()或waitpid()等待子进程结束.如果父进程没有做这种处理,且父进程初始化时也没有调用signal(SIGCHLD, SIG_IGN)来显示忽略对SIGCHLD的处理,这时子进程将一直保持当前的退出状态,不会完全退出.这样的子进程不能被调度,所做的只是在进程列表中占据

Android Framework学习(一)之init进程解析

init进程是Android系统中用户空间的第一个进程,它被赋予了很多极其重要的工作职责,init进程相关源码位于system/core/init,本篇博客我们就一起来学习init进程(基于Android 7.0). init入口函数分析 init的入口函数为main,位于system/core/init/init.cpp int main(int argc, char** argv) { if (!strcmp(basename(argv[0]), "ueventd")) { ret

init进程 &amp;&amp; 解析Android启动脚本init.rc &amp;&amp; 修改它使不启动android &amp;&amp; init.rc中启动一个sh文件

Android启动后,系统执行的第一个进程是一个名称为init 的可执行程序.提供了以下的功能:设备管理.解析启动脚本.执行基本的功能.启动各种服务.代码的路径:system/core/init,编译的结果是一个可执行文件:init.这个init 的可执行文件是系统运行的第一个用户空间的程序,它以守护进程的方式运行.启动脚本则就是下面要讲的Init.rc. ======================================================================

Android init 语法解析

Android init脚本语言的规范 语法描述:system/core/init/readme.txt 关键字: token:  计算机语言中的一个单词,就跟英文中的单词差不多一人概念. Section: 语句块,相当于C语言中大括号内的一个块.一个Section以Service或On开头的语句块.以Service开头的Section叫做服务,而以On开头的叫做动作(Action). services: 服务. Action: 动作 commands:命令. options:选项. trigg

Android init进程——源码分析

概述 Android本质上是一个基于Linux内核的开源操作系统,与我现在用的Ubuntu系统类似,但是所有的Android设备都是运行在ARM处理器(ARM源自进阶精简指令集机器,源自ARM架构)上,而像Ubuntu操作系统是x86(x86是一系列的基于intel 8086 CPU计算机微处理器指令集架构)系统.不过既然Android也是基于Linux内核的系统,那么基本的启动过程也应该符合Linux的规则.下图基本描述了当你按下电源开关后Android设备的执行步骤: 一个完整的Linux系

android init进程分析 基本流程

(懒人最近想起我还有csdn好久没打理了,这个android init躺在我的草稿箱中快5年了,稍微改改发出来吧) android设备上电,引导程序引导进入boot(通常是uboot),加载initramfs.kernel镜像,启动kernel后,进入用户态程序.第一个用户空间程序是init, PID固定是1.在android系统上,init的代码位于/system/core/init下,基本功能有: 管理设备 解析并处理启动脚本init.rc 实时维护这个init.rc中的服务 init进程的