解析Service(1)
1.parse_service
解析Service先从parse_service开始,代码如下:
- static void *parse_service(struct parse_state *state,int nargs, char **args)
- {
- struct service *svc;//service结构体,用于保存当前解析出的Service
- ……//省略错误处理代码
- nargs -= 2;
- /*为Service分配存储空间*/
- svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
- /*用解析到的内容构造service结构体*/
- svc->name = args[1];
- svc->classname = "default";
- memcpy(svc->args, args + 2, sizeof(char*) * nargs);
- svc->args[nargs] = 0;
- svc->nargsnargs = nargs;
- svc->onrestart.name = "onrestart";
- /*初始化Service中restart Option的Commands链表,然后
- *将Servic的节点slist放入service_list双向链表*/
- list_init(&svc->onrestart.commands);
- /*将Service节点的指针部分放入service_list中*/
- list_add_tail(&service_list, &svc->slist);
- return svc;
- }
parse_service函数主要做了三项工作:1)为新建的Service分配存储空间,2)初始化Service,
3)将Service放入一个service_list链表。其中涉及几个重要的数据类型和函数:service_list、list_init和list_add_tail,以及service结构体。
(1)service_list
service_list由list_declare定义,list_declare实际上是一个宏,位于/system/core/include/cutils/list.h。其源码如下:
- #define list_declare(name) \
- struct listnode name = { \
- .next = &name, \
- .prev = &name, \
- }
service_list声明了一个双向链表,存储了前向和后向指针。
(2)list_init和list_add_tail
list_init和list_add_tail的实现代码位于/system/core/libcutils/list.c中,提供了基本的双向链表操作。list_init的源码如下:
- void list_init(struct listnode *node)
- {
- node->next = node;
- node->prev = node;
- }
list_add_tail的源码如下:
- void list_add_tail(struct listnode *head, struct listnode *item)
- {
- item->next = head;
- item->prev = head->prev;
- head->prev->next = item;
- head->prev = item;
- }
list_add_tail只是将item加入到双向链表的尾部。
注意 Android借鉴了Linux内核中常用的链表实现方法。把链表的指针部分和数据部分分离。
首先定义了node结构体:
- struct listnode
- {
- struct listnode *next;
- struct listnode *prev;
- };
3.4.4 解析Service(2)
将链表的前向指针和后项指针放入这个struct listnode的结构中。当需要处理不同数据节点时,就把这个listnode嵌入不同的数据节点中。这样操作链表就是操作这个listnode,与具体的数据无关。如parse_service函数中,list_add_tail(&service_list, &svc->slist);便是将Service节点的listnode指针部分放入service_list链表。当需要操作listnode对应的数据时,就可通过成员偏移量找到对应的数据,这部分以后分析。
(3)service结构体
parse_service中最重要的一个数据类型便是service,它存储了Service这个Section的内容。service结构体定义在/system/core/init/init.h中,代码如下:
- struct service {
- /* list of all services */
- struct listnode slist;
- const char *name; //Service的名字
- const char *classname; //Service的分类名
- unsigned flags; //Service的属性标志
- pid_t pid; //Service的进程号
- time_t time_started; //上次启动时间
- time_t time_crashed; //上次异常退出的时间
- int nr_crashed; //异常退出的次数
- uid_t uid; //用户ID
- gid_t gid; //组ID
- gid_t supp_gids[NR_SVC_SUPP_GIDS];
- size_t nr_supp_gids;
- struct socketinfo *sockets; //Service使用的Socket
- struct svcenvinfo *esnvvars; //Service使用的环境变量
- /*Service重启时要执行的Action。这里其实是由关键字onrestart声明的Option。由于onrestart
- *声明的Option后面的参数是Command,而Action就是一个Command序列,所以这里用Action代替*/
- struct action onrestart;
- /*触发该 service 的组合键,通过/dev/keychord获取 */
- int *keycodes;
- int nkeycodes;
- int keychord_id;
- /*IO优先级,与IO调度有关*/
- int ioprio_class;
- int ioprio_pri;
- /*参数个数*/
- int nargs;
- /*参数名*/
- char *args[1];
- }; /*args 必须位于结构体末端 */
可见,Service需要填充的内容很多,parse_service函数只是初始化了Service的基本信息,详细信息需要由parse_line_service填充。
2.parse_line_service
parse_line_service的源码如下:
- static void parse_line_service(struct parse_state *state, int nargs, char **args)
- {
- /* 从state的context变量中取出刚才创建的Service */
- struct service *svc = state->context;
- struct command *cmd;
- int i, kw, kw_nargs;
- if (nargs == 0) {
- return;
- }
- svc->ioprio_class = IoSchedClass_NONE;
- /* 根据lookup_keyword函数匹配关键字信息,这次匹配的是Service对应的Option关键字*/
- kw = lookup_keyword(args[0]);
- switch (kw) {
- case K_class:
- if (nargs != 2) {
- ……//省略错误处理内容
- }else {
- svc->classname = args[1];
- }
- break;
- ……//省略部分case语句
- case K_onrestart:
- nargs--;
- args++;
- kw = lookup_keyword(args[0]);
- ……//省略部分内容
- /*这里对应onrestart Option的Command创建过程,也是调用了list_add_tail函数操作双向链表*/
- cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
- cmd->func = kw_func(kw);
- cmd->nargsnargs = nargs;
- memcpy(cmd->args, args, sizeof(char*) * nargs);
- list_add_tail(&svc->onrestart.commands, &cmd->clist);
- break;
- ……//省略部分case语句
- case K_socket: {/* name type perm [ uid gid ] */
- struct socketinfo *si;
- ……//省略部分内容
- /*以下是解析Socket,有些服务需要使用Socket,socketinfo描述Socket的信息*/
- si = calloc(1, sizeof(*si));
- si->name = args[1];//以下设置了Socket的基本信息
- ……
- break;
- }
- ……//省略部分case语句
- default: //只支持固定的关键字,否则出错
- parse_error(state, "invalid option '%s'\n", args[0]);
- }
- }
到这里Service就解析完了,接着分析Action的解析过程。
3.4.5 解析Action
1.parse_action
解析Action首先从parse_action函数开始,代码如下:
- static void *parse_action(struct parse_state *state, int nargs, char **args)
- {
- struct action *act;
- ……//省略错误处理内容
- act = calloc(1, sizeof(*act));
- act->name = args[1];
- list_init(&act->commands);
- /*将Action的指针节点放入action_list中*/
- list_add_tail(&action_list, &act->alist);
- return act;
- }
从parse_action函数的代码可以看出,解析Action的过程与解析Service的过程十分相似。首先给新创建的Action分配存储空间,然后将Action的指针节点放入一个action_list列表中。这里又涉及两个重要的数据类型:action结构体和action_list链表。
action_list与service_list都是由list_declare宏声明,即static list_declare(action_list)。
action结构体定义在/system/core/init/init.h中,代码如下:
- struct action {
- /*这个指针节点所在的链表存储了所有Action的指针节点 */
- struct listnode alist;
- /*这个指针节点所在的链表存储了所有即将执行的Action的指针节点*/
- struct listnode qlist;
- /*这个指针节点所在的链表存储了所有要触发的Action的指针节点*/
- struct listnode tlist;
- unsigned hash;
- const char *name;
- /*Action对应的Command*/
- struct listnode commands;
- struct command *current;
- };
2.parse_line_action
熟悉了Action的存储形式,接着分析Action的解析过程。定位到parse_line_action函数,该函数位于init_parser.c中,代码如下:
- static void parse_line_action(struct parse_state* state, int nargs, char **args)
- {
- struct command *cmd;
- /*通过state结构体得到当前Action的引用*/
- struct action *act = state->context;
- int (*func)(int nargs, char **args);
- int kw, n;
- /*依然是根据关键字匹配,不过这次匹配的是Command */
- kw = lookup_keyword(args[0]);
- n = kw_nargs(kw);
- ……//省略错误处理内容
- cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
- cmd->func = kw_func(kw);//获取Command对应的指令函数
- cmd->nargsnargs = nargs;
- memcpy(cmd->args, args, sizeof(char*) * nargs);
- /*将Command加入Action的Command列表*/
- list_add_tail(&act->commands, &cmd->clist);
parse_line_action函数的执行过程很清晰,要比parse_line_service简单很多。
这里涉及一个重要的数据类型struct command。command结构体定义在/system/core/init/init.h中,代码如下:
- struct command
- {
- /* list of commands in an action */
- struct listnode clist;
- /* command对应的执行函数*/
- int (*func)(int nargs, char **args);
- int nargs;
- char *args[1];
- };
至此,init.rc的解析过程便告一段落。接下来开始分析Action和Service的执行阶段。
3.5 触发并启动Action和Service
init解析init.rc后,生成了存放Service和Action的链表。那么init又是如何控制这些Action和Service的呢?本节将详细分析这部分内容。
3.5.1 触发Action
init解析完init.rc后,接着执行了action_for_each_trigger和queue_builtin_action。这两个函数做了些什么呢?
首先定位到action_for_each_trigger,其实现代码位于init_parser.c中,代码如下:
- void action_for_each_trigger(const char *trigger, void (*func)(struct action *act))
- {
- struct listnode *node;
- struct action *act;
- /*一个怪异的函数调用,特别是node_to_item的第二个参数*/
- list_for_each(node, &action_list) {
- act = node_to_item(node, struct action, alist);
- if (!strcmp(act->name, trigger)) {
- func(act);//执行了传入的func函数
- }
- }
- }
list_for_each和node_to_item到底做了些什么?node_to_item第二个参数struct action又是什么?这两部分定义在list.h中,其代码如下:
- #define list_for_each(node, list) \
- for (node = (list)->next; node != (list); nodenode = node->next)
原来list_for_each是一个宏,代表一个for循环。node_to_item的代码如下:
- #define node_to_item(node, container, member) \
- (container *) (((char*) (node)) - offsetof(container, member))
node_to_item又是一个宏,第二个参数接受一个container标识的参数,这个参数将由一个数据类型替换,所以才能在代码中直接传入类型struct action。
这里涉及C语言中一个非常关键的宏定义:offsetof。这个宏利用了结构体中成员偏移量是固定的这个特性,用于求结构体中某个成员在该结构体中的偏移量。其定义在
- /bionic/libc/kernel/common/linux/stddef.h文件中,代码如下:
- #ifdef __compiler_offsetof
- #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
- #else
- #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
- #endif
下面详细分析这个宏定义。
(TYPE *)0是将0强制转换为TYPE型指针。告诉编译器有一个指向TYPE类型的指针,这个指针的地址值是0。当然这都是欺骗编译器的,因为不需要操作这个0地址,不会出错。如果定义ptr = (TYPE *)0,ptr是指向TYPE类型的指针,它的基地址值就是0。那么
ptr->MEMBER就是MEMBER这个元素了,&(ptr->MEMBER)就是MENBER的地址。既然基地址为0,这样MEMBER的地址便是MEMBER在TYPE中的偏移量。最后把结果强制转换为size_t(size_t其实是unsigned int)就得到了MEMBER的偏移量。分析完了offsetof,再回到action_for_each_trigger 函数。将node_to_item(node, struct action, alist)替换为如下代码:
- (struct action *) (((char*) (node)) - offsetof(struct action, alist))
(char*) (node)是按照char*格式读取node的值, node中便是alist的地址。然后将offsetof(struct action, alist)替换为如下代码:
- ((size_t) &(( struct action *)0)-> alist)
这里得到了alist在action中的偏移量。(((char*) (node)) - offsetof(struct action, alist))便得到了这个node对应的Action的地址,最后告诉编译器以(struct action *)格式读取这个地址,这样便得到了node所在的Action,找到了node对应的数据。
接下来分析action_add_queue_tail中做了什么。代码如下:
- void action_add_queue_tail(struct action *act)
- {
- list_add_tail(&action_queue, &act->qlist);
- }
action_add_queue_tail中只是把Action中的qlist放入了action_queue中。找到action_queue的声明,发现它与service_list和action_list一样,都是由list_declare声明的宏。代码如下:
- static list_declare(action_queue);
queue_builtin_action的执行过程与action_for_each_trigger类似,最后也是调用了action_add_queue_tail和list_add_tail方法,这里不再具体分析。
看来action_for_each_trigger和queue_builtin_action都没有实际执行Service和Action。