深入讲解Android Property机制
Posted 悠然红茶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入讲解Android Property机制相关的知识,希望对你有一定的参考价值。
深入讲解android Property机制
侯亮
1 概述
Android系统(本文以Android 4.4为准)的属性(Property)机制有点儿类似Windows系统的注册表,其中的每个属性被组织成简单的键值对(key/value)供外界使用。
我们可以通过在adb shell里敲入getprop命令来获取当前系统的所有属性内容,而且,我们还可以敲入类似“getprop 属性名”的命令来获取特定属性的值。另外,设置属性值的方法也很简单,只需敲入“setprop 属性名 新值”命令即可。
可是问题在于我们不想只认识到这个层次,我们希望了解更多一些Property机制的运作机理,而这才是本文关心的重点。
说白了,Property机制的运作机理可以汇总成以下几句话:
1) 系统一启动就会从若干属性脚本文件中加载属性内容;
2) 系统中的所有属性(key/value)会存入同一块共享内存中;
3) 系统中的各个进程会将这块共享内存映射到自己的内存空间,这样就可以直接读取属性内容了;
4) 系统中只有一个实体可以设置、修改属性值,它就是属性服务(Property Service);
5) 不同进程只可以通过socket方式,向属性服务发出修改属性值的请求,而不能直接修改属性值;
6) 共享内存中的键值内容会以一种字典树的形式进行组织。
Property机制的示意图如下:
2 Property Service
2.1 init进程里的Property Service
Property Service实体其实是在init进程里启动的。我们知道,init是Linux系统中用户空间的第一个进程。它负责创建系统中最关键的几个子进程,比如zygote等等。在本节中,我们主要关心init进程是如何启动Property Service的。
我们查看core/init/Init.c文件,可以看到init进程的main()函数,它里面和property相关的关键动作有:
1)间接调用__system_property_area_init():打开属性共享内存,并记入__system_property_area变量;
2)间接调用init_workspace():只读打开属性共享内存,并记入环境变量;
3)根据init.rc,异步激发property_service_init_action(),该函数中会:
l 加载若干属性文本文件,将具体属性、属性值记入属性共享内存;
l 创建并监听socket;
4)根据init.rc,异步激发queue_property_triggers_action(),将刚刚加载的属性对应的激发动作,推入action列表。
main()中的调用关系如下:
2.1.1 初始化属性共享内存
我们可以看到,在init进程的main()函数里,辗转打开了一个内存文件“/dev/__properties__”,并把它设定为128KB大小,接着调用mmap()将这块内存映射到init进程空间了。这个内存的首地址被记录在__system_property_area__全局变量里,以后每添加或修改一个属性,都会基于这个__system_property_area__变量来计算位置。
初始化属性内存块时,为什么要两次open那个/dev/__properties__文件呢?我想原因是这样的:第一次open的句柄,最终是给属性服务自己用的,所以需要有读写权限;而第二次open的句柄,会被记入pa_workspace.fd,并在合适时机添加进环境变量,供其他进程使用,因此只能具有读取权限。
第一次open时,执行的代码如下:
fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
传给open()的参数标识里指明了O_RDWR,表示用“读写方式”打开文件。另外O_NOFOLLOW标识主要是为了防止我们打开“符号链接”,不过我们知道,__properties__文件并不是符号链接,所以当然可以成功open。O_CLOEXEC标识是为了保证一种独占性,也就是说当init进程打开这个文件时,此时就算其他进程也open这个文件,也会在调用exec执行新程序时自动关闭该文件句柄。O_EXCL标识和O_CREATE标识配合起来,表示如果文件不存在,则创建之,而如果文件已经存在,那么open就会失败。第一次open动作后,会给__system_property_area__赋值,然后程序会立即close刚打开的句柄。
第二次open动作发生在接下来的init_workspace()函数里。此时会再一次打开__properties__文件,这次却是以只读模式打开的:
int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);
打开的句柄记录在pa_workspace.fd处,以后每当init进程执行socket命令,并调用service_start()时,会执行类似下面的句子:
get_property_workspace(&fd, &sz); // 读取pa_workspace.fd
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
说白了就是把
pa_workspace.fd
的句柄记入一个名叫“
ANDROID_PROPERTY_WORKSPACE
”的环境变量去。
【system/core/init/Init.c】
/* add_environment - add "key=value" to the current environment */
int add_environment(const char *key, const char *val)
int n;
for (n = 0; n < 31; n++)
if (!ENV[n])
size_t len = strlen(key) + strlen(val) + 2;
char *entry = malloc(len);
snprintf(entry, len, "%s=%s", key, val);
ENV[n] = entry;
return 0;
return 1;
这个环境变量在日后有可能被其他进程拿来用,从而将属性内存区映射到自己的内存空间去,这个后文会细说。
接下来,main()函数在设置好属性内存块之后,会调用queue_builtin_action()函数向内部的action_list列表添加action节点。关于这部分的详情,可参考其他讲述Android启动机制的文档,这里不再赘述。我们只需知道,后续,系统会在合适时机回调“由queue_builtin_action()的参数”所指定的property_service_init_action()函数就可以了。
2.1.2 初始化属性服务
property_service_init_action()函数只是在简单调用start_property_service()而已,后者的代码如下:
【core/init/Property_service.c】
void start_property_service(void)
int fd;
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
/* Read vendor-specific property runtime overrides. */
vendor_load_properties();
load_override_properties();
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
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);
listen(fd, 8);
property_set_fd = fd;
其主要动作无非是加载若干属性文件,然后创建并监听一个socket接口。
2.1.2.1 加载属性文本文件
start_property_service()函数首先会调用load_properties_from_file()函数,尝试加载一些属性脚本文件,并将其中的内容写入属性内存块里。从代码里可以看到,主要加载的文件有:
l /system/build.prop
l /system/default.prop(该文件不一定存在)
l /data/local.prop
l /data/property目录里的若干脚本
load_properties_from_file()函数的代码如下:
【core/init/Property_service.c】
static void load_properties_from_file(const char *fn)
char *data;
unsigned sz;
data = read_file(fn, &sz);
if(data != 0)
load_properties(data);
free(data);
其中调用的read_file()函数很简单,只是把文件内容的所有字节读入一个buffer,并在内容最后添加两个字节:’\\n’和0。
接着调用的load_properties()函数,会逐行分析传来的buffer,解析出行内的key、value部分,并调用property_set(),将key、value设置进系统的属性共享内存去。
我们绘制出property_service_init_action()函数的调用关系图,如下:
2.1.2.2 创建socket接口
在加载动作完成后,start_property_service ()会创建一个socket接口,并监听这个接口。
【core/init/Property_service.c】
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);
listen(fd, 8);
property_set_fd = fd;
这个socket是专门用来监听其他进程发来的“修改”属性值的命令的,它被设置成“非阻塞”(O_NONBLOCK)的socket。
2.1.3 初始化属性后的触发动作
既然在上一小节的property_service_init_action()动作中,系统已经把必要的属性都加载好了,那么现在就可以遍历刚生成的action_list,看看哪个刚加载好的属性可以进一步触发连锁动作。这就是init进程里为什么有两次和属性相关的queue_builtin_action()的原因。
【system/core/init/Init.c】
static int queue_property_triggers_action(int nargs, char **args)
queue_all_property_triggers();
/* enable property triggers */
property_triggers_enabled = 1;
return 0;
【system/core/init/Init_parser.c】
void queue_all_property_triggers()
struct listnode *node;
struct action *act;
list_for_each(node, &action_list)
act = node_to_item(node, struct action, alist);
if (!strncmp(act->name, "property:", strlen("property:")))
/* parse property name and value
syntax is property:<name>=<value> */
const char* name = act->name + strlen("property:");
const char* equals = strchr(name, '=');
if (equals)
char prop_name[PROP_NAME_MAX + 1];
char value[PROP_VALUE_MAX];
int length = equals - name;
if (length > PROP_NAME_MAX)
ERROR("property name too long in trigger %s", act->name);
else
memcpy(prop_name, name, length);
prop_name[length] = 0;
/* does the property exist, and match the trigger value? */
property_get(prop_name, value);
if (!strcmp(equals + 1, value) ||!strcmp(equals + 1, "*"))
action_add_queue_tail(act);
这段代码是说,当获取的属性名和属性值,与当初init.rc里记录的某action的激发条件匹配时,就把该action插入执行队列的尾部(action_add_queue_tail(act))。
2.2 init进程循环监听socket
现在再回过头看init进程,其main()函数的最后,我们可以看到一个for(;;)循环,不断监听外界发来的命令,包括设置属性的命令。
【system/core/init/Init.c】
for(;;)
. . . . . .
. . . . . .
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
for (i = 0; i < fd_count; i++)
if (ufds[i].revents == POLLIN)
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
2.2.1 处理“ctl.”命令
当从socket收到“设置属性”的命令后,会调用上面的handle_property_set_fd()函数,代码截选如下:
【core/init/Property_service.c】
void handle_property_set_fd()
prop_msg msg;
. . . . . .
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0)
return;
. . . . . .
switch(msg.cmd)
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
. . . . . .
if(memcmp(msg.name,"ctl.",4) == 0)
. . . . . .
if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx))
handle_control_message((char*) msg.name + 4, (char*) msg.value);
else
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
else
if (check_perms(msg.name, cr.uid, cr.gid, source_ctx))
property_set((char*) msg.name, (char*) msg.value);
else
ERROR("sys_prop: permission denied uid:%d name:%s\\n",
cr.uid, msg.name);
. . . . . .
close(s);
. . . . . .
break;
. . . . . .
<span style="font-family:宋体;margin: 0px; padding: 0px;"></span>
看到了吗?设置属性时,一开始就把属性名和属性值的长度都限制了。
#define PROP_NAME_MAX 32
#define PROP_VALUE_MAX 92
也就是说,有意义的部分的最大字节数分别为31字节和91字节,最后一个字节先被强制设为0了。
2.2.1.1 check_control_perms()
对于普通属性而言,主要是调 以上是关于深入讲解Android Property机制的主要内容,如果未能解决你的问题,请参考以下文章