1 前言

什么是init.rc文件?

import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.trace.rcon early-init# Set init and its forked children's oom_adj.write /proc/1/oom_adj -16# Set the security context for the init process.# This should occur before anything else (e.g. ueventd) is started.setcon u:r:init:s0start ueventd# create mountpointsmkdir /mnt 0775 root systemon initsysclktz 0loglevel 3......on fs
# mount mtd partitions# Mount /system rw first to give the filesystem a chance to save a checkpointmount yaffs2 mtd@system /systemmount yaffs2 mtd@system /system ro remountmount yaffs2 mtd@userdata /data nosuid nodevmount yaffs2 mtd@cache /cache nosuid nodevon post-fs......on post-fs-data......on boot.......
on nonencryptedclass_start late_starton chargerclass_start chargeron property:vold.decrypt=trigger_reset_mainclass_reset main......
service servicemanager /system/bin/servicemanagerclass coreuser systemgroup systemcriticalonrestart restart zygoteonrestart restart mediaonrestart restart surfaceflingeronrestart restart drmservice vold /system/bin/voldclass coresocket vold stream 0660 root mountioprio be 2service netd /system/bin/netdclass mainsocket netd stream 0660 root systemsocket dnsproxyd stream 0660 root inetsocket mdns stream 0660 root systemservice debuggerd /system/bin/debuggerd......

上面的就是一个init.rc的片段。可以在Android源代码中找到,位置在/system/core/rootdir/
init.rcAndroid系统/init程序读取的初始化配置文件,用于启动Android中的各种服务,以及配置系统。

init.rc分析

文件使用的脚本格式被称作“Android Init Language”(AIL)
AIL的解析以由空格(Whitespace)分隔的token组成的行为基本单位,这些行由四种类型组成,——ActionCommandServiceOption

如果一行最开始是"#",那么这一行是注释。

/initmain函数中如下调用函数解析该文件:

INFO("reading config file\n");
init_parse_config_file("/init.rc");

函数init_parse_config_file(/system/core/init/init_parser.c)

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

该函数过程如下:

  • 首先调用函数read_file(在/system/core/init/util.c中),把/init.rc配置文件读到data^data变量中,同时确保文件以“\n \0”结尾
  • 最后,也是最关键的部分,调用parse_config函数进行配置文件分析。

为了更好的了解/init.rc的配置究竟是什么,我们必须仔细分析parse_config函数。

函数parse_config(/system/core/init/init_parser.c)

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];//用于存储每一行的token字符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 (kw_is(kw, SECTION)) {state.parse_line(&state, 0, 0);parse_new_section(&state, kw, nargs, args);} else {state.parse_line(&state, nargs, args);}nargs = 0;}break;case T_TEXT:if (nargs < INIT_PARSER_MAXARGS) {args[nargs++] = state.text;}break;}}parser_done: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);......}
}

该函数有两个重要的数据结构
struct parse_state定义(/system/core/init/parser.h

struct parse_state
{char *ptr;//文本的指针char *text;//指向解析的单个tokenint line;//行号int nexttoken;void *context;//指向不同的数据结构,表示当前正在解析Action或者Service/*处理行的函数,可以证明init.rc的解析是以行为单位的*/void (*parse_line)(struct parse_state *state, int nargs, char **args);const char *filename;//当前的文件名void *priv;
};

有点编译原理基础的就会明白,这就是parser的状态。
还有

char *args[INIT_PARSER_MAXARGS];
int nargs;

在以行单位的解析中,把每一行解析的token字符串指针存放在args字符指针数组中,把当前行中的token个数存放在nargs中。

在函数parse_config中通过switch结构进行状态的转换。各种状态如下。

状态 解释
T_EOF 代表文件的结束
T_NEWLINE 代表新的一行,所以需要解析当前行
T_TEXT 代表一个token,把指针放到对应的args,并更新nargs

重点分析状态T_NEWLINE.

 case T_NEWLINE:state.line++;if (nargs) {int kw = lookup_keyword(args[0]);if (kw_is(kw, SECTION)) {state.parse_line(&state, 0, 0);parse_new_section(&state, kw, nargs, args);} else {state.parse_line(&state, nargs, args);}nargs = 0;}break;

首先通过lookup_keyworkdkw_is函数,查找本行的第一个token是什么类型的,如果是Section,就调用parse_new_section,否则调用行处理函数。

疑问来了,state.parse_line不是在开始赋值为parse_line_no_op的空函数了吗?调用空函数有什么意义啊?
其实,秘密就在parse_new_section函数中。我们一步一步来解释。 首先解决什么是Section,这就需要分析lookup_keywordkw_is函数了。

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;......break;case 'd':if (!strcmp(s, "isabled")) return K_disabled;if (!strcmp(s, "omainname")) return K_domainname;break;.......return K_UNKNOWN;
}

该函数根据字符串返回一一对应的枚举类型。kw_is是宏:

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

keyword_info是全局变量定义如下:(/system/core/init/keywords.h

#define KEYWORD(symbol, flags, nargs, func) \[ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
struct {const char *name;int (*func)(int nargs, char **args);unsigned char nargs;unsigned char flags;
} keyword_info[KEYWORD_COUNT] = {[ K_UNKNOWN ] = { "unknown", 0, 0, 0 },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(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, 1, do_setsebool)KEYWORD(socket,      OPTION,  0, 0)KEYWORD(start,       COMMAND, 1, do_start)KEYWORD(stop,        COMMAND, 1, do_stop)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)
};

关键字importonservice代表新的Section的开始。 所以init.rc文件的结构如下:

  • 最高层由Section组成,分为triggerimportservice,分别以onimport,service关键字开头。
  • importSection只有一行,至于载入其他rc文件
  • triggerSection由 Command组成。
  • serviceSection有 Option组成。

接下来我们直击核心函数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;
}

在该函数中可以明显看到Section的种类,以及state.parse_line被更改。接下来我们按Section种类,分三部分分析。
但是在这之前先介绍三个数据结构:

static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);

这三个全局变量都是链表的表头,是/init/init.rc解析所要操作的关键函数,也可以说是解析的目的所在。service_list代表解析得到的Serviceaction_list代表解析得到的Actionaction_queue代表将要执行的Action队列。
/init可以认为主要是做了如下工作:

  1. 解析/init.rc,把得到的Action和 Service连接到action_listservice_list中。
  2. 内部或者外部出发trigger把对应的Action连接到action_queue
  3. for循环中依次执行action_queue队列中Action对应的Command

Section Serivice

每个Service,由一个struct service数据结构代表,定义如下:

struct service {/* list of all services */struct listnode slist;//连接到service_listconst char *name;const char *classname;unsigned flags;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;#ifdef HAVE_SELINUXchar *seclabel;
#endifstruct socketinfo *sockets;struct svcenvinfo *envvars;struct action onrestart;  /* Actions to execute on restart. *//* keycodes for triggering this service via /dev/keychord */int *keycodes;int nkeycodes;int keychord_id;int ioprio_class;int ioprio_pri;int nargs;/* "MUST BE AT THE END OF THE STRUCT" */char *args[1];
}; /*     ^-------'args' MUST be at the end of thi

函数parse_service(/system/core/init/init_parser.c)

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

首先本Section的第一行必须是如下格式

service <service name> <program name>

而且在valid_name函数中规定,service name必须不超过16个字符,而且只能由字母、数字、“-”、“_”组成。

当出现重名的service时,会被忽略。

最后把Service挂到service_list尾部

下面分析parse_line_service函数

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 = lookup_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, "ioprio 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|idle> <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:nargs--;args++;kw = lookup_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: { /* name value */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: {/* name type perm [ uid gid ] */struct socketinfo *si;if (nargs < 4) {parse_error(state, "socket option requires name, type, perm arguments\n");break;}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_seclabel:
#ifdef HAVE_SELINUXif (nargs != 2) {parse_error(state, "seclabel option requires a label string\n");} else {svc->seclabel = args[1];}
#endifbreak;default:parse_error(state, "invalid option '%s'\n", args[0]);}
}

该函数就是对ServiceOption进行解析,并把相应的struct service的字段赋值。

ServicesOption是服务的修饰符,可以影响服务如何以及怎样运行。服务支持的选项如下:

1. critical

表明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入 Recovery (恢复)模式。

2. disabled

表明这个服务不会同与他同trigger (触发器)下的服务自动启动。该服务必须被明确的按名启动。必须通过start <service name>Command,class_start <class_name>Command不能启动即使该服务在

3.  setenv <name> <value>

在进程启动时将环境变量设置为。

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

创建一个unix域的名为/dev/socket/ 的套接字,并传递它的文件描述符给已启动的进程。 必须是 "dgram","stream" 或"seqpacket"。用户和组默认是0。

5.  user <username>

在启动这个服务前改变该服务的用户名。此时默认为 root。

6.  group <groupname> [<groupname> ]*

在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups函数),档案默认是root。

7.  oneshot

服务退出时不重启。

8.  class <name>

指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认为"default"类服务。

9. onrestart

当服务重启,执行一个命令。

Section Action

每个Action以及包含的Command由如下数据结构表示:

struct command
{/* list of commands in an action */struct listnode clist;int (*func)(int nargs, char **args);int nargs;char *args[1];
};struct action {/* node in list of all actions */struct listnode alist;//连接到action_list/* node in the queue of pending actions */struct listnode qlist;//连接到action_queue/* node in list of actions for a trigger */struct listnode tlist;unsigned hash;const char *name;struct listnode commands;//包含command的链表表头struct command *current;
};

函数parse_action(/system/core/init/init_parser.c)

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_add_tail(&action_list, &act->alist);/* XXX add to hash */return act;
}

首先本Section的第一行必须是如下格式

on <trigger name>

最后把Action挂到action_list尾部。可以看到似乎打算把Action加到hash表中但是还没有实现。

下面分析parse_line_action函数

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 = lookup_keyword(args[0]);if (!kw_is(kw, COMMAND)) {parse_error(state, "invalid command '%s'\n", args[0]);return;}n = kw_nargs(kw);if (nargs < n) {parse_error(state, "%s requires %d %s\n", args[0], n - 1,n > 2 ? "arguments" : "argument");return;}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);
}

Actions后需要跟若干个命令,这些命令如下:

1.  exec <path> [<argument> ]*

创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。由于它不是内置命令,应尽量避免使用exec ,它可能会引起init执行超时。

2.  export <name> <value>

在全局环境中将<name>变量的值设为<value>。(这将会被所有在这命令之后运行的进程所继承)

3.  ifup <interface>

启动网络接口

4.  import <filename>

指定要解析的其他配置文件。常被用于当前配置文件的扩展

5.  hostname <name>

设置主机名

6.  chdir <directory>

改变工作目录

7.  chmod <octal-mode><path>

改变文件的访问权限

8.  chown <owner><group> <path>

更改文件的所有者和组

9.  chroot <directory>

改变处理根目录

10.  class_start<serviceclass>

启动所有指定服务类下的未运行服务。

11  class_stop<serviceclass>

停止指定服务类下的所有已运行的服务。

12.  domainname <name>

设置域名

13.  insmod <path>

加载path指定的驱动模块

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

创建一个目录<path> ,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和 root组。

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

试图在目录<dir>挂载指定的设备。<device> 可以是mtd@name的形式指定一个mtd块设备。<mountoption>包括 "ro"、"rw"、"re

16.  setkey

保留,暂时未用

17.  setprop <name><value>

将系统属性<name>的值设为<value>

18. setrlimit <resource> <cur> <max>

设置<resource>的rlimit (资源限制)

19.  start <service>

启动指定服务(如果此服务还未运行)。

20.stop<service>

停止指定服务(如果此服务在运行中)。

21. symlink <target> <path>

创建一个指向<path>的软连接<target>

22. sysclktz <mins_west_of_gmt>

设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)

23.  trigger <event>

触发一个事件。用于Action排队

24.  wait <path> [<timeout> ]

等待一个文件是否存在,当文件存在时立即返回,或到指定的超时时间后返回,如果不指定,默认超时时间是5秒。

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

<path>指定的文件写入一个或多个字符串。

Section Import

每个Import由如下数据结构表示:

struct import {struct listnode list;const char *filename;
};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 handling 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);
}

函数expand_props把配置文件中的${<property_name>},通过Property System读取对应的值。通常要读取ro.machinero.arch的值,展开后形成真正的文件名,然后挂载在state.priv上。

parse_config函数的末尾,有如下代码:

parser_done: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);

实现了对import进的文件的解析。

Android init.rc分析相关推荐

  1. Android init.rc文件格式解析

    /****************************************************************************** Android init.rc文件格式解 ...

  2. Android init.rc文件解析过程详解(三)

    Android init.rc文件解析过程详解(三) 三.相关结构体 1.listnode listnode结构体用于建立双向链表,这种结构广泛用于kernel代码中, android源代码中定义了l ...

  3. Android init.rc 服务启动不成功

    Android init.rc 服务启动不成功 问题 在开发过程中发现一个问题,我们需要在开机的时候判断硬件版本号去启动服务, 服务的名字是ledservice和ledservice4,但是发现每次烧 ...

  4. 安卓 linux init.rc,[原创]Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  5. Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  6. Android init.rc文件解析过程详解(一)

        Android init.rc文件解析过程详解(一) 一.init.rc文件结构介绍 init.rc文件基本组成单位是section, section分为三种类型,分别由三个关键字(所谓关键字 ...

  7. android init.rc中启动的service 默认是disable的,后续如何启动此服务

    如果 android init.rc中启动的service 默认是disable的,如何才能启动此服务呢? init.rc中可以直接启动service 附带的参数决定启动程序的状态,例如数据业务中配置 ...

  8. Android 系统(242)---Android init.rc执行顺序

    Android init.rc执行顺序 1. 所有的action运行于service之前 2.  下面为各个section的执行顺序,英文编号的section是系统内建的(写死在init.c中的命令) ...

  9. Android程序暂停sh,init进程 解析Android启动脚本init.rc 修改它使不启动android init.rc中启动一个sh文件...

    Android启动后,系统执行的第一个进程是一个名称为init 的可执行程序.提供了以下的功能:设备管理.解析启动脚本.执行基本的功能.启动各种服务.代码的路径: system/core/init,编 ...

最新文章

  1. untitled与前端——初学
  2. 20返回指针的函数与指向函数的指针
  3. Android的MediaPlayer
  4. Python函数中apply、map、applymap的区别
  5. 滑动验证前端代码实现
  6. fat32 linux 打包工具_11款最棒的Linux数据恢复工具发布啦!
  7. 为什么私有GIT服务器上无法查看上传的代码?
  8. Nature 子刊重磅:腾讯与钟南山团队发布新冠危重症 AI 预测模型 Cox
  9. Remository3.52简体中文语言包
  10. java linux driver,JAVA:使用GeckoDriver在Linux上运行Selenium测试:驱动程序不可执行
  11. FileZilla Server与FileZilla Client
  12. 南京大学LANDS组
  13. 谷歌浏览器设置默认,但是打不开外部超链接/点击超链接没反应
  14. Oracle 工具篇+Oracle Remote Diagnostic Agent(RDA)
  15. 小马哥robofly四轴代码解读:PWM电机输出
  16. centos7利用docker 快速搭建苹果CMS站点
  17. 数组统计问题(统计各学生的优秀率及格率)C语言
  18. 学习《华为基本法》(5):经营重心
  19. [后端开发]Http请求413错误解决方法
  20. 阿里巴巴的“旺信”上线

热门文章

  1. poj2392 Space Elevator
  2. 珍惜那些爱你的人,不要令他们失望,加油吧,我们。
  3. Golang 库学习笔记 Gin(二)
  4. 吴恩达 coursera AI 第四课总结+作业答案
  5. Windows 下安装Pytorch
  6. c 运算符重载总结
  7. [云炬创业基础笔记]第十章企业的利润计划测试3
  8. 利用阿里云自定义镜像实现服务器数据/网站快速迁移
  9. 干货 | 谈谈我是如何入门这场 AI 大赛的
  10. tf.nn.conv2d() / tf.nn.depthwise_conv2d() 和 Batchsize效益