Android的init过程详解(三)

解析Service(1)

1.parse_service

解析Service先从parse_service开始,代码如下:

  
  
  
  
  1. static void *parse_service(struct parse_state *state,int nargs, char **args)  
  2. {  
  3.   struct service *svc;//service结构体,用于保存当前解析出的Service  
  4.   ……//省略错误处理代码  
  5.   nargs -2;  
  6.   /*为Service分配存储空间*/  
  7.   svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);  
  8.   /*用解析到的内容构造service结构体*/  
  9.   svc->name = args[1];  
  10.   svc->classname = "default";  
  11.   memcpy(svc->args, args + 2, sizeof(char*) * nargs);  
  12.   svc->args[nargs] = 0;  
  13.   svc->nargsnargs = nargs;  
  14.   svc->onrestart.name = "onrestart";  
  15.   /*初始化Service中restart Option的Commands链表,然后  
  16.    *将Servic的节点slist放入service_list双向链表*/  
  17.   list_init(&svc->onrestart.commands);  
  18.   /*将Service节点的指针部分放入service_list中*/  
  19.   list_add_tail(&service_list, &svc->slist);  
  20.   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。其源码如下:

  
  
  
  
  1. #define list_declare(name) \  
  2. struct listnode name = { \  
  3.    .next = &name, \  
  4.    .prev = &name, \  

service_list声明了一个双向链表,存储了前向和后向指针。

(2)list_init和list_add_tail

list_init和list_add_tail的实现代码位于/system/core/libcutils/list.c中,提供了基本的双向链表操作。list_init的源码如下:

  
  
  
  
  1. void list_init(struct listnode *node)  
  2. {  
  3.    node->next = node;  
  4.    node->prev = node;  

list_add_tail的源码如下:

  
  
  
  
  1. void list_add_tail(struct listnode *head, struct listnode *item)  
  2. {  
  3.    item->next = head;  
  4.    item->prev = head->prev;  
  5.    head->prev->next = item;  
  6.    head->prev = item;  

list_add_tail只是将item加入到双向链表的尾部。

注意 Android借鉴了Linux内核中常用的链表实现方法。把链表的指针部分和数据部分分离。

首先定义了node结构体:

  
  
  
  
  1. struct listnode  
  2. {  
  3. struct listnode *next;  
  4. struct listnode *prev;  
  5. };  

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中,代码如下:

  
  
  
  
  1. struct service {  
  2.    /* list of all services */  
  3.    struct listnode slist;  
  4.    const char *name;  //Service的名字  
  5.    const char *classname;  //Service的分类名  
  6.    unsigned flags;   //Service的属性标志  
  7.    pid_t pid;   //Service的进程号  
  8.    time_t time_started;  //上次启动时间  
  9.    time_t time_crashed;  //上次异常退出的时间  
  10.    int nr_crashed;   //异常退出的次数  
  11.    uid_t uid;   //用户ID  
  12.    gid_t gid;   //组ID  
  13.    gid_t supp_gids[NR_SVC_SUPP_GIDS];  
  14.    size_t nr_supp_gids;  
  15.    struct socketinfo *sockets;  //Service使用的Socket  
  16.    struct svcenvinfo *esnvvars;  //Service使用的环境变量  
  17.    /*Service重启时要执行的Action。这里其实是由关键字onrestart声明的Option。由于onrestart  
  18. *声明的Option后面的参数是Command,而Action就是一个Command序列,所以这里用Action代替*/  
  19. struct action onrestart;  
  20. /*触发该 service 的组合键,通过/dev/keychord获取 */  
  21. int *keycodes;  
  22. int nkeycodes;  
  23. int keychord_id;  
  24. /*IO优先级,与IO调度有关*/  
  25. int ioprio_class;  
  26. int ioprio_pri;  
  27. /*参数个数*/  
  28. int nargs;  
  29. /*参数名*/  
  30. char *args[1];  
  31. }; /*args 必须位于结构体末端 */ 

可见,Service需要填充的内容很多,parse_service函数只是初始化了Service的基本信息,详细信息需要由parse_line_service填充。

2.parse_line_service

parse_line_service的源码如下:

  
  
  
  
  1. static void parse_line_service(struct parse_state *state, int nargs, char **args)  
  2. {  
  3. /* 从state的context变量中取出刚才创建的Service */  
  4. struct service *svc = state->context;  
  5. struct command *cmd;  
  6. int i, kw, kw_nargs;  
  7. if (nargs == 0) {  
  8.    return;  
  9. }  
  10. svc->ioprio_class = IoSchedClass_NONE;  
  11. /* 根据lookup_keyword函数匹配关键字信息,这次匹配的是Service对应的Option关键字*/  
  12. kw = lookup_keyword(args[0]);  
  13. switch (kw) {  
  14. case K_class:  
  15.    if (nargs != 2) {  
  16.       ……//省略错误处理内容  
  17.      }else {  
  18.       svc->classname = args[1];  
  19.     }  
  20.     break;  
  21. ……//省略部分case语句  
  22. case K_onrestart:  
  23.    nargs--;  
  24.    args++;  
  25.    kw = lookup_keyword(args[0]);  
  26.    ……//省略部分内容  
  27.   /*这里对应onrestart Option的Command创建过程,也是调用了list_add_tail函数操作双向链表*/  
  28.   cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);  
  29.   cmd->func = kw_func(kw);  
  30.   cmd->nargsnargs = nargs;  
  31.   memcpy(cmd->args, args, sizeof(char*) * nargs);  
  32.   list_add_tail(&svc->onrestart.commands, &cmd->clist);  
  33.   break;  
  34.  ……//省略部分case语句  
  35.  case K_socket: {/* name type perm [ uid gid ] */  
  36.     struct socketinfo *si;  
  37.     ……//省略部分内容  
  38.  /*以下是解析Socket,有些服务需要使用Socket,socketinfo描述Socket的信息*/  
  39.  si = calloc(1, sizeof(*si));  
  40.  si->name = args[1];//以下设置了Socket的基本信息  
  41.  ……  
  42.  break;  
  43.   }  
  44.   ……//省略部分case语句  
  45.   default: //只支持固定的关键字,否则出错  
  46. parse_error(state, "invalid option '%s'\n", args[0]);  
  47.   }  

到这里Service就解析完了,接着分析Action的解析过程。
 

3.4.5 解析Action

1.parse_action

解析Action首先从parse_action函数开始,代码如下:

  
  
  
  
  1. static void *parse_action(struct parse_state *state, int nargs, char **args)  
  2.  
  3. {  
  4.    struct action *act;  
  5.    ……//省略错误处理内容  
  6.    act = calloc(1, sizeof(*act));  
  7.    act->name = args[1];  
  8.    list_init(&act->commands);  
  9.    /*将Action的指针节点放入action_list中*/  
  10.    list_add_tail(&action_list, &act->alist);  
  11.    return act;  
  12. }  

从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中,代码如下:

  
  
  
  
  1. struct action {  
  2.    /*这个指针节点所在的链表存储了所有Action的指针节点 */  
  3.    struct listnode alist;  
  4.    /*这个指针节点所在的链表存储了所有即将执行的Action的指针节点*/  
  5.    struct listnode qlist;  
  6.    /*这个指针节点所在的链表存储了所有要触发的Action的指针节点*/  
  7.    struct listnode tlist;  
  8.    unsigned hash;  
  9.    const char *name;  
  10.    /*Action对应的Command*/  
  11.    struct listnode commands;  
  12.    struct command *current;  
  13. }; 

2.parse_line_action

熟悉了Action的存储形式,接着分析Action的解析过程。定位到parse_line_action函数,该函数位于init_parser.c中,代码如下:

  
  
  
  
  1. static void parse_line_action(struct parse_state* state, int nargs, char **args)  
  2. {  
  3. struct command *cmd;  
  4. /*通过state结构体得到当前Action的引用*/  
  5. struct action *act = state->context;  
  6. int (*func)(int nargs, char **args);  
  7. int kw, n;  
  8. /*依然是根据关键字匹配,不过这次匹配的是Command */  
  9. kw = lookup_keyword(args[0]);  
  10. n = kw_nargs(kw);  
  11. ……//省略错误处理内容  
  12. cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);  
  13. cmd->func = kw_func(kw);//获取Command对应的指令函数  
  14. cmd->nargsnargs = nargs;  
  15. memcpy(cmd->args, args, sizeof(char*) * nargs);  
  16. /*将Command加入Action的Command列表*/  
  17. list_add_tail(&act->commands, &cmd->clist); 

parse_line_action函数的执行过程很清晰,要比parse_line_service简单很多。

这里涉及一个重要的数据类型struct command。command结构体定义在/system/core/init/init.h中,代码如下:

  
  
  
  
  1. struct command  
  2. {  
  3.    /* list of commands in an action */  
  4.    struct listnode clist;  
  5.    /* command对应的执行函数*/  
  6.    int (*func)(int nargs, char **args);  
  7.    int nargs;  
  8.    char *args[1];  
  9. }; 

至此,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中,代码如下:

  
  
  
  
  1. void action_for_each_trigger(const char *trigger, void (*func)(struct action *act))  
  2. {  
  3. struct listnode *node;  
  4. struct action *act;  
  5. /*一个怪异的函数调用,特别是node_to_item的第二个参数*/  
  6. list_for_each(node, &action_list) {  
  7.     act = node_to_item(node, struct action, alist);  
  8.     if (!strcmp(act->name, trigger)) {  
  9.        func(act);//执行了传入的func函数  
  10.     }  
  11. }  

list_for_each和node_to_item到底做了些什么?node_to_item第二个参数struct action又是什么?这两部分定义在list.h中,其代码如下:

  
  
  
  
  1. #define list_for_each(node, list) \  
  2. for (node = (list)->next; node != (list); nodenode = node->next) 

原来list_for_each是一个宏,代表一个for循环。node_to_item的代码如下:

  
  
  
  
  1. #define node_to_item(node, container, member) \  
  2.    (container *) (((char*) (node)) - offsetof(container, member)) 


node_to_item又是一个宏,第二个参数接受一个container标识的参数,这个参数将由一个数据类型替换,所以才能在代码中直接传入类型struct action。

这里涉及C语言中一个非常关键的宏定义:offsetof。这个宏利用了结构体中成员偏移量是固定的这个特性,用于求结构体中某个成员在该结构体中的偏移量。其定义在

  
  
  
  
  1. /bionic/libc/kernel/common/linux/stddef.h文件中,代码如下:  
  2. #ifdef __compiler_offsetof  
  3. #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)  
  4. #else  
  5. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)  
  6. #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)替换为如下代码:

  
  
  
  
  1. (struct action *) (((char*) (node)) - offsetof(struct action, alist)) 

(char*) (node)是按照char*格式读取node的值, node中便是alist的地址。然后将offsetof(struct action, alist)替换为如下代码:
 

  
  
  
  
  1. ((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中做了什么。代码如下:

  
  
  
  
  1. void action_add_queue_tail(struct action *act)  
  2. {  
  3.  
  4. list_add_tail(&action_queue, &act->qlist);  
  5. }  

action_add_queue_tail中只是把Action中的qlist放入了action_queue中。找到action_queue的声明,发现它与service_list和action_list一样,都是由list_declare声明的宏。代码如下:

  
  
  
  
  1. 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过程详解(三))