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 chartrigger, 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中,其代码如下:
原来list_for_each是一个宏,代表一个for循环。node_to_item的代码如下:
(container ) (((char) (node)) - offsetof(container, member))node_to_item又是一个宏,第二个参数接受一个container标识的参数,这个参数将由一个数据类型替换,所以才能在代码中直接传入类型struct action。
这里涉及C语言中一个非常关键的宏定义:offsetof。这个宏利用了结构体中成员偏移量是固定的这个特性,用于求结构体中某个成员在该结构体中的偏移量。其定义在/bionic/libc/kernel/common/linux/stddef.h文件中,代码如下:
下面详细分析这个宏定义。
(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。
3.5.2 执行Action
上一节分析了Action的触发,那么Action又是在哪里被执行的呢?定位到execute_one_command函数,其位于init.c中,代码如下:void execute_one_command(void){ int ret; /从Action中取出Command/ if (!cur_action || !cur_command ||
is_last_command(cur_action, cur_command)) { cur_action = action_remove_queue_head(); cur_command = NULL; if (!cur_action) return; cur_command = get_first_command(cur_action);} else {
cur_command = get_next_command(cur_action, cur_command);} /调用Command中定义的func函数,执行Command/ ret = cur_command->func(cur_command->nargs, cur_command->args);}execute_one_command函数做了两部分工作:取命令和执行命令的func函数。这里的func便是command结构体中的成员函数func,这个函数是在parse_line_action解析Action的时候赋值的,代码如下: cmd->func = kw_func(kw);接下来定位到kw_func,分析这里都赋值了哪些函数。kw_func位于init_parser.c中,其代码如下:
这是个宏定义,需要找到keyword_info的定义。keyword_info位于init_parser.c中,其代码如下:
[ 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_info中包含了keywords.h头文件,其代码如下:
int do_chroot(int nargs, charargs);int do_chdir(int nargs, charargs);int do_class_start(int nargs, charargs);int do_class_stop(int nargs, charargs);//省略部分函数
enum { K_UNKNOWN,
KEYWORD(capability, OPTION, 0, 0) KEYWORD(chdir, COMMAND, 1, do_chdir) KEYWORD(chroot, COMMAND, 1, do_chroot) KEYWORD(class, OPTION, 0, 0) //省略部分KEYWORD KEYWORD(exec, COMMAND, 1, do_exec) KEYWORD(on, SECTION, 0, 0) KEYWORD(oneshot, OPTION, 0, 0) KEYWORD(onrestart, OPTION, 0, 0) //省略部分KEYWORD KEYWORD(ioprio, OPTION, 0, 0)
KEYWORD_COUNT,};
这里定义了所有Command对应的执行函数,执行Command就是执行这些函数。接下来,分别讲解Action和Service是如何执行的。
这里以init.rc中定义的early-init Action为例讲解Action的执行过程。early-init的定义如下:on early-init write /proc/1/oom_adj -16 start ueventdwrite和start都是Command,在KEYWORD映射表中分别对应可执行函数do_write和do_start。do_write定义在builtins.c中,定位到do_write函数体,其代码如下:int do_write(int nargs, charargs){
const charpath = args[1]; const charvalue = args[2];……//省略部分内容/write_file最终调用了open、write、close库函数往path里写入value/
return write_file(path, value);}write命令最终调用了基本的函数库,写入命令指定的参数。接下来定位到do_start函数体,代码如下:int do_start(int nargs, charargs){ struct service svc; //do_start是用于启动Service的 svc = service_find_by_name(args[1]); if (svc) {
/启动Service,这个Service就是ueventd/ service_start(svc, NULL); } return 0;}找到了service_start,距离真相就不远了。下面继续分析service_start函数,其位于init.c中,代码如下:void service_start(struct service svc, const char dynamic_args){
struct stat s; pid_t pid; int needs_console; int n; //Service启动前需要清除异常状态 svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET)); svc->time_started = 0; ……//省略部分内容 /调用fork创建子进程,fork函数调用一次,但会返回两次,分别返回子进程和父进程。其中 返回0表示在子进程中;返回大于0的数字表示在父进程中,这个大于0的数字便是子进程的进程I*/ pid = fork(); if (pid == 0) {//返回0,表示在子进程中 struct socketinfosi; struct svcenvinfo ei; char tmp[32]; int fd, sz; /将属性信息添加到环境变量中/ if (properties_inited()) { get_property_workspace(&fd, &sz); add_environment("ANDROID_PROPERTY_WORKSPACE", tmp); } for (ei = svc->envvars; ei; ei = ei->next) add_environment(ei->name, ei->value); /创建Socket,并在环境变量中设置Socket信息/ for (si = svc->sockets; si; si = si->next) { int socket_type = ( !strcmp(si->type, "stream") ? SOCK_STREAM : (!strcmp(si->type, "dgram") ? SOCK_DGRAM :SOCK_SEQPACKET)); int s = create_socket(si->name, socket_type, si->perm, si->uid, si->gid); if (s >= 0) { publish_socket(si->name, s); } }……//省略部分内容 /根据参数值,调用Linux系统函数execve执行Service对应的命令,这里是ueventd/
if (!dynamic_args) { if (execve(svc->args[0], (char) svc->args,(char) ENV) < 0) { ERROR("cannot execve(‘%s’): %s\n", svc->args[0], strerror(errno)); } } else { ……//省略部分内容 } /以下是父进程,设置了Service的启动信息,并更新Service的属性状态,属性系统下节介绍/ svc->time_started = gettime(); svc->pid = pid; svc->flags |= SVC_RUNNING; if (properties_inited()) notify_service_state(svc->name, "running");}到这里early-init这个Action就分析完了。从这个Action可以看出,有一部分Command是以Service的方式执行的,这部分Service并不是以service关键字显式声明的。那么显式声明的Service又是如何启动的呢?
3.5.3 启动Service
从init.c的main函数中只看到了Action的触发和执行,似乎并没有找到Service启动的痕迹。这个不难,在上一节分析early-init的时候出现了service_start函数,它是专门用来启动Service的,只需要找出谁调用了它,就能找到Service是在哪里启动的。定位到service_start_if_not_disabled函数,其位于builtins.c中,代码如下:static void service_start_if_not_disabled(struct service svc){
if (!(svc->flags & SVC_DISABLED)) { service_start(svc, NULL); }}service_start_if_not_disabled也是根据传入的service结构体调用service_start的,这不是最终目标。继续定位service_start_if_not_disabled的调用者,找到do_class_start函数,其依然位于builtins.c中,代码如下:int do_class_start(int nargs, charargs){
service_for_each_class(args[1], service_start_if_not_disabled); return 0;}这个函数不就是keywords.h中定义的class_start这个Command所对应的函数吗? keywords.h中的定义如下:
KEYWORD(class_start, COMMAND, 1, do_class_start)原来只要运行了class_start 这个Command,就会启动相应的Service。所以,接下来,只需要在init.rc中找到哪里执行了class_start这个Command,就知道Service是在哪里启动了。init.rc中的很多Action都执行了class_start这个Command,选择在启动阶段触发并且在init.rc中配置过的Action。其中Trigger为boot的Action满足条件,其代码如下:on boot ……//省略其他Command /core和main是Service的分类名,这里将启动所有在
Option中配置了class core和class main的Service/ class_start core class_start main原来在init的main方法中,执行到action_for_each_trigger("boot", action_add_queue_tail),在触发boot Action的过程中,将要启动的Service与Command关联起来的。可见,init是把Service作为一个进程,用Command启动的,这样所有Service便是init的子进程了。这些由init启动的Service主要有:ueventd、servicemanager、vold、zygote、installd、ril-daemon、debuggerd、bootanim(显示开机动画)等,通常称这些Service为守护进程服务(Daemon Service)。到这里Action和Service的启动就分析完了,下面分析init对属性服务的处理。
3.5.4 init对属性服务的处理
init中除了解析init.rc中配置的Action和Service外,还处理了一些内置Action,这些工作由queue_builtin_action函数完成。其中最重要的便是属性服务(Property Service)相关的部分。1.处理属性服务的流程Android为了存储全局系统设置信息,提供了一个系统属性共享内存区,这个共享内存区的内容是一些键值对的列表,对外提供get和set方法读写属性。系统启动时由init初始化并开启属性服务。现在回到init.c的main函数,分析init中是如何处理属性服务的。定位到属性服务相关部分,代码如下://共享内存区分配property_init();……if (!is_charger)
//加载默认属性 property_load_boot_defaults();……//触发属性服务相关的Actionqueue_builtin_action(property_service_init_action, "property_service_init");queue_builtin_action(queue_property_triggers_action,"queue_propety_triggers");init中与属性服务相关的工作有四部分:1)通过property_init函数调用init_property_area()函数初始化属性区,打开ashmem设备,申请共享内存,以便所有用户进程可以共享这块内存。2)通过property_load_boot_defaults函数加载/default.prop文件中定义的默认属性。3)通过queue_builtin_action函数触发property_service_init。4)通过queue_builtin_action函数触发queue_propety_triggers。其中第一部分涉及Android Shared Memory,读者只需要知道分配了一块共享内存区域便可;第二部分只是简单的文件加载。下面只分析第三部分和第四部分。(1)property_service_init_actionproperty_service_init_action是init执行的第一个属性触发函数,位于init.c中,在其内部直接调用了start_property_service函数,该函数定义于property_service.c中,代码如下:void start_property_service(void){
int fd; /*这里加载了其他默认属性文件 * #define PROP_PATH_SYSTEM_BUILD "/system/build.prop" * #define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop" * #define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop" */ load_properties_from_file(PROP_PATH_SYSTEM_BUILD); load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
/*默认属性加载完毕后,加载一些持久化的属性。存储持久化属性的路径位 *于/data/property目录下,由PERSISTENT_PROPERTY_DIR宏定义 *#define PERSISTENT_PROPERTY_DIR "/data/property"持久化属性,是以persist.开头的属性/load_persistent_properties();/ #define PROP_SERVICE_NAME "property_service"创建一个Socket,用于接收客户端请求/ fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0); if(fd < 0) return; fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, O_NONBLOCK);/*监听fd上的连接请求,并建立一个请求队列,最大请求数是8这些连接请求将在请求队列中等待被accept()方法接收/listen(fd, 8); /设置property_set_fd以便在init中处理。还记得init中poll函数在等待这个fd上的事件发生吗/property_set_fd = fd; }start_property_service方法中主要做了三部分工作:1)加载属性文件,2)创建Socket接收客户端请求,3)监听Socket。(2)queue_property_triggers_actionqueue_property_triggers_action是init执行的第二个属性触发函数,位于init.c中,代码如下:static int queue_property_triggers_action(int nargs, char **args){queue_all_property_triggers();property_triggers_enabled = 1;//为property_triggers_enabled赋值return 0;}这里调用了queue_all_property_triggers,位于init_parser.c中,代码如下:void queue_all_property_triggers(){
struct listnode node; struct action act; /遍历action_list/ list_for_each(node, &action_list) { /取出一个Action/ act = node_to_item(node, struct action, alist); /判断Action名字中是否有property:,解析property:<name>=<value> / if (!strncmp(act->name, "property:", strlen("property:"))) { const char* name = act->name + strlen("property:"); const char* equals = strchr(name, '='); if (equals) { ……//省略部分内容 if (length > PROP_NAME_MAX) {//错误处理 } else { ……//省略部分内容 value = property_get(prop_name); if (value && (!strcmp(equals + 1, value) ||!strcmp(equals + 1, "*"))) { //将这些Action加入可执行队列 action_add_queue_tail(act); }……//省略部分内容}queue_property_triggers_action触发了所有名字以“property:”开头的Action。Android的属性系统是一种特殊的Action,这种Action以“on property:”为前缀,其代码如下:on property:ro.debuggable=1 start consolero.debuggable=1定义了一个条件,只有当这个条件为真时,才执行Action中指定的Command。
2.属性服务客户端
前面分析到start_property_service中开启了一个Socket接收客户端请求,这个请求又是从哪里发出的?即属性服务的客户端是什么?在属性设置过程中,属性服务器调用了property_set函数设置属性。其实在客户端也有一个对应的名为 property_set的函数,这个函数供客户端与属性服务通信,位于/system/core/libcutils/properties.c,代码如下:int property_set(const char key, const char value){
return __system_property_set(key, value);}这里调用了__system_property_set函数,位于bionic/libc/bionic/system_properties.c中,代码如下:int __system_property_set(const char key, const char value){
int err; int tries = 0; int update_seen = 0; prop_msg msg; ……//省略部分内容 /send_prop_msg中建立了s = socket(AF_LOCAL, SOCK_STREAM, 0)/ err = send_prop_msg(&msg); ……//省略部分内容 return 0;}接着分析send_prop_msg,在这里真正创建了Socket通信连接。代码如下:static int send_prop_msg(prop_msg msg){
struct pollfd pollfds[1]; struct sockaddr_un addr; socklen_t alen; size_t namelen; int s; int r; int result = -1; /创建Socket/ s = socket(AF_LOCAL, SOCK_STREAM, 0); ……//省略部分内容 memset(&addr, 0, sizeof(addr)); namelen = strlen(property_service_socket); /设置服务Socket/ strlcpy(addr.sun_path, property_service_socket, sizeof addr.sun_path); addr.sun_family = AF_LOCAL; alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1; /连接服务Socket/ if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen) < 0)) { close(s); return result; } /发送消息/ r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0)); if(r == sizeof(prop_msg)) { ……//省略部分内容 } close(s); return result;}可见Android的属性系统是通过Socket实现客户端和服务端通信的,通信的接口是property_set和property_get这两个函数。到这里为止,属性系统的三大部分分析完了。接下来分析init最后一个阶段:循环监听处理事件。