您的位置:首页 > 移动开发 > Android开发

Android的init过程详解(三)

2013-10-28 15:53 399 查看
解析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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: