Android的init过程详解(三)
2013-10-28 15:53
399 查看
解析Service(1)
1.parse_service
解析Service先从parse_service开始,代码如下:
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。其源码如下:
service_list声明了一个双向链表,存储了前向和后向指针。
(2)list_init和list_add_tail
list_init和list_add_tail的实现代码位于/system/core/libcutils/list.c中,提供了基本的双向链表操作。list_init的源码如下:
list_add_tail的源码如下:
list_add_tail只是将item加入到双向链表的尾部。
注意 Android借鉴了Linux内核中常用的链表实现方法。把链表的指针部分和数据部分分离。
首先定义了node结构体:
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中,代码如下:
可见,Service需要填充的内容很多,parse_service函数只是初始化了Service的基本信息,详细信息需要由parse_line_service填充。
2.parse_line_service
parse_line_service的源码如下:
到这里Service就解析完了,接着分析Action的解析过程。
3.4.5 解析Action
1.parse_action
解析Action首先从parse_action函数开始,代码如下:
从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中,代码如下:
2.parse_line_action
熟悉了Action的存储形式,接着分析Action的解析过程。定位到parse_line_action函数,该函数位于init_parser.c中,代码如下:
parse_line_action函数的执行过程很清晰,要比parse_line_service简单很多。
这里涉及一个重要的数据类型struct command。command结构体定义在/system/core/init/init.h中,代码如下:
至此,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中,代码如下:
list_for_each和node_to_item到底做了些什么?node_to_item第二个参数struct action又是什么?这两部分定义在list.h中,其代码如下:
原来list_for_each是一个宏,代表一个for循环。node_to_item的代码如下:
node_to_item又是一个宏,第二个参数接受一个container标识的参数,这个参数将由一个数据类型替换,所以才能在代码中直接传入类型struct action。
这里涉及C语言中一个非常关键的宏定义:offsetof。这个宏利用了结构体中成员偏移量是固定的这个特性,用于求结构体中某个成员在该结构体中的偏移量。其定义在
下面详细分析这个宏定义。
(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)替换为如下代码:
(char*) (node)是按照char*格式读取node的值, node中便是alist的地址。然后将offsetof(struct action, alist)替换为如下代码:
这里得到了alist在action中的偏移量。(((char*) (node)) - offsetof(struct action, alist))便得到了这个node对应的Action的地址,最后告诉编译器以(struct action *)格式读取这个地址,这样便得到了node所在的Action,找到了node对应的数据。
接下来分析action_add_queue_tail中做了什么。代码如下:
action_add_queue_tail中只是把Action中的qlist放入了action_queue中。找到action_queue的声明,发现它与service_list和action_list一样,都是由list_declare声明的宏。代码如下:
queue_builtin_action的执行过程与action_for_each_trigger类似,最后也是调用了action_add_queue_tail和list_add_tail方法,这里不再具体分析。
看来action_for_each_trigger和queue_builtin_action都没有实际执行Service和Action。
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。
相关文章推荐
- Android init.rc文件解析过程详解(三)
- [原创]Android init.rc文件解析过程详解(二)
- Android的init过程详解(二)初始化语言(init.rc)解析
- Android init.rc文件解析过程详解(一)
- Android init.rc文件解析过程详解(一)
- Android init.rc文件解析过程详解(二)
- Android的init过程详解(一)
- Android的init过程详解(一)
- Android的init过程详解
- Android init.rc文件解析过程详解(三)
- android启动过程详解(一)——解析init.rc
- Android的init过程详解(一)
- Android的init过程详解(二)
- Android的init过程详解(一)
- Android的init过程详解(一)
- Android 的init过程详解
- Android init.rc文件解析过程详解(一)
- Android的init过程详解(一)
- Android的init过程详解(一)
- Android的init过程详解(一) 推荐