一、 简介
每一个属性都是一个名值对,名和值都是文本。Android系统中,属性被大量使用,用来记录系统设置或者在进程间交换信息。属性在整个系统中是可见的,每一个进程都可以get/set属性。
系统初始化时,在init这个进程中,Android会分配一些内存来存储这些属性,Init源码在/system/core/init中。Init这个进程会开启一个运行在init进程中的property service,每一个想设置属性的client都需要连接到这个property service上去,然后向它发送消息,property service会在共享内存中更新/创建属性。查询属性的client都会从共享内存中直接读取属性,这就提高了读取效率。 相关初始化代码如下:
queue_builtin_action(property_init_action, "property_init");
....
queue_builtin_action(set_init_properties_action, "set_init_properties");
...
queue_builtin_action(property_service_init_action, "property_service_init");
二、 属性系统的架构
如下图所示:
图中有3个进程、一组永久属性文件和一块共享内存区域。共享内存区域是所有属性记录的存储所在。只有property服务进程才可以写入共享内存区域,它负责从永久文件中加载属性记录并将它们保存在共享内存中。
consumer进程将共享内存加载到其自身的虚拟地址空间并直接访问这些属性。setter进程同样将共享内存加载到其自身的虚拟地址空间,但其不能直接写该内存。当setter试图增加或者更新一个属性时,它将该属性通过unix domain socket发送至property服务。property服务代表setter进程将该属性写入共享内存和永久文件中。
property服务运行于init进程中。init进程首先创建一个共享内存区域,并保存一个指向该区域的描述符fd。init进程将该区域通过使用了MAP_SHARED标志的mmap映射至它自身的虚拟地址空间,这样,任何对于该区域的更新对于所有进程都是可见的。fd和区域大小被存储在一个名为ANDROID_PROPERTY_WORKSPACE的变量中。任何其他进程,比如consumer和setter将使用这个变量来获得fd和尺寸,这样它们就能mmap这个区域到它们自身的虚拟地址空间中。该共享内存区域如下图所示:
在这之后,init进程将从下列文件加载属性:
/default.prop
/system/build.prop
/system/default.prop
/data/local.prop
属性将会以上述顺序加载。后加载的属性将覆盖原先的值。这些属性加载之后,最后加载的属性会被保持在/data/property中。
下一步是启动property服务。在这一步中,一个unix domain socket服务被创建。此socket的路径是 /dev/socket/property_service,该路径对于其他客户端进程是熟知的。最后,init进程调用poll来等待该socket上的连接事件。
在consumer一边,当它初始化libc(bionic/libc/bionic/libc_common.c __libc_init_common 函数),它将从环境变量中返回fd和尺寸,并映射共享内存到其自身的地址空间(bionic/libc/bionic/system_properties.c __system_properties_init 函数)。在这之后,libcutils可以像读取普通内存那样为consumer读取属性。
目前,属性是不能够被删除的。也就是说,一旦添加了一个属性,它将不能够被删除,其键也不能够被改变。
三、特别属性
如果属性名称以“ro.”开头,那么这个属性被视为只读属性。一旦设置,属性值不能改变。
如果属性名称以“persist.”开头,当设置这个属性时,其值也将写入/data/property。
如果属性名称以“net.”开头,当设置这个属性时,“net.change”属性将会自动设置,以加入到最后修改的属性名。(这是很巧妙的。 netresolve模块的使用这个属性来追踪在net.*属性上的任何变化。)
属性“ ctrl.start ”和“ ctrl.stop ”是用来启动和停止服务。每一项服务必须在/init.rc中定义。系统启动时,init守护进程将解析init.rc和启动属性服务。一旦收到设置“ ctrl.start ”属性的请求,属性服务将使用该属性值作为服务名找到该服务,启动该服务。这项服务的启动结果将会放入“ init.svc.<服务名>“属性中 。客户端应用程序可以轮询那个属性值,以确定结果。
四、如何读取/设置属性
Android上有三种主要途径来get/set属性。
1. native code
当编写本地应用程序时,可以使用property_get和property_set 这两个API来读取/设置属性。要使用它们,我们需要include cutils/properties.h,并链接libcutils库。而libcutils又调用libc中的 __system_property_xxx 函数获得共享内存中的属性。libc的源代码位于:device/system/bionic。
int property_get(const char *key, char *value, const char *default_value);
int property_set(const char *key, const char *value);
2. java code
在Java包(java.lang.System)中提供有System.getProperty和System.setProperty方法。但值得注意的是,尽管这两个API在语义上等同native函数,但其将数据存储于完全不同的位置。实际上,dalvik VM使用一个哈希表来存储这些属性。所以,用这两个API存储的属性是独立的,不能存取native属性,反之亦然。
然而Android有一个内部隐藏类(@hide,对SDK不可见)android.os.SystemProperties来操纵native属性。其通过jni来存取native属性库。
3. shell脚本
Android toolbox程序提供了两个工具: setprop和getprop获取和设置属性。其使用方法:
getprop <属性名> (还带参数则显示系统中所有的属性)
setprop <属性名><<属性值>
Action
默认情况下,设置属性只会使"init"守护程序写入共享内存,它不会执行任何脚本或二进制程序。但是,您可以将您的想要的实现的操作与init.rc中某个属性的变化相关联.例如,在默认的init.rc中有:
# adbd on at boot in emulator
on property:ro.kernel.qemu=1
start adbd
on property:persist.service.adb.enable=1
start adbd
on property:persist.service.adb.enable=0
stop adbd
这样,如果你设置persist.service.adb.enable为1 ,"init"守护程序就知道需要采取行动:开启adbd服务。
五、补充
通过查看property_service.c,我们可以明确以下事实:
1. 属性名不是随意取的。在property_perms数组中定义了当前系统上可用的所有属性的前缀,以及相对应的存取权限UID。对属性的设置要满足权限要求,同时命名也要在这些定义的范围内。
2. PA_COUNT_MAX指定了系统(共享内存区域中)最多能存储多少个属性。
3. PROP_NAME_MAX指定了一个属性的key最大允许长度;PROP_VALUE_MAX则指定了value的最大允许长度。
此外,http://blog.csdn.net/tekkamanitachi/archive/2009/06/18/4280982.aspx 这篇文章翻译了Android的官方文档,从另一个角度叙述了属性系统,需要者请参看。