深入讲解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进程里启动的。我们知道,initLinux系统中用户空间的第一个进程。它负责创建系统中最关键的几个子进程,比如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__文件并不是符号链接,所以当然可以成功openO_CLOEXEC标识是为了保证一种独占性,也就是说当init进程打开这个文件时,此时就算其他进程也open这个文件,也会在调用exec执行新程序时自动关闭该文件句柄。O_EXCL标识和O_CREATE标识配合起来,表示如果文件不存在,则创建之,而如果文件已经存在,那么open就会失败。第一次open动作后,会给__system_property_area__赋值,然后程序会立即close刚打开的句柄。

第二次open动作发生在接下来的init_workspace()函数里。此时会再一次打开__properties__文件,这次却是以只读模式打开的: 
int fd = open(PROP_FILENAMEO_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,解析出行内的keyvalue部分,并调用property_set(),将keyvalue设置进系统的属性共享内存去。

        我们绘制出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机制的主要内容,如果未能解决你的问题,请参考以下文章

Android 深入系统完全讲解(16)

Android 深入系统完全讲解(13)

Android 深入系统完全讲解(14)

iOS runtime探究: 从runtime开始理解OC的属性property

Android property属性机制

Android property属性机制