《Android的设计与实现:卷I》——第3章 3.5触发并启动Action和Service

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本节书摘来自华章出版社《Android的设计与实现:卷I》——第3章,第3.5节。作者: 杨云君著.更多章节内容可以访问云栖社区“华章计算机”公众号查看。

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

define list_for_each(node, list) \

for (node = (list)->next; node != (list); node = 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。

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

define kw_func(kw) (keyword_info[kw].func)

这是个宏定义,需要找到keyword_info的定义。keyword_info位于init_parser.c中,其代码如下:

define KEYWORD(symbol, flags, nargs, func) \

[ 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 },

include "keywords.h"

};

undef KEYWORD

define kw_func(kw) (keyword_info[kw].func)

keyword_info中包含了keywords.h头文件,其代码如下:

ifndef KEYWORD

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);
//省略部分函数

define MAKE_KEYWORD_ENUM

define KEYWORD(symbol, flags, nargs, func) K_##symbol,

enum {
K_UNKNOWN,

endif

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)

ifdef MAKE_KEYWORD_ENUM

KEYWORD_COUNT,
};

undef MAKE_KEYWORD_ENUM

undef KEYWORD

endif

这里定义了所有Command对应的执行函数,执行Command就是执行这些函数。接下来,分别讲解Action和Service是如何执行的。

这里以init.rc中定义的early-init Action为例讲解Action的执行过程。early-init的定义如下:
on early-init
write /proc/1/oom_adj -16
start ueventd
write和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();

……
//触发属性服务相关的Action
queue_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_action
property_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);

ifdef ALLOW_LOCAL_PROP_OVERRIDE

load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);

endif

/*默认属性加载完毕后,加载一些持久化的属性。存储持久化属性的路径位
*于/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_action
queue_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 console
ro.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最后一个阶段:循环监听处理事件。

相关文章
|
6月前
|
Android开发
Android 11 添加Service服务SELinux问题
Android 11 添加Service服务SELinux问题
325 1
|
6月前
|
Android开发
Android基础知识:请解释Service是什么,它与IntentService的区别是什么?
Android基础知识:请解释Service是什么,它与IntentService的区别是什么?
108 0
|
6月前
|
XML Java Android开发
Android Studio App开发之服务Service的讲解及实战(包括启动和停止,绑定与解绑,推送服务到前台实现音乐播放器,附源码)
Android Studio App开发之服务Service的讲解及实战(包括启动和停止,绑定与解绑,推送服务到前台实现音乐播放器,附源码)
817 0
|
5月前
|
调度 Android开发
43. 【Android教程】服务:Service
43. 【Android教程】服务:Service
55 2
|
6月前
|
Android开发
Android Service Call /dev/xxx SELinux
Android Service Call /dev/xxx SELinux
106 1
|
2月前
|
Android开发
Android学习 —— 测试init.rc中的条件触发的处理顺序
Android学习 —— 测试init.rc中的条件触发的处理顺序
|
3月前
|
编解码 网络协议 Android开发
Android平台GB28181设备接入模块实现后台service按需回传摄像头数据到国标平台侧
我们在做Android平台GB28181设备对接模块的时候,遇到这样的技术需求,开发者希望能以后台服务的形式运行程序,国标平台侧没有视频回传请求的时候,仅保持信令链接,有发起视频回传请求或语音广播时,打开摄像头,并实时回传音视频数据或接收处理国标平台侧发过来的语音广播数据。
|
3月前
|
Android开发
Android项目架构设计问题之定义一个关闭当前页面的Action如何解决
Android项目架构设计问题之定义一个关闭当前页面的Action如何解决
17 0
|
4月前
|
存储 API Android开发
kotlin开发安卓app,使用webivew 触发 onShowFileChooser, 但只能触发一次,第二次无法触发,是怎么回事。 如何解决
在Android WebView开发中,`onShowFileChooser`方法用于开启文件选择。当用户只能选择一次文件可能是因为未正确处理选择回调。解决此问题需确保:1) 实现`WebChromeClient`并覆写`onShowFileChooser`;2) 用户选择文件后调用`ValueCallback.onReceiveValue`传递URI;3) 传递结果后将`ValueCallback`设为`null`以允许再次选择。下面是一个Kotlin示例,展示如何处理文件选择和结果回调。别忘了在Android 6.0+动态请求存储权限,以及在Android 10+处理分区存储。
|
4月前
|
测试技术 Android开发
Android中使用performClick触发点击事件
Android中使用performClick触发点击事件
下一篇
无影云桌面