Android 4.4 Init进程分析六 :属性服务

****************************************************************************

Android 4.4 init进程分析文章链接

  Android 4.4 Init进程分析一 :Android init进程概述

  Android 4.4 Init进程分析二 :Android初始化语言

  Android 4.4 Init进程分析三:init.rc脚本文件的解析

  Android 4.4 Init进程分析四 :init.rc脚本文件的执行

  Android 4.4 Init进程分析五 :进程的终止与再启动

  Android 4.4 Init进程分析六 :属性服务

***************************************************************************

 

1 前言

  1. 属性变更请求是init事件处理循环处理的另一个事件。

  2. 在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API。

  3. 属性又键(key)和值(value)构成,其表现形式为“键=值”。

  4. 系统中所有运行中的进程都可以访问属性值,但仅有init进程才能修改属性值,其他进程修改属性值时,必须向init进程提出请求,最终由init进程负责修改属性值。

  5. init进程修改属性值时,首先会先检查各属性的访问权限,而后再修改属性值。当属性值修改后,若定义在init.rc文件中的某个条件得到满足,则与此条件相匹配的动作就会发生。每个动作都有一个触发器(trigger),他决定动作的执行时间,记录在“on property”关键字后的命令即被执行。

  如下图,简单描述了init进程与其他进程在访问并修改属性值的大致情形:

 

 

2 属性初始化

  在init进程的main()函数中,调用了property_init()函数,用来初始化属性域,如下代码:

http://androidxref.com/4.4_r1/xref/system/core/init/init.c#1009

1 int main(int argc, char **argv)
2 {
3      ....
4     property_init();
5      ....  
6 }    

再看一下property_init()函数的定义:

http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#541

1 void property_init(void)
2 {
3     init_property_area();
4 }

可以看到函数内调用了init_property_area()函数,如下定义

http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#133

static int init_property_area(void)
{
    if (property_area_inited)
        return -1;

    if(__system_property_area_init())
        return -1;

    if(init_workspace(&pa_workspace, 0))
        return -1;

    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

    property_area_inited = 1;
    return 0;
}
View Code

这个函数会在内存中开辟一块共享内存区域,而后将其用作ashmem(Android Shared Memory),并生成属性域。外部进程可以访问这块共享内存区域,获得属性值,但它们不能通过直接访问共享内存区域的方式更改属性值。

init_property_area()函数执行后所创建的属性域被初始化。

在属性域完成初始化之后,就会从指定的文件中读取初始值,并设置为属性值。可以在main()函数中看到:

http://androidxref.com/4.4_r1/xref/system/core/init/init.c#1035

    if (!is_charger)
        property_load_boot_defaults();

property_load_boot_defaults()函数定义:

http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#546

void property_load_boot_defaults(void)
{
    load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
PROP_PATH_RAMDISK_DEFAULT定义为:
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop" 

load_properties_from_file()函数定义为:

 1 static void load_properties_from_file(const char *fn)
 2 {
 3     char *data;
 4     unsigned sz;
 5 
 6     data = read_file(fn, &sz);
 7 
 8     if(data != 0) {
 9         load_properties(data);
10         free(data);
11     }
12 }
View Code

load_properties_from_file()函数读取指定的property file内容到内存,然后调用load_properties():

http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#440

 1 static void load_properties(char *data)
 2 {
 3     char *key, *value, *eol, *sol, *tmp;
 4 
 5     sol = data;
 6     while((eol = strchr(sol, '\n'))) {
 7         key = sol;
 8         *eol++ = 0;
 9         sol = eol;
10 
11         value = strchr(key, '=');
12         if(value == 0) continue;
13         *value++ = 0;
14 
15         while(isspace(*key)) key++;
16         if(*key == '#') continue;
17         tmp = value - 2;
18         while((tmp > key) && isspace(*tmp)) *tmp-- = 0;
19 
20         while(isspace(*value)) value++;
21         tmp = eol - 2;
22         while((tmp > value) && isspace(*tmp)) *tmp-- = 0;
23 
24         property_set(key, value);
25     }
26 }
View Code

在load_properties()函数中分析得到key and value,并调用property_set(key, value);将属性值写入共享内存。

******************************************

再回到main()函数继续执行,可以看到如下语句:

http://androidxref.com/4.4_r1/xref/system/core/init/init.c#1064

1 queue_builtin_action(property_service_init_action, "property_service_init");

这个动作执行时,会去调用property_service_init_action()函数:

http://androidxref.com/4.4_r1/xref/system/core/init/init.c#789

 1 static int property_service_init_action(int nargs, char **args)
 2 {
 3     /* read any property files on system or data and
 4      * fire up the property service.  This must happen
 5      * after the ro.foo properties are set above so
 6      * that /data/local.prop cannot interfere with them.
 7      */
 8     start_property_service();
 9     return 0;
10 }

进而调用了start_property_service()函数。用于创建启动属性服务所需要的Unix域套接字,并保存套接字描述符,如下函数定义:

http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#581

 1 void start_property_service(void)
 2 {
 3     int fd;
 4 
 5     load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
 6     load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
 7     load_override_properties();
 8     /* Read persistent properties after all default values have been loaded. */
 9     load_persistent_properties();
10 
11     fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
12     if(fd < 0) return;
13     fcntl(fd, F_SETFD, FD_CLOEXEC);
14     fcntl(fd, F_SETFL, O_NONBLOCK);
15 
16     listen(fd, 8);
17     property_set_fd = fd;
18 }
View Code

start_property_service()函数在创建套接字之前,先读取存储在各个文件中的基本设置值,将它们设置为属性值,如上函数定义会读取以下文件:

1 //文件
2 #define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
3 #define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"
4 #define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"

当所有系统初始值设置完毕后,开始读取保存在/data/property目录中的属性值:

 1 load_persistent_properties();
 2 
 3 ==>>>
 4 
 5 static void load_persistent_properties()
 6 {
 7     DIR* dir = opendir(PERSISTENT_PROPERTY_DIR);
 8     ....
 9 }
10 
11 ==>>>
12 
13 #define PERSISTENT_PROPERTY_DIR  "/data/property"

在/data/property目录中保存着系统运行中其他进程新生成的属性值或更改的属性值,属性的key被用作文件名,value被保存在文件中。

 

在属性初始值设置完毕后,就会创建名称为  /dev/socket/property_service  的Unix域套接字。

上述过程我们大体可以总结如下图所示:

 

3 属性变更请求处理

3.1 其他进程请求变更属性值

 其他进程设置属性时是调用了libcutils函数库中的property_set()方法,该方法的定义去下:

http://androidxref.com/4.4_r1/xref/system/core/libcutils/properties.c#34

1 int property_set(const char *key, const char *value)
2 {
3     return __system_property_set(key, value);
4 }

可以看到,property_set调用了__system_property_set方法,这个方法位于:/bionic/libc/bionic/system_properties.c文件中:

http://androidxref.com/4.4_r1/xref/bionic/libc/bionic/system_properties.c#540

 1 int __system_property_set(const char *key, const char *value)
 2 {
 3     int err;
 4     prop_msg msg;
 5 
 6     if(key == 0) return -1;
 7     if(value == 0) value = "";
 8     if(strlen(key) >= PROP_NAME_MAX) return -1;
 9     if(strlen(value) >= PROP_VALUE_MAX) return -1;
10 
11     memset(&msg, 0, sizeof msg);
12     msg.cmd = PROP_MSG_SETPROP;
13     strlcpy(msg.name, key, sizeof msg.name);
14     strlcpy(msg.value, value, sizeof msg.value);
15 
16     err = send_prop_msg(&msg);
17     if(err < 0) {
18         return err;
19     }
20 
21     return 0;
22 }
View Code

上述方法最终调用到send_prop_msg()函数:

http://androidxref.com/4.4_r1/xref/bionic/libc/bionic/system_properties.c#send_prop_msg

 1 static int send_prop_msg(prop_msg *msg)
 2 {
 3     struct pollfd pollfds[1];
 4     struct sockaddr_un addr;
 5     socklen_t alen;
 6     size_t namelen;
 7     int s;
 8     int r;
 9     int result = -1;
10 
11     s = socket(AF_LOCAL, SOCK_STREAM, 0);
12     if(s < 0) {
13         return result;
14     }
15 
16     memset(&addr, 0, sizeof(addr));
17     namelen = strlen(property_service_socket);
18     strlcpy(addr.sun_path, property_service_socket, sizeof addr.sun_path);
19     addr.sun_family = AF_LOCAL;
20     alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;
21 
22     if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen)) < 0) {
23         close(s);
24         return result;
25     }
26 
27     r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0));
28 
29     if(r == sizeof(prop_msg)) {
30         // We successfully wrote to the property server but now we
31         // wait for the property server to finish its work.  It
32         // acknowledges its completion by closing the socket so we
33         // poll here (on nothing), waiting for the socket to close.
34         // If you 'adb shell setprop foo bar' you'll see the POLLHUP
35         // once the socket closes.  Out of paranoia we cap our poll
36         // at 250 ms.
37         pollfds[0].fd = s;
38         pollfds[0].events = 0;
39         r = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
40         if (r == 1 && (pollfds[0].revents & POLLHUP) != 0) {
41             result = 0;
42         } else {
43             // Ignore the timeout and treat it like a success anyway.
44             // The init process is single-threaded and its property
45             // service is sometimes slow to respond (perhaps it's off
46             // starting a child process or something) and thus this
47             // times out and the caller thinks it failed, even though
48             // it's still getting around to it.  So we fake it here,
49             // mostly for ctl.* properties, but we do try and wait 250
50             // ms so callers who do read-after-write can reliably see
51             // what they've written.  Most of the time.
52             // TODO: fix the system properties design.
53             result = 0;
54         }
55     }
56 
57     close(s);
58     return result;
59 }
View Code

send_prop_msg()函数可以看到:建立与属性服务/dev/socket/property_service 的socket连接,并发送要修改的属性信息。

3.2 init进程处理属性变更

  init进程就收到属性变更请求后,init进程就会调用handle_property_set_fd()函数:

 

 1 int main(int argc, char **argv)
 2 {  
 3     ....
 4       for (i = 0; i < fd_count; i++) {
 5             if (ufds[i].revents == POLLIN) {
 6                 if (ufds[i].fd == get_property_set_fd())
 7                     handle_property_set_fd();// 处理属性变更请求
 8                 else if (ufds[i].fd == get_keychord_fd())
 9                     handle_keychord();
10                 else if (ufds[i].fd == get_signal_fd())
11                     handle_signal();
12             }
13         }
14     ....
15 }

我们来看一下handle_property_set_fd()函数的定义:

http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#358

 1 void handle_property_set_fd()
 2 {
 3     ....
 4 
 5     /* Check socket options here */
 6     if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { //分析1
 7         close(s);
 8         ERROR("Unable to receive socket options\n");
 9         return;
10     }
11 
12     ....
13     switch(msg.cmd) {
14     case PROP_MSG_SETPROP:
15         msg.name[PROP_NAME_MAX-1] = 0;
16         msg.value[PROP_VALUE_MAX-1] = 0;
17         ...
18         if(memcmp(msg.name,"ctl.",4) == 0) {
19             // Keep the old close-socket-early behavior when handling
20             // ctl.* properties.
21             close(s);
22             if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) { //分析2
23                 handle_control_message((char*) msg.name + 4, (char*) msg.value);
24             } else {
25                 ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
26                         msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
27             }
28         } else {
29             if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) { //分析3
30                 property_set((char*) msg.name, (char*) msg.value); //分析4
31             } else {
32                 ERROR("sys_prop: permission denied uid:%d  name:%s\n",
33                       cr.uid, msg.name);
34             }
35 
36             // Note: bionic's property client code assumes that the
37             // property server will not close the socket until *AFTER*
38             // the property is written to memory.
39             close(s);
40         }
41     ...
42     }
43 }
View Code

分析1:执行handle_property_set_fd()函数时,会先从套接字获取SO_PEERCRED值,以便检查传递信息的进程的访问权限。在struct ucred结构体中,存储着传递信息的进程uid、pid和gid值。通过此结构体中的值,以及消息的类型,检查进程的访问权限。

分析2:在属性消息中,以“ctl”开头的消息并非请求更改系统属性值的消息,而是请求进程启动与终止的消息。在代码中调用check_control_perms()函数检查访问权限,仅有system server、root、以及相关进程才能使用ctl消息,终止或启动进程

分析3:其他消息都被用来更改系统属性值,调用check_perms()函数检查访问权限。各属性的访问权限采用Linux的uid进程区分,其定义如下代码所示。若在系统运行中变更属性设置,应充分考虑各属性的访问权限。

http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#60

 1 struct {
 2     const char *prefix;
 3     unsigned int uid;
 4     unsigned int gid;
 5 } property_perms[] = {
 6     { "net.rmnet0.",      AID_RADIO,    0 },
 7     { "net.gprs.",        AID_RADIO,    0 },
 8     { "net.ppp",          AID_RADIO,    0 },
 9     { "net.qmi",          AID_RADIO,    0 },
10     { "net.lte",          AID_RADIO,    0 },
11     { "net.cdma",         AID_RADIO,    0 },
12     { "ril.",             AID_RADIO,    0 },
13     { "gsm.",             AID_RADIO,    0 },
14     { "persist.radio",    AID_RADIO,    0 },
15     { "net.dns",          AID_RADIO,    0 },
16     { "sys.usb.config",   AID_RADIO,    0 },
17     { "net.",             AID_SYSTEM,   0 },
18     { "dev.",             AID_SYSTEM,   0 },
19     { "runtime.",         AID_SYSTEM,   0 },
20     { "hw.",              AID_SYSTEM,   0 },
21     { "sys.",             AID_SYSTEM,   0 },
22     { "sys.powerctl",     AID_SHELL,    0 },
23     { "service.",         AID_SYSTEM,   0 },
24     { "wlan.",            AID_SYSTEM,   0 },
25     { "bluetooth.",       AID_BLUETOOTH,   0 },
26     { "dhcp.",            AID_SYSTEM,   0 },
27     { "dhcp.",            AID_DHCP,     0 },
28     { "debug.",           AID_SYSTEM,   0 },
29     { "debug.",           AID_SHELL,    0 },
30     { "log.",             AID_SHELL,    0 },
31     { "service.adb.root", AID_SHELL,    0 },
32     { "service.adb.tcp.port", AID_SHELL,    0 },
33     { "persist.sys.",     AID_SYSTEM,   0 },
34     { "persist.service.", AID_SYSTEM,   0 },
35     { "persist.security.", AID_SYSTEM,   0 },
36     { "persist.service.bdroid.", AID_BLUETOOTH,   0 },
37     { "selinux."         , AID_SYSTEM,   0 },
38     { NULL, 0, 0 }
39 };
View Code

分析4:最后调用property_set()函数更改属性值。若没有问题,接着调用property_changed()函数

property_changed()函数的定义如下:

http://androidxref.com/4.4_r1/xref/system/core/init/init.c#412

1 void property_changed(const char *name, const char *value)
2 {
3     if (property_triggers_enabled)
4         queue_property_triggers(name, value);
5 }

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#517

 1 void queue_property_triggers(const char *name, const char *value)
 2 {
 3     struct listnode *node;
 4     struct action *act;
 5     list_for_each(node, &action_list) {
 6         act = node_to_item(node, struct action, alist);
 7         if (!strncmp(act->name, "property:", strlen("property:"))) {
 8             const char *test = act->name + strlen("property:");
 9             int name_length = strlen(name);
10 
11             if (!strncmp(name, test, name_length) &&
12                     test[name_length] == '=' &&
13                     (!strcmp(test + name_length + 1, value) ||
14                      !strcmp(test + name_length + 1, "*"))) {
15                 action_add_queue_tail(act);
16             }
17         }
18     }
19 }
View Code

queue_property_triggers()函数中,去action_list中检索出与这个属性值相关的action加入到action_queue,等待执行。

在init.rc脚本文件中,记录着某个属性改变后要采取的动作,动作执行的条件以“on property:<key> = <value>”形式给出。当某个条件相关的键值被设定后,与该条件相关的触发器就会被触发。比如:

# adbd on at boot in emulator
on property:ro.kernel.qemu=1
    start adbd

当ro.kernel.qemu属性值被设置为1时,adbd服务就会启动。

 

 到这里属性变更的处理就分析完了!

 大体过程如下:

 

 

 

 

 

========

88888888

========

posted on 2020-01-17 17:18  二的次方  阅读(528)  评论(0编辑  收藏  举报