Android4.4之init

来源:互联网 发布:highcharts ajax json 编辑:程序博客网 时间:2024/04/28 01:45
android是基于Linux内核的操作系统,所以系统的第一个用户空间进程是init进程, pid固定是1。

1.系统属性的设置

来看一下源码
/system/core/init/init.c -> main()

962int main(int argc, char **argv)963{964    int fd_count = 0;965    struct pollfd ufds[4];966    char *tmpdev;967    char* debuggable;968    char tmp[32];969    int property_set_fd_init = 0;970    int signal_fd_init = 0;971    int keychord_fd_init = 0;972    bool is_charger = false;973974    if (!strcmp(basename(argv[0]), "ueventd"))975        return ueventd_main(argc, argv);976977    if (!strcmp(basename(argv[0]), "watchdogd"))978        return watchdogd_main(argc, argv);979    ...980 }

首先简单介绍下ueventd和watchdogd
*ueventd是一个守护进程,主要作用是接收uevent来创建或删除/dev/xxx(设备节点)
watchdog也是一个用户空间的守护进程,可以定期对系统进行检测*
eventd和watchdog是init的软连接,就是说events和watchdog程序都是执行init.cpp->main()
在这里main函数的参数argv的第一个,argv[0]为自身运行目录路径和程序名,从而执行相应的代码
ok,简单了解,这里并不是关注的重点,接着往下看。

/system/core/init/init.c -> main()

989  int main(int argc, char** argv) {997    ...998    // Clear the umask.999    umask(0);1000   ...1001 }

清理umask(进程的文件模型创建掩码),这个主要是文件权限的问题。
Linux内核给每一个进程都设定了一个掩码,当程序调用open、mkdir等函数创建文件或目录时,传入open的mode会先和掩码做运算,得到的文件mode,才是文件真正的mode。
譬如要创建一个目录,并设定它的文件权限为0777,mkdir(“testdir”, 0777)
但实际上写入的文件权限却未必是777,因为mkdir系统调用在创建testdir时,会将0777与当前进程的掩码(称为umask)运算,具体运算方法为 0777&~umask作为testdir的真正权限。因此上述init中首先调用umask(0)将进程掩码清0,这样调用open/mkdir等函数创建文件或目录时,传入的mode就会作为实际的值写入文件系统。
/system/core/init/init.c -> main()

982983        /* Get the basic filesystem setup we need put984         * together in the initramdisk on / and then we'll985         * let the rc file figure out the rest.986         */987    mkdir("/dev", 0755);988    mkdir("/proc", 0755);989    mkdir("/sys", 0755);990991    mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");992    mkdir("/dev/pts", 0755);993    mkdir("/dev/socket", 0755);994    mount("devpts", "/dev/pts", "devpts", 0, NULL);995    mount("proc", "/proc", "proc", 0, NULL);996    mount("sysfs", "/sys", "sysfs", 0, NULL);997998    /* indicate that booting is in progress to background fw loaders, etc */999    close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));

这里创建目录,并挂载内核文件系统。
/dev是内存文件系统,不会保存,每次开机都要重新创建。
检测/dev/.booting文件是否可读写和创建

接着看/system/core/init/init.c -> main()

1001        /* We must have some place other than / to create the1002         * device nodes for kmsg and null, otherwise we won't1003         * be able to remount / read-only later on.1004         * Now that tmpfs is mounted on /dev, we can actually1005         * talk to the outside world.1006         */1007    open_devnull_stdio();1008    klog_init();

看一下open_devnull_stdio()
/system/core/init/init.c -> open_devnull_stdio()

378void open_devnull_stdio(void)379{380    int fd;381    static const char *name = "/dev/__null__";382    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {383        fd = open(name, O_RDWR);384        unlink(name);385        if (fd >= 0) {386            dup2(fd, 0);387            dup2(fd, 1);388            dup2(fd, 2);389            if (fd > 2) {390                close(fd);391            }392            return;393        }394    }395396    exit(1);397}

这里/sys/fs/selinux/null并不存在会打开失败,从而创建/dev/null目录项并标准输入(0),标准输出(1),标准错误文件描述符(2)定向到/dev/null,这样输入输出就没有了。
为什么要把stdio重定向到/dev/null设备呢?因为此时系统刚开始启动,用于接收init进程标准输出、标准错误的设备节点还不存在,所以直接把它们重定向到/dev/null了。

没有标准输出怎么打印日志?
接着看init.c->klog_init()

35void klog_init(void)36{37    static const char *name = "/dev/__kmsg__";3839    if (klog_fd >= 0) return; /* Already initialized */4041    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {42        klog_fd = open(name, O_WRONLY);43        if (klog_fd < 0)44                return;45        fcntl(klog_fd, F_SETFD, FD_CLOEXEC);46        unlink(name);47    }48}49

并未初始化过,因此这里klog_fd为0,然后调用mknod创建设备节点文件/dev/kmsg.
其实这里的/dev/kmsg与/dev/kmsg是同一节点,他们的主设备号都为1,从设备号都为11。
同样上面的/dev/null与/dev/null也是同一节点,他们的主设备号都为1,从设备号都为3。
然后打开该文件将文件描述符保存到变量klog_fd中,接着调用fcntl(klog_fd, F_SETFD, FD_CLOEXEC),这里FD_CLOEXEC的值为1,表示当程序执行exec函数时本fd将被系统自动关闭,不传递给exec创建的新进程。
随后调用unlink来删除/dev/kmsg文件.
unlink功能描述:从文件系统中删除一个名称。如果名称是文件的最后一个连接,并且没有其它进程将文件打开,名称对应的文件会实际被删除。

继续看init.c

1007 int main(int argc, char **argv){1008    ...1009    property_init();1010    ...1011 }

这里是属性服务的初始化,可以参考这篇文章,讲的很详细了

接着看init.c->main();

1009 int main(int argc, char **argv){1010    ...1011    get_hardware_name(hardware, &revision);10121013    process_kernel_cmdline();1014    ...1015 }

先看下init.c->get_hardware_name();

399void get_hardware_name(char *hardware, unsigned int *revision)400{401    char data[1024];402    int fd, n;403    char *x, *hw, *rev;404405    /* Hardware string was provided on kernel command line */406    if (hardware[0])407        return;408409    fd = open("/proc/cpuinfo", O_RDONLY);410    if (fd < 0) return;411412    n = read(fd, data, 1023);413    close(fd);414    if (n < 0) return;415416    data[n] = 0;417    hw = strstr(data, "\nHardware");418    rev = strstr(data, "\nRevision");419420    if (hw) {421        x = strstr(hw, ": ");422        if (x) {423            x += 2;424            n = 0;425            while (*x && *x != '\n') {426                if (!isspace(*x))427                    hardware[n++] = tolower(*x);428                x++;429                if (n == 31) break;430            }431            hardware[n] = 0;432        }433    }434435    if (rev) {436        x = strstr(rev, ": ");437        if (x) {438            *revision = strtoul(x + 2, 0, 16);439        }440    }441}

get_hardware_name()函数从”/proc/cpuinfo”文件读取相应字符串到data中
这里写图片描述
最终将SMDK4x12转成小写保存在init的hardware,将0008转成16进制保存在init的revision中
strstr()函数的功能:就是在第一个参数中查找第二个参数第一次出现的地址,将地址赋值给一个字符指针,接着就可以利用这个字符指针找到从这个地址开始往后的字符。
再看下init.c->process_kernel_cmdline();

770static void process_kernel_cmdline(void)771{772    /* don't expose the raw commandline to nonpriv processes */773    chmod("/proc/cmdline", 0440);774775    /* first pass does the common stuff, and finds if we are in qemu.776     * second pass is only necessary for qemu to export all kernel params777     * as props.778     */779    import_kernel_cmdline(0, import_kernel_nv);780    if (qemu[0])781        import_kernel_cmdline(1, import_kernel_nv);782783    /* now propogate the info given on command line to internal variables784     * used by init as well as the current required properties785     */786    export_kernel_boot_props();787}443void import_kernel_cmdline(int in_qemu,444                           void (*import_kernel_nv)(char *name, int in_qemu))445{446    char cmdline[1024];447    char *ptr;448    int fd;449450    fd = open("/proc/cmdline", O_RDONLY);451    if (fd >= 0) {452        int n = read(fd, cmdline, 1023);453        if (n < 0) n = 0;454455        /* get rid of trailing newline, it happens */456        if (n > 0 && cmdline[n-1] == '\n') n--;457458        cmdline[n] = 0;459        close(fd);460    } else {461        cmdline[0] = 0;462    }463464    ptr = cmdline;465    while (ptr && *ptr) {466        char *x = strchr(ptr, ' ');467        if (x != 0) *x++ = 0;468        import_kernel_nv(ptr, in_qemu);469        ptr = x;470    }471}686static void import_kernel_nv(char *name, int for_emulator)687{688    char *value = strchr(name, '=');689    int name_len = strlen(name);690691    if (value == 0) return;692    *value++ = 0;693    if (name_len == 0) return;694695    if (for_emulator) {696        /* in the emulator, export any kernel option with the697         * ro.kernel. prefix */698        char buff[PROP_NAME_MAX];699        int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );700701        if (len < (int)sizeof(buff))702            property_set( buff, value );703        return;704    }705706    if (!strcmp(name,"qemu")) {707        strlcpy(qemu, value, sizeof(qemu));708    } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {709        const char *boot_prop_name = name + 12;710        char prop[PROP_NAME_MAX];711        int cnt;712713        cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);714        if (cnt < PROP_NAME_MAX)715            property_set(prop, value);716    }717}

首先将/proc/cmdline文件权限修改为只有root用户和root组用户可读写。
然后import_kernel_cmdline()函数的第一个参数in_qemu表示是否是虚拟机。
这里将/proc/cmdline的内容读进内存,然后以空格将整个字符串拆分成小的字符串
以我的samsung GT-N5100为例,/proc/cmdline内容为
这里写图片描述
然后分别调用import_kernel_nv()将拆分后的小字符串传入(注意这里的in_qemu为0,表示非虚拟机)。
import_kernel_nv()中将传入的字符串在以’=’拆分成name和value。此时for_emulator为0,所以跳到判断name是否等于’qemu’,又因为我/proc/cmdline中并没有qemu的name,所以并不会给qemu数组写入任何值,接着最终只会将格式为androidboot.XXX的name改成ro.boot.XXX,并将修改后的name和value写进android的系统属性中。
回到process_kernel_cmdline()由于前面并没有往qemu中写入任何值,所以qemu[0]为0,接着会执行export_kernel_boot_props()。
init.c->export_kernel_boot_props()

719static void export_kernel_boot_props(void)720{721    char tmp[PROP_VALUE_MAX];722    int ret;723    unsigned i;724    struct {725        const char *src_prop;726        const char *dest_prop;727        const char *def_val;728    } prop_map[] = {729        { "ro.boot.serialno", "ro.serialno", "", },730        { "ro.boot.mode", "ro.bootmode", "unknown", },731        { "ro.boot.baseband", "ro.baseband", "unknown", },732        { "ro.boot.bootloader", "ro.bootloader", "unknown", },733    };734735    for (i = 0; i < ARRAY_SIZE(prop_map); i++) {736        ret = property_get(prop_map[i].src_prop, tmp);737        if (ret > 0)738            property_set(prop_map[i].dest_prop, tmp);739        else740            property_set(prop_map[i].dest_prop, prop_map[i].def_val);741    }742743    ret = property_get("ro.boot.console", tmp);744    if (ret)745        strlcpy(console, tmp, sizeof(console));746747    /* save a copy for init's usage during boot */748    property_get("ro.bootmode", tmp);749    strlcpy(bootmode, tmp, sizeof(bootmode));750751    /* if this was given on kernel command line, override what we read752     * before (e.g. from /proc/cpuinfo), if anything */753    ret = property_get("ro.boot.hardware", tmp);754    if (ret)755        strlcpy(hardware, tmp, sizeof(hardware));756    property_set("ro.hardware", hardware);757758    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);759    property_set("ro.revision", tmp);760761    /* TODO: these are obsolete. We should delete them */762    if (!strcmp(bootmode,"factory"))763        property_set("ro.factorytest", "1");764    else if (!strcmp(bootmode,"factory2"))765        property_set("ro.factorytest", "2");766    else767        property_set("ro.factorytest", "0");768}

这里很简单,就是设置一些系统属性。接着往下看main()。
init.c->main()

1015    union selinux_callback cb;1016    cb.func_log = klog_write;1017    selinux_set_callback(SELINUX_CB_LOG, cb);10181019    cb.func_audit = audit_callback;1020    selinux_set_callback(SELINUX_CB_AUDIT, cb);10211022    selinux_initialize();1023    /* These directories were necessarily created before initial policy load1024     * and therefore need their security context restored to the proper value.1025     * This must happen before /dev is populated by ueventd.1026     */1027    restorecon("/dev");1028    restorecon("/dev/socket");1029    restorecon("/dev/__properties__");1030    restorecon_recursive("/sys");1031

这段代码是4.1之后添加的,有关SELinux的初始化和检查,暂时还没有研究过SELinux,而且这里与android启动的关系也不大,暂时先不看。
init.c->main()

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

如果当前启动模式不是充电模式,将从/default.prop文件中加载默认的一些属性设置。

2. init.rc的解析

接下来好戏开始了,开始对init.rc进行解析
关于init.rc的具体语法可以看下/system/core/init/readme.txt,这里简单介绍下
init.rc主要定义两类结构:action与service
action
action是一组命令的集合

38Actions take the form of:40on <trigger>41   <command>42   <command>43   <command>

先说下command,command的格式如下
command-name <parament1> [parament2...]
<>表示必须存在的参数,[]表示可选参数
可以这样理解,每个command表示一个要执行的动作,对应一个函数当action被触发时,会依次执行每个command对应的函数。

trigger是一个触发器,表示什么时候执行这个action,在init.rc中主要有两种类型:
1普通型
这种类型的trigger直接以字符串表示,用以描述一个时间节点,当该trigger对应的action加入到全局action_list中后(该过程下面会讲述),在init.c->main()中可以通过相应的时间节点找出相应的action并将他们按序加入到一个待执行队列中稍后依次触发他们。
2属性型
trigger为property:<name>=<value>
这种类型的action只有在property name的值为value时才会被触发。

service
service表示一个可执行程序

51service <name> <pathname> [ <argument> ]*52   <option>53   <option>54   ...

name字段为service的名字
pathname为该service对应的二进制程序的路径
随后是该程序的参数列表
option是该service的属性,具体option可以看下/system/core/init/readme.txt的描述。
这里提一下有一个class属性,

101class <name>102   Specify a class name for the service.  All services in a103   named class may be started or stopped together.  A service104   is in the class "default" if one is not specified via the105   class option.

该属性name在init.rc主要有core, main,charger三种,如果service没有定义该属性则name默认为default。他的作用将service归属于一个class组,然后可以在Actions中通过class_start、class_stop、class_reset等命令启动、停止、重启动相应class组中的所有service。

补充下除了action和service,还有一个import是用来导入其他init脚本文件的

好了,来具体看一下解析init.rc的代码
init.c->main()

1036int main(int argc, char **argv)1037    ...1038    INFO("reading config file\n");1039    init_parse_config_file("/init.rc");1040    ...1041}

/system/core/init/init_parser.c

404int init_parse_config_file(const char *fn)405{406    char *data;407    data = read_file(fn, 0);408    if (!data) return -1;409410    parse_config(fn, data);411    DUMP();412    return 0;413}

先将/init.rc的内容读入内存,然后调用parse_config()函数进行解析。

/system/core/init/init_parser.c

347static void parse_config(const char *fn, char *s)348{349    struct parse_state state;350    struct listnode import_list;351    struct listnode *node;352    char *args[INIT_PARSER_MAXARGS];353    int nargs;354355    nargs = 0;356    state.filename = fn;357    state.line = 0;358    state.ptr = s;359    state.nexttoken = 0;360    state.parse_line = parse_line_no_op;361362    list_init(&import_list);363    state.priv = &import_list;364365    for (;;) {366        switch (next_token(&state)) {367        case T_EOF:368            state.parse_line(&state, 0, 0);369            goto parser_done;370        case T_NEWLINE:371            state.line++;372            if (nargs) {373                int kw = lookup_keyword(args[0]);374                if (kw_is(kw, SECTION)) {375                    state.parse_line(&state, 0, 0);376                    parse_new_section(&state, kw, nargs, args);377                } else {378                    state.parse_line(&state, nargs, args);379                }380                nargs = 0;381            }382            break;383        case T_TEXT:384            if (nargs < INIT_PARSER_MAXARGS) {385                args[nargs++] = state.text;386            }387            break;388        }389    }390391parser_done:392    list_for_each(node, &import_list) {393         struct import *import = node_to_item(node, struct import, list);394         int ret;395396         INFO("importing '%s'", import->filename);397         ret = init_parse_config_file(import->filename);398         if (ret)399             ERROR("could not import file '%s' from '%s'\n",400                   import->filename, fn);401    }402}

parse_config函数比较复杂主要做了
1. 解析字符串中所有的action,并将其链入action_list队列
2. 解析字符串中所有的service,并将他们链入service_list队列
3. 将import的文件读进内存,然后重复前面两步。

ok,在接下来具体分析之前先简单看一下有关的几个数据结构:
/system/core/init/init.h

26struct command27{28        /* list of commands in an action */29    struct listnode clist;//用于将command链入一个双链队列3031    int (*func)(int nargs, char **args);//command语句所对应的函数指针32    int nargs;//command语句所表示的命令对应的函数的参数个数33    char *args[1];//命令对应的函数的参数34};3536struct action {37        /* node in list of all actions */38    struct listnode alist;//用于将action链入action_list队列39        /* node in the queue of pending actions */40    struct listnode qlist;//用于将action链入action_queue队列41        /* node in list of actions for a trigger */42    struct listnode tlist;//也是用于将action链入队列,暂时在代码中看到4344    unsigned hash;//在目前init实现中未使用45    const char *name;//action的trigger(用来标记action的执行时机)4647    struct listnode commands;//该action所有struct command链表48    struct command *current;//在Actions执行时,存储当前正在被执行的struct command指针。49};51struct socketinfo {52    struct socketinfo *next;53    const char *name;54    const char *type;55    uid_t uid;56    gid_t gid;57    int perm;58};5960struct svcenvinfo {61    struct svcenvinfo *next;62    const char *name;63    const char *value;64};6581struct service {82        /* list of all services */83    struct listnode slist;//用于将action链入service_list队列8485    const char *name;//service的name86    const char *classname;//设置service的类别,感觉有点像开机启动service的优先级,默认的class名称为default,还有core、main、charger8788    unsigned flags;////位图变量,其各个位代表不同的servcie的属性(对应service中的option字段)89    pid_t pid;////当service对应的程序执行时,存放其进程号 90    time_t time_started;    //进程启动时间  91    time_t time_crashed;    //存放第一次进程崩溃时间92    int nr_crashed;       //存放进程崩溃次数  9394    uid_t uid;//该servcie对应进程的uid  95    gid_t gid;//该service对应进程的gidinit_parse_config_file("/init.rc"); 96    gid_t supp_gids[NR_SVC_SUPP_GIDS];//该service对应进程的附加群组id  97    size_t nr_supp_gids;//该service所隶属的附件组的数目 9899    char *seclabel;//存放selinux所需要的security context  100101    struct socketinfo *sockets;// 为service创建的sockets 102    struct svcenvinfo *envvars;// 为service设置的环境变量 103104    struct action onrestart;  //服务重启时,执行的命令105106    /* keycodes for triggering this service via /dev/keychord */107    int *keycodes;//keycodes相关,init中未使用108    int nkeycodes;109    int keychord_id;110111    int ioprio_class;// io优先级  112    int ioprio_pri;113114    int nargs;//对应service语句传入的参数数目  115    /* "MUST BE AT THE END OF THE STRUCT" */116    char *args[1];//存放service语句实际传入的参数,其长度将会被修正为nargs+1  117}; /*     ^-------'args' MUST be at the end of this struct! */

另外还有一点先说明下,init_parse.c中定义了三个全局的双链队列:

/system/core/init/init_parser.c39static list_declare(service_list);40static list_declare(action_list);41static list_declare(action_queue);

service_list是全局service链表,解析启动脚本过程中,service对应数据结构struct service将会挂载到这里。action_list是全局Actions链表,Actions对应数据结构struct action将会挂载到这里。至于action_queue在解析完毕之后,实际执行Actions时才会用到。

/system/core/init/parser.h

24struct parse_state25{26    char *ptr;27    char *text;28    int line;29    int nexttoken;30    void *context;31    void (*parse_line)(struct parse_state *state, int nargs, char **args);32    const char *filename;33    void *priv;34};

ptr是还未解析的字符串
text当前读出的内容
line当然就是行数了
nexttoken保存下一个要执行的操作(0或T_NEWLINE)
filename当前解析字符串所属文件的文件名
context当前正在解析的action或者service对象
parse_line解析command(parse_line_action())或者option(parse_line_service())的函数指针,或者如果如果当前并没有在解析一个action或service这个函数指针指向parse_line_no_op()是一个空实现。
state.priv指向一个链表,是当前文件import的所有文件。

/system/core/include/cutils/list.h

26struct listnode27{28    struct listnode *next;29    struct listnode *prev;30};

/system/core/libcutils/list.c

19void list_init(struct listnode *node)20{21    node->next = node;22    node->prev = node;23}

/system/core/libcutils/list.c

25void list_add_tail(struct listnode *head, struct listnode *item)26{27    item->next = head;28    item->prev = head->prev;29    head->prev->next = item;30    head->prev = item;31}

这里listnode的作用很明显也很简单,就是listnode的宿主数据结构可以通过listnode链入一个双向的队列中。

ok,来看代码:
/system/core/init/parser.c

68int next_token(struct parse_state *state)69{70    char *x = state->ptr;71    char *s;7273    if (state->nexttoken) {74        int t = state->nexttoken;75        state->nexttoken = 0;76        return t;77    }7879    for (;;) {80        switch (*x) {81        case 0:82            state->ptr = x;83            return T_EOF;84        case '\n':85            x++;86            state->ptr = x;87            return T_NEWLINE;88        case ' ':89        case '\t':90        case '\r':91            x++;92            continue;93        case '#':94            while (*x && (*x != '\n')) x++;95            if (*x == '\n') {96                state->ptr = x+1;97                return T_NEWLINE;98            } else {99                state->ptr = x;100                return T_EOF;101            }102        default:103            goto text;104        }105    }106107textdone:108    state->ptr = x;109    *s = 0;110    return T_TEXT;111text:112    state->text = s = x;113textresume:114    for (;;) {115        switch (*x) {116        case 0:117            goto textdone;118        case ' ':119        case '\t':120        case '\r':121            x++;122            goto textdone;123        case '\n':124            state->nexttoken = T_NEWLINE;125            x++;126            goto textdone;127        case '"':128            x++;129            for (;;) {130                switch (*x) {131                case 0:132                        /* unterminated quoted thing */133                    state->ptr = x;134                    return T_EOF;135                case '"':136                    x++;137                    goto textresume;138                default:139                    *s++ = *x++;140                }141            }142            break;143        case '\\':144            x++;145            switch (*x) {146            case 0:147                goto textdone;148            case 'n':149                *s++ = '\n';150                break;151            case 'r':152                *s++ = '\r';153                break;154            case 't':155                *s++ = '\t';156                break;157            case '\\':158                *s++ = '\\';159                break;160            case '\r':161                    /* \ <cr> <lf> -> line continuation */162                if (x[1] != '\n') {163                    x++;164                    continue;165                }166            case '\n':167                    /* \ <lf> -> line continuation */168                state->line++;169                x++;170                    /* eat any extra whitespace */171                while((*x == ' ') || (*x == '\t')) x++;172                continue;173            default:174                    /* unknown escape -- just copy */175                *s++ = *x++;176            }177            continue;178        default:179            *s++ = *x++;180        }181    }182    return T_EOF;183}

next_token()的作用是忽略state->ptr(还未解析的字符串)中的注释,从中读出字符串并保存在state->text中直到遇到空格或换行。
当遇到空格时返回T_TEXT,当遇到换行时返回T_NEWLINE。
截取init.rc中部分内容为例:

12on early-init13    # Set init and its forked children's oom_adj.14    write /proc/1/oom_adj -161516    # Set the security context for the init process.17    # This should occur before anything else (e.g. ueventd) is started.18    setcon u:r:init:s01920    start ueventd

第一次执行next_token()读到on和early-init之间的空格,将on的开始地址保存在state->text中。state->ptr指针增加三位,从early-init开始。然后返回T_TEXT。
回到parse_config()中如果next_token()返回的是T_TEXT,而且nargs(此时为0)小于64,则将state->text以nargs为脚标存进args数组中。此时,args[0]存放的就是指向”on”开始字符串的首地址。
然后继续执行next_token(),这一次读到了第一行尾。并将early-init字符串的开始地址保存在state->text中。state->ptr指针增加11位,从第二行开始。将state->nexttoken 的值设置为T_NEWLINE;然后返回T_TEXT。
再回到parse_config()中这时next_token()返回的还是T_TEXT,所以将”early-init”开始字符串的首地址存进args[1]中。
这时再进入next_token(),因为state->nexttoken的值为T_NEWLINE(2),非0,所以先将state->nexttoken设为0,然后返回T_NEWLINE。
回到parse_config()中这时next_token()返回的就是T_NEWLINE了,所以先将行号增加1,然后判断nargs(此时为2)非0,接着调用lookup_keyword()并将args[0]也就是”on”的首地址传了过去。
看一下lookup_keyword()

79int lookup_keyword(const char *s)80{81    switch (*s++) {80       ...128    case 'o':129        if (!strcmp(s, "n")) return K_on;130        if (!strcmp(s, "neshot")) return K_oneshot;131        if (!strcmp(s, "nrestart")) return K_onrestart;132        break;133     ...169    return K_UNKNOWN;170}

卧槽,K_on在哪,瞬间蒙逼了…看下/system/core/init/init_parser.c中的这块代码

58#include "keywords.h"5960#define KEYWORD(symbol, flags, nargs, func) \61    [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },6263struct {64    const char *name;65    int (*func)(int nargs, char **args);66    unsigned char nargs;67    unsigned char flags;68} keyword_info[KEYWORD_COUNT] = {69    [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },70#include "keywords.h"71};72#undef KEYWORD

这里的代码很巧妙,先理解下define中的#和##:
#是把参数字符串化;##是一个连接符号,用于把参数连在一起
这里keywords.h被include两遍,在第一次include keywords.h时KEYWORD宏被定义为下面形式

#define KEYWORD(symbol, flags, nargs, func) K_##symbol,

所以keywords.h中的enum会改为下面形式

43enum {44    K_UNKNOWN,46    K_capability,47    K_chdir, 49  ...68    K_on,69  ...100    K_ioprio,102    KEYWORD_COUNT,103};

之后KEYWORD宏被undef了,在init_parser.c中重新被定义为

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

所以在第二次include keywords.h时keywords.h就变成了

68 keyword_info[KEYWORD_COUNT] = {69    [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },70  [K_capability]={"capability",0,  0, 0x04},71  ...72  [K_on]={"on", 0,0,0x01},73  ...74};

ok,这下子就明朗的。lookup_keyword()返回枚举类型K_on,然后通过宏kw_is(kw, type) (keyword_info[kw].flags & (type))检查K_on是否是一个SECTION,结果为true,接着执行下面两句

375                    state.parse_line(&state, 0, 0);376                    parse_new_section(&state, kw, nargs, args);

这时state的函数指针parse_line指向parse_line_no_op,是一个空操作,所以往下看parse_new_section()函数
/system/core/init/init_parser.c

320void parse_new_section(struct parse_state *state, int kw,321                       int nargs, char **args)322{323    printf("[ %s %s ]\n", args[0],324           nargs > 1 ? args[1] : "");325    switch(kw) {326    case K_service:327        state->context = parse_service(state, nargs, args);328        if (state->context) {329            state->parse_line = parse_line_service;330            return;331        }332        break;333    case K_on:334        state->context = parse_action(state, nargs, args);335        if (state->context) {336            state->parse_line = parse_line_action;337            return;338        }339        break;340    case K_import:341        parse_import(state, nargs, args);342        break;343    }344    state->parse_line = parse_line_no_op;345}

parseaction()通过参数生成一个action结构,并把它保存在state->context中,然后将state->parse_line函数指针指向parse_line_service(),然后parse_new_section()就返回了。来具体看下parse_action():
/system/core/init/init_parser.c

821static void *parse_action(struct parse_state *state, int nargs, char **args)822{823    struct action *act;824    if (nargs < 2) {825        parse_error(state, "actions must have a trigger\n");826        return 0;827    }828    if (nargs > 2) {829        parse_error(state, "actions may not have extra parameters\n");830        return 0;831    }832    act = calloc(1, sizeof(*act));833    act->name = args[1];834    list_init(&act->commands);835    list_init(&act->qlist);836    list_add_tail(&action_list, &act->alist);837        /* XXX add to hash */838    return act;839}

这里一开始先检查了下action的参数个数是否符合语法规定,然后调用calloc()申请一个action节点,接着将trigger(这里也就是early-init)保存到了act->name。然后初始化了act->qlist和act->commands,并将该action链入action_list队列尾部,这几点不多说了。最后返回该action。
现在就返回到init_parser.c的->parse_config()了,然后把nargs置为0就开始了下一次for循环。略过#开头的注释行解析,next_token()函数解析完14 write /proc/1/oom_adj -16这一行后,nargs为3,args数组前三项分别保存了”write”,”/proc/1/oom_adj”,”-16”三个字符串。所以lookup_keyword()返回K_write, 接着kw_is(kw, SECTION)为false,因此执行state.parse_line(&state, nargs, args);。这里parse_line函数指针在之前parse_new_section()中指向了parse_line_action(),来具体看一下:
/system/core/init/init_parser.c

841static void parse_line_action(struct parse_state* state, int nargs, char **args)842{843    struct command *cmd;844    struct action *act = state->context;845    int (*func)(int nargs, char **args);846    int kw, n;847848    if (nargs == 0) {849        return;850    }851852    kw = lookup_keyword(args[0]);853    if (!kw_is(kw, COMMAND)) {854        parse_error(state, "invalid command '%s'\n", args[0]);855        return;856    }857858    n = kw_nargs(kw);859    if (nargs < n) {860        parse_error(state, "%s requires %d %s\n", args[0], n - 1,861            n > 2 ? "arguments" : "argument");862        return;863    }864    cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);865    cmd->func = kw_func(kw);866    cmd->nargs = nargs;867    memcpy(cmd->args, args, sizeof(char*) * nargs);868    list_add_tail(&act->commands, &cmd->clist);869}

这里就是申请一个command节点,将cmd->func函数指针指向初始化keyword_info时定义的函数指针,这里是声明在keywords.h中的do_write()函数,然后将nargs保存在cmd->nargs中,将args数组保存在cmd->args中,最后将command链入act->commands的队列尾。
到这里基本上就能够明白action的解析过程了,解析完的action都被保存在了全局的action_list中,后面会根据trigger也就是act->name将action链入action_queue中然后依次执行,这个后面 action的执行 章节再看。

看完action再来看下service,以下面代码为例

408service ueventd /sbin/ueventd409    class core410    critical411    seclabel u:r:ueventd:s0

类似action的解析,解析完第一行后args数组前三项分别存放了”service”,”evened”和”/sbin/ueventd”字符串的首地址。这里传入parse_line()的nargs为0,所以可以直接忽略这部调用,因为无论是在parse_line_service()还是在parse_line_action()中,当nargs为0都没有做任何事情。进入parse_new_section()中,此时kw=K_service,所以调用parse_service()解析service,并将解析的service保存到state->context,然后将state->parse_line函数指针指向parse_line_service(),接着就返回了。来看下parse_service():
/system/core/init/init_parser.c

613static void *parse_service(struct parse_state *state, int nargs, char **args)614{615    struct service *svc;616    if (nargs < 3) {617        parse_error(state, "services must have a name and a program\n");618        return 0;619    }620    if (!valid_name(args[1])) {621        parse_error(state, "invalid service name '%s'\n", args[1]);622        return 0;623    }624625    svc = service_find_by_name(args[1]);626    if (svc) {627        parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);628        return 0;629    }630631    nargs -= 2;632    svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);633    if (!svc) {634        parse_error(state, "out of memory\n");635        return 0;636    }637    svc->name = args[1];638    svc->classname = "default";639    memcpy(svc->args, args + 2, sizeof(char*) * nargs);640    svc->args[nargs] = 0;641    svc->nargs = nargs;642    svc->onrestart.name = "onrestart";643    list_init(&svc->onrestart.commands);644    list_add_tail(&service_list, &svc->slist);645    return svc;646}

service_find_by_name()是根据传过来的service name去全局serviced队列service_list中查找相同name的service,主要是为了避免重复定义同名service。接着调用calloc()申请一个service节点,将service name(这里是”ueventd”)赋值给svc->name,svc->classname默认设为”default”,nargs赋值给svc->nargs,初始化该service的onrestart.commands队列,最后将该service链入全局service_list,然后返回该service。
先在可以返回到parse_config()中继续调用next_token()读出内容了,当读完上面示例的第二行后,args数组前两项分别存放了”class”和”core”字符串的首地址,nargs为2。接着得到kw为K_class不是一个SECTION所以执行state.parse_line()也就是前面设置的parse_line_service():
/system/core/init/init_parser.c

648static void parse_line_service(struct parse_state *state, int nargs, char **args)649{650    struct service *svc = state->context;651    struct command *cmd;652    int i, kw, kw_nargs;653654    if (nargs == 0) {655        return;656    }657658    svc->ioprio_class = IoSchedClass_NONE;659660    kw = lookup_keyword(args[0]);661    switch (kw) {662    case K_capability:663        break;664    case K_class:665        if (nargs != 2) {666            parse_error(state, "class option requires a classname\n");667        } else {668            svc->classname = args[1];669        }670        break;671    case K_console:672        svc->flags |= SVC_CONSOLE;673        break;674    case K_disabled:675        svc->flags |= SVC_DISABLED;676        svc->flags |= SVC_RC_DISABLED;677        break;678    case K_ioprio:679        if (nargs != 3) {680            parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n");681        } else {682            svc->ioprio_pri = strtoul(args[2], 0, 8);683684            if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {685                parse_error(state, "priority value must be range 0 - 7\n");686                break;687            }688689            if (!strcmp(args[1], "rt")) {690                svc->ioprio_class = IoSchedClass_RT;691            } else if (!strcmp(args[1], "be")) {692                svc->ioprio_class = IoSchedClass_BE;693            } else if (!strcmp(args[1], "idle")) {694                svc->ioprio_class = IoSchedClass_IDLE;695            } else {696                parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n");697            }698        }699        break;700    case K_group:701        if (nargs < 2) {702            parse_error(state, "group option requires a group id\n");703        } else if (nargs > NR_SVC_SUPP_GIDS + 2) {704            parse_error(state, "group option accepts at most %d supp. groups\n",705                        NR_SVC_SUPP_GIDS);706        } else {707            int n;708            svc->gid = decode_uid(args[1]);709            for (n = 2; n < nargs; n++) {710                svc->supp_gids[n-2] = decode_uid(args[n]);711            }712            svc->nr_supp_gids = n - 2;713        }714        break;715    case K_keycodes:716        if (nargs < 2) {717            parse_error(state, "keycodes option requires atleast one keycode\n");718        } else {719            svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));720            if (!svc->keycodes) {721                parse_error(state, "could not allocate keycodes\n");722            } else {723                svc->nkeycodes = nargs - 1;724                for (i = 1; i < nargs; i++) {725                    svc->keycodes[i - 1] = atoi(args[i]);726                }727            }728        }729        break;730    case K_oneshot:731        svc->flags |= SVC_ONESHOT;732        break;733    case K_onrestart:734        nargs--;735        args++;736        kw = lookup_keyword(args[0]);737        if (!kw_is(kw, COMMAND)) {738            parse_error(state, "invalid command '%s'\n", args[0]);739            break;740        }741        kw_nargs = kw_nargs(kw);742        if (nargs < kw_nargs) {743            parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,744                kw_nargs > 2 ? "arguments" : "argument");745            break;746        }747748        cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);749        cmd->func = kw_func(kw);750        cmd->nargs = nargs;751        memcpy(cmd->args, args, sizeof(char*) * nargs);752        list_add_tail(&svc->onrestart.commands, &cmd->clist);753        break;754    case K_critical:755        svc->flags |= SVC_CRITICAL;756        break;757    case K_setenv: { /* name value */758        struct svcenvinfo *ei;759        if (nargs < 2) {760            parse_error(state, "setenv option requires name and value arguments\n");761            break;762        }763        ei = calloc(1, sizeof(*ei));764        if (!ei) {765            parse_error(state, "out of memory\n");766            break;767        }768        ei->name = args[1];769        ei->value = args[2];770        ei->next = svc->envvars;771        svc->envvars = ei;772        break;773    }774    case K_socket: {/* name type perm [ uid gid ] */775        struct socketinfo *si;776        if (nargs < 4) {777            parse_error(state, "socket option requires name, type, perm arguments\n");778            break;779        }780        if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")781                && strcmp(args[2],"seqpacket")) {782            parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n");783            break;784        }785        si = calloc(1, sizeof(*si));786        if (!si) {787            parse_error(state, "out of memory\n");788            break;789        }790        si->name = args[1];791        si->type = args[2];792        si->perm = strtoul(args[3], 0, 8);793        if (nargs > 4)794            si->uid = decode_uid(args[4]);795        if (nargs > 5)796            si->gid = decode_uid(args[5]);797        si->next = svc->sockets;798        svc->sockets = si;799        break;800    }801    case K_user:802        if (nargs != 2) {803            parse_error(state, "user option requires a user id\n");804        } else {805            svc->uid = decode_uid(args[1]);806        }807        break;808    case K_seclabel:809        if (nargs != 2) {810            parse_error(state, "seclabel option requires a label string\n");811        } else {812            svc->seclabel = args[1];813        }814        break;815816    default:817        parse_error(state, "invalid option '%s'\n", args[0]);818    }819}

这里就是根据kw的值设置前面创建的service的相应的属性或标记位,比较简单不多描述了。

init.rc中import的作用类似于include,就是导入其他的文件内容。

最后返回到init_parse_config_file()中,DUMP()是跟log有关的函数不需关注。

这样基本上init.rc的解析基本上就结束了。到此已经把init.rc及其导入文件中所有的action和service已经解析完了并分别链入了全局的action_list和service_list中,那么他们什么时候执行呢?带着问题,来看下下一节:action_queue的链入。

3. action_queue的链入

回到/system/core/init/init.c->main(),接下来就是从action_list中取出相应时间节点的action并将其链入action_queue中等待执行。看代码:
/system/core/init/init.c->main()

1039int main(int argc, char **argv){1040    ...1041    action_for_each_trigger("early-init", action_add_queue_tail);10421043    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");1044    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");1045    queue_builtin_action(keychord_init_action, "keychord_init");1046    queue_builtin_action(console_init_action, "console_init");10471048    /* execute all the boot actions to get us started */1049    action_for_each_trigger("init", action_add_queue_tail);10501051    /* skip mounting filesystems in charger mode */1052    if (!is_charger) {1053        action_for_each_trigger("early-fs", action_add_queue_tail);1054        action_for_each_trigger("fs", action_add_queue_tail);1055        action_for_each_trigger("post-fs", action_add_queue_tail);1056        action_for_each_trigger("post-fs-data", action_add_queue_tail);1057    }10581059    /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random1060     * wasn't ready immediately after wait_for_coldboot_done1061     */1062    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");10631064    queue_builtin_action(property_service_init_action, "property_service_init");1065    queue_builtin_action(signal_init_action, "signal_init");1066    queue_builtin_action(check_startup_action, "check_startup");10671068    if (is_charger) {1069        action_for_each_trigger("charger", action_add_queue_tail);1070    } else {1071        action_for_each_trigger("early-boot", action_add_queue_tail);1072        action_for_each_trigger("boot", action_add_queue_tail);1073    }10741075        /* run all property triggers based on current state of the properties */1076    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");107710781079#if BOOTCHART1080    queue_builtin_action(bootchart_init_action, "bootchart_init");1081#endif1082    ...1083}

这里主要涉及两个函数action_for_each_trigger()和queue_builtin_action(),分别来看下
action_for_each_trigger()两个参数:一个为action的trigger;另一个为一个函数指针,init.c->main()中该函数指针传的都是action_add_queue_tail()。看下代码:

504void action_for_each_trigger(const char *trigger,505                             void (*func)(struct action *act))506{507    struct listnode *node;508    struct action *act;509    list_for_each(node, &action_list) {510        act = node_to_item(node, struct action, alist);511        if (!strcmp(act->name, trigger)) {512            func(act);513        }514    }515}588void action_add_queue_tail(struct action *act)589{590    if (list_empty(&act->qlist)) {591        list_add_tail(&action_queue, &act->qlist);592    }593}

代码简单到掉眼泪,就是循环遍历全局队列action_list的每个节点,如果发现某个action的trigger与传过来的相同,就调用action_add_queue_tail()将该action链入action_queue队列的尾部。

在看下queue_builtin_action()函数的两个参数:第一个为一个函数指针,第二个看形参名就知道是action的trigger。来具体看下代码:

569void queue_builtin_action(int (*func)(int nargs, char **args), char *name)570{571    struct action *act;572    struct command *cmd;573574    act = calloc(1, sizeof(*act));575    act->name = name;576    list_init(&act->commands);577    list_init(&act->qlist);578579    cmd = calloc(1, sizeof(*cmd));580    cmd->func = func;581    cmd->args[0] = name;582    list_add_tail(&act->commands, &cmd->clist);583584    list_add_tail(&action_list, &act->alist);585    action_add_queue_tail(act);586}

代码也是非常之简单,主要做了三件事情:
1)构造一个触发器为name的struct action结构体,并创建一个struct command,对应函数为行参fund
2)将struct action添加到action_list链表末尾。
3)将struct action添加到action_queue链表末尾。

好的现在万事俱备,只差执行action_queue队列了。

4.action的执行

1083    for(;;) {1084        int nr, i, timeout = -1;10851086        execute_one_command();1087        restart_processes();1088        ...1089    }

现在到了init.c->main()最后的for循环了。
execute_one_command()的作用就是执行action的一条command。
restart_processes()作用就是执行标志为SVC_RESTARTING的进程,fork一个新进程。
分别看下代码:

531void execute_one_command(void)532{533    int ret;534535    if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {536        cur_action = action_remove_queue_head();537        cur_command = NULL;538        if (!cur_action)539            return;540        INFO("processing action %p (%s)\n", cur_action, cur_action->name);541        cur_command = get_first_command(cur_action);542    } else {543        cur_command = get_next_command(cur_action, cur_command);544    }545546    if (!cur_command)547        return;548549    ret = cur_command->func(cur_command->nargs, cur_command->args);550    INFO("command '%s' r=%d\n", cur_command->args[0], ret);551}595struct action *action_remove_queue_head(void)596{597    if (list_empty(&action_queue)) {598        return 0;599    } else {600        struct listnode *node = list_head(&action_queue);601        struct action *act = node_to_item(node, struct action, qlist);602        list_remove(node);603        list_init(node);604        return act;605    }606}514static struct command *get_next_command(struct action *act, struct command *cmd)515{516    struct listnode *node;517    node = cmd->clist.next;518    if (!node)519        return NULL;520    if (node == &act->commands)521        return NULL;522523    return node_to_item(node, struct command, clist);524}

execute_one_command()先判断当前action为空,或者当前command为空,或者当前command是action的最后一个command,就会会通过action_remove_queue_head()从action_queue头部取下一个listnode并把它转换成action结构,并将cur_action指针指向它。然后从该action的command队列中取出它的第一个listnode并把它转换成command结构,接着再将cur_command指针指向这个command。
如果前面一步的if判断结果为false,说明当前有一个action在执行,并且它的command还没有执行完,所以通过get_next_command()取出它的下一个command。
最后就执行了前面得到的command的func函数指针指向的函数。
再看下restart_processes():

434static void restart_processes()435{436    process_needs_restart = 0;437    service_for_each_flags(SVC_RESTARTING,438                           restart_service_if_needed);439}491void service_for_each_flags(unsigned matchflags,492                            void (*func)(struct service *svc))493{494    struct listnode *node;495    struct service *svc;496    list_for_each(node, &service_list) {497        svc = node_to_item(node, struct service, slist);498        if (svc->flags & matchflags) {499            func(svc);500        }501    }502}418static void restart_service_if_needed(struct service *svc)419{420    time_t next_start_time = svc->time_started + 5;421422    if (next_start_time <= gettime()) {423        svc->flags &= (~SVC_RESTARTING);424        service_start(svc, NULL);425        return;426    }427428    if ((next_start_time < process_needs_restart) ||429        (process_needs_restart == 0)) {430        process_needs_restart = next_start_time;431    }432}

service_for_each_flags()遍历service_list列表,找出那些flags中携带有SVC_RESTARTING标志的service节点,并执行restart_service_if_needed()。注意:为了防止出现service频繁重启,本次启动距离上次启动时间不得少于5秒。
service的具体启动service_start()函数后面有时间单独写篇文章介绍。
这样在这个无限循环中,action_queue里面每个action的每个command最终都会被执行,每个满足条件的需重启的service也会被启动。

for循环中还有这样一段代码:

1089        if (!property_set_fd_init && get_property_set_fd() > 0) {1090            ufds[fd_count].fd = get_property_set_fd();1091            ufds[fd_count].events = POLLIN;1092            ufds[fd_count].revents = 0;1093            fd_count++;1094            property_set_fd_init = 1;1095        }1096        if (!signal_fd_init && get_signal_fd() > 0) {1097            ufds[fd_count].fd = get_signal_fd();1098            ufds[fd_count].events = POLLIN;1099            ufds[fd_count].revents = 0;1100            fd_count++;1101            signal_fd_init = 1;1102        }1103        if (!keychord_fd_init && get_keychord_fd() > 0) {1104            ufds[fd_count].fd = get_keychord_fd();1105            ufds[fd_count].events = POLLIN;1106            ufds[fd_count].revents = 0;1107            fd_count++;1108            keychord_fd_init = 1;1109        }

这三个描述符分别是用来监听属性改变、子进程死亡和组合按键的。他们分别是在
queue_builtin_action(property_service_init_action, “property_service_init”);
queue_builtin_action(signal_init_action, “signal_init”);
queue_builtin_action(keychord_init_action, “keychord_init”);
中被创建。
组合按键关键字是keycodes,如果某个service中含有keycodes选项,那么当用户按下某种组合键时,该service将被重启。init.rc中并没有包含该关键字的service,不详细阐述了。
/system/core/init/init.c

789static int property_service_init_action(int nargs, char **args)790{791    /* read any property files on system or data and792     * fire up the property service.  This must happen793     * after the ro.foo properties are set above so794     * that /data/local.prop cannot interfere with them.795     */796    start_property_service();797    return 0;798}

/system/core/init/property_service.c

581void start_property_service(void)582{583    int fd;584585    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);586    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);587    load_override_properties();588    /* Read persistent properties after all default values have been loaded. */589    load_persistent_properties();590591    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);592    if(fd < 0) return;593    fcntl(fd, F_SETFD, FD_CLOEXEC);594    fcntl(fd, F_SETFL, O_NONBLOCK);595596    listen(fd, 8);597    property_set_fd = fd;598}

创建一个socket用于进程间通信,对应的socket文件为/dev/socket/property_service,然后将该文件的文件描述符保存在property_set_fd中。这样当某个进程调用property_set来设置属性时,就会通过该socket给init进程发送一个消息,最终init会在poll中发现socket文件中有内容可读:

1131        nr = poll(ufds, fd_count, timeout);1132        if (nr <= 0)1133            continue;11341135        for (i = 0; i < fd_count; i++) {1136            if (ufds[i].revents == POLLIN) {1137                if (ufds[i].fd == get_property_set_fd())1138                    handle_property_set_fd();

在poll中如果描述符对应的文件有内容可读,则ufds[i].revents会被置为POLLIN。
接下来的调用流程为:
handle_property_set_fd() -> property_set()-> property_changed() -> queue_property_triggers()->action_add_queue_tail()
在queue_property_triggers函数中从全局action_list中匹配 property:=类型的Actions,如果属性数值满足,则将该Actions加入到action-queue中,这样该Actions中的command将会在之后的execute_one_command()被调用。

signal_init也是类似的流程
/system/core/init/init.c

799800static int signal_init_action(int nargs, char **args)801{802    signal_init();803    return 0;804}

/system/core/init/signal_handler.c

131void signal_init(void)132{133    int s[2];134135    struct sigaction act;136    memset(&act, 0, sizeof(act));137    act.sa_handler = sigchld_handler;138    act.sa_flags = SA_NOCLDSTOP;139    sigaction(SIGCHLD, &act, 0);140141    /* create a signalling mechanism for the sigchld handler */142    if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {143        signal_fd = s[0];144        signal_recv_fd = s[1];145        fcntl(s[0], F_SETFD, FD_CLOEXEC);146        fcntl(s[0], F_SETFL, O_NONBLOCK);147        fcntl(s[1], F_SETFD, FD_CLOEXEC);148        fcntl(s[1], F_SETFL, O_NONBLOCK);149    }150151    handle_signal();152}
36static void sigchld_handler(int s)37{38    write(signal_fd, &s, 1);39}

linux系统中子进程死亡时会向父进程发送一个SIGCHLD信号,sigaction(SIGCHLD, &act, 0)就是注册收到子进程SIGCHLD信号后的回调方法。所以每当有子进程终止时,系统就会回调sigchld_handler()函数。sigchld_handler()非常简单,就是是向signal_init()中创建的“socket对”里的signal_fd写数据,然后init.c在poll函数中就能通过“socket对”的另一个句柄signal_recv_fd得到所写的数据。然后调用handle_signal():

1131        nr = poll(ufds, fd_count, timeout);1132        if (nr <= 0)1133            continue;11341135        for (i = 0; i < fd_count; i++) {1136            if (ufds[i].revents == POLLIN) {1137                if (ufds[i].fd == get_property_set_fd())1138                    handle_property_set_fd();1139                else if (ufds[i].fd == get_keychord_fd())1140                    handle_keychord();1141                else if (ufds[i].fd == get_signal_fd())1142                    handle_signal();1143            }1144        }

/system/core/init/signal_handler.c

121void handle_signal(void)122{123    char tmp[32];124125    /* we got a SIGCHLD - reap and restart as needed */126    read(signal_recv_fd, tmp, sizeof(tmp));127    while (!wait_for_one_process(0))128        ;129}
44static int wait_for_one_process(int block)45{46    pid_t pid;47    int status;48    struct service *svc;49    struct socketinfo *si;50    time_t now;51    struct listnode *node;52    struct command *cmd;5354    while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );55    if (pid <= 0) return -1;56    INFO("waitpid returned pid %d, status = %08x\n", pid, status);5758    svc = service_find_by_pid(pid);59    if (!svc) {60        ERROR("untracked pid %d exited\n", pid);61        return 0;62    }6364    NOTICE("process '%s', pid %d exited\n", svc->name, pid);6566    if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {67        kill(-pid, SIGKILL);68        NOTICE("process '%s' killing any children in process group\n", svc->name);69    }7071    /* remove any sockets we may have created */72    for (si = svc->sockets; si; si = si->next) {73        char tmp[128];74        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);75        unlink(tmp);76    }7778    svc->pid = 0;79    svc->flags &= (~SVC_RUNNING);8081        /* oneshot processes go into the disabled state on exit,82         * except when manually restarted. */83    if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {84        svc->flags |= SVC_DISABLED;85    }8687        /* disabled and reset processes do not get restarted automatically */88    if (svc->flags & (SVC_DISABLED | SVC_RESET) )  {89        notify_service_state(svc->name, "stopped");90        return 0;91    }9293    now = gettime();94    if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {95        if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {96            if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {97                ERROR("critical process '%s' exited %d times in %d minutes; "98                      "rebooting into recovery mode\n", svc->name,99                      CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);100                android_reboot(ANDROID_RB_RESTART2, 0, "recovery");101                return 0;102            }103        } else {104            svc->time_crashed = now;105            svc->nr_crashed = 1;106        }107    }108109    svc->flags &= (~SVC_RESTART);110    svc->flags |= SVC_RESTARTING;111112    /* Execute all onrestart commands for this service. */113    list_for_each(node, &svc->onrestart.commands) {114        cmd = node_to_item(node, struct command, clist);115        cmd->func(cmd->nargs, cmd->args);116    }117    notify_service_state(svc->name, "restarting");118    return 0;119}

wait_for_one_process()中先找到挂掉的进程对应的service节点,并将该节点的flags添加SVC_RESTARTING标记,然后执行这个service节点中所有onrestart选项对应的动作。注意这里并没有直接重启service,而是在init.c->main()中的下一次for循环中,通过调用restart_processes()找到flags有SVC_RESTARTING标记的service重启他们。

本来想写的尽量详细,后来。。后来我就学乖了。。

参考博客:
http://blog.csdn.net/prife/article/details/41777245
http://blog.csdn.net/chenyufei1013/article/details/7927923

0 0
原创粉丝点击