Android系统大量使用属性,用于记录系统设置和进程通讯。属性是在整个系统中全局可见的。每个进程都可以get/set属性。在系统初始化时,Android将分配一个共享内存区来存储属性。这是由init进程(见system/core/init/init.c)完成的:init守护进程在执行完系统启动过程后,将成为一个属性管理的后台服务,接收其它进程对于属性的管理请求。
----------------------------------------------------------------------------------------------------------------------------------------
0. 客户端/服务端所涉及的源文件
客户端涉及的源文件:
bionic/libc/bionic/system_properties.c#提供新进程的属性初始化API
system/core/include/cutils/properties.h
system/core/libcutils/properties.c#提供进程操作属性的API
服务端涉及的源文件:
system/core/init/init_parser.c#为init进程实现属性改变时的伴随动作机制
system/core/init/property_service.c# 为init进程提供属性初始化/读/写/列等支持
system/core/init/init.c# init实现了属性管理服务端
1. 客户端访问属性所使用的API
属性保存在共享内存区中,但随着实现“共享”的方式不同,客户端的行为也少有变化,但不管怎样,客户端总可以调用system/core/libcutils/properties.c中定义的以下API函数来访问属性信息:
int property_get(const char *key, char *value,const char *default_value);
int property_set(const char *key, const char*value);
int property_list(void (*propfn)(const char*key, const char *value, void *cookie), void *cookie);
另外,用adb开一个android的字符终端,其中可以使用以下命令:
setprop ctl.start $SERVICE_NAME #启动某个服务
setprop ctl.stop $SERVICE_NAME #停止某个服务
2. 属性的存储空间:"共享"内存
客户端使用的源文件是system/core/libcutils/properties.c,它包含了各种平台下客户端使用属性的基本函数:
case 1:对于实体的android设备(定义了HAVE_LIBC_SYSTEM_PROPERTIES),所谓的“共享”实际上是通过共享一个文件来实现的,所有共享此文件的进程都将文件mmap到自身的进程空间内,且使用了MAP_SHARED共享标志,所以,一个进程对文件内容的改变对于其它进程来说立即可见,但是需要某种机制来通知init进程执行相关的动作(在init.*.rc文件中定义);这种通知的媒介是一个unixsocket:ANDROID_RESERVED_SOCKET_PREFIX(/dev/socket/)/property_service(见文件system/core/libcutils/socket_local_client.[ch]中的函数socket_local_client()和文件system/core/libcutils/properties.c中的函数staticsend_prop_msg(prop_msg *msg));
case 2:对于Linux上的android虚拟机(定义了HAVE_SYSTEM_PROPERTY_SERVER)来说,由于存在一个专用的“systempropertyserver”,所以通过IPC来请此server来完成属性的get/set/list;IPC的媒介是pipe文件"/tmp/android-sysprop"(见system/core/include/cutils/properties.h定义的SYSTEM_PROPERTY_PIPE_NAME);
case 3:对于Win32上的android虚拟机(其它):通过系统范围内的环境变量(都以“PROP_”开头)共享。以下不考虑这类情况。
在case1下,客户端拥有属性的本地存储,所以获取属性值很简单:直接到相关存储区寻找即可:
system/core/libcutils/properties.c :property_get() ->
bionic/libc/bionic/system_properties.c :__system_property_get() ->
bionic/libc/bionic/system_properties.c :__system_property_find()
而这个属性存储区是这样初始化的:
bionic/libc/bionic/ { libc_init_static.c : __noreturn void__libc_init() |libc_init_dynamic.c : void __libc_preinit() }->
bionic/libc/bionic/libc_init_common.c : void__libc_init_common(uintptr_t *elfdata)->
bionic/libc/bionic/system_properties.c :__system_properties_init()--- 所以应用程序不用显式调用此函数!
在__system_properties_init()中,首先通过环境变量ANDROID_PROPERTY_WORKSPACE获得属性区的fd和容量,然后将该fd映射进内存。这个环境变量是init进程在函数system/core/init/init.c: service_start()中fork出新进程后、新进程开始运行之前通过add_environment()为新进程添加的,其格式为$fd.$size,而android进程都是直接或间接通过某个service创建的,故可以获得此环境变量。
3. 属性的初始化
属性最初由init进程根据flash上的内容初始化,其它进程通过则共享这部分内容,这种共享可以通过mmap直接共享,也可能是通过查询请求来实现的。
属性在init进程中的初始化过程如下 :
(注:init.rc的分析过程是先扫描,扫描时将所有要执行的命令收集在 action 列表中,在最后的无限循环中逐条执行这些action中的命令,其中的"property_init"命令就是把属性初始化成文件/default.prop设的值,“keychord_init”就是启动所有的service,"logo_init"就是为显示logo而准备文件INIT_IMAGE_FILE["/initlogo.rle"],"property_service_init"就是根据若干候选属性文件进一步设置系统属性,...)
system/core/init/init.c :static int property_init_action(int nargs,char **args) ->这在分析init.rc文件时发生!
system/core/init/property_service.c: voidproperty_init(void) ->
system/core/init/property_service.c: static int init_property_area(void)->
system/core/init/property_service.c: static int init_workspace(workspace *w, size_tsize):
创建临时文件/dev/__properties__,把它映射到内存;然后再读打开此文件(返回的fd用于在本进程中读属性值);最后删除此文件。返回的结构含有:文件的内存映射地址,映射的长度,只读的fd。
函数init_property_area()会设置被映射文件的FD_CLOEXEC标志,并清空所映射的内存区域。随后,property_init()会调用system/core/init/property_service.c: static void load_properties_from_file(const char*fn)来根据以下文件(见文件bionic/libc/include/sys/_system_properties.h)来初始化属性表:
PROP_PATH_RAMDISK_DEFAULT (即"/default.prop")
在system/core/init/init.c : static int property_service_init_action(intnargs, char **args)中,会调用system/core/init/property_service.c : voidstart_property_service()依次从以下文件读入属性表(文件名定义见bionic/libc/include/sys/_system_properties.h):
PROP_PATH_SYSTEM_BUILD("/system/build.prop" )
PROP_PATH_SYSTEM_DEFAULT("/system/default.prop")
PROP_PATH_LOCAL_OVERRIDE ("/data/local.prop")
之后,还要通过system/core/init/property_service.c :load_persistent_properties()函数从目录PERSISTENT_PROPERTY_DIR(即"/data/property")下的各个文件中读取persistent的属性值。
属性会以上述顺序加载。后加载的属性将覆盖原先的值。
4. 属性的设置
对于以上所提的case 2,属性的修改很简单:发出一个请求,然后等待返回结果。
对于以上所提的case1来说,因为属性列表已经映射到本进程,故本可以直接修改其值,但麻烦在于要把这个事件通知给init进程去执行相关动作。实际上是通过以下过程实现的:
1)发送“修改属性”请求给init进程,通道是以上所提到过的unixsocket文件/dev/socket/property_service;
2)init进程会轮询此socket文件,并调用handle_property_set_fd()来处理所收到的请求(目前只有一种请求,就是PROP_MSG_SETPROP),此函数定义在文件system/core/init/property_service.c中;
3)属性设置:分2种情况(都需要先经过permission检查后才能继续):
3.1)设置ctl.* :这是'控制'类属性(主要只有两个属性:“ctrl.start”和“ctrl.stop”),用于启动/停止某个服务,由函数system/core/init/init.c:handle_control_message()处理,其实就是找到对应的service结构并启动或停止它(见前面提到的service_start()函数)。该服务的启动结果将会放入"init.svc.<服务名>"属性中,客户端应用程序可以轮询那个属性值,以确定服务启动的结果。
3.2)设置普通属性:
* 如果属性名称以“ro.”开头,那么这个属性被视为只读属性。一旦设置,属性值不能改变;
*如果属性名称以“net.”开头,当设置这个属性时,“net.change”属性将会自动设置,以加入到最后修改的属性名。这是很巧妙的。netresolve模块使用这个属性来追踪在net.*属性上的任何变化;
*对于persistent开头的属性还要保存到文件/data/property/PROPERTY_NAME中,见system/core/init/property_service.c: write_persistent_property()函数;
属性设置后,还要触发相关的动作:由system/core/init/property_service.c :property_set()处理。具体过程是:首先在本地修改属性值,最后通过system/core/init/init.c :property_changed(name,value)来通知init进程执行相应动作,而property_changed()又会通过system/core/init/init_parser.c :queue_property_triggers()进而通过system/core/init /init_parser.c :action_add_queue_tail()将对应的动作加入行为排队。system/core/init/init.c的main()在结尾的无限循环中会调用execute_one_command(),它会通过system/core/init/init_parser.c :action_remove_queue_head()从行为队列中取出第一个节点,并执行相应的动作(init每循环一次只执行一个这样的动作)。
5. 属性的读取
对于以上所提的case2来说,读取属性是很简单的:即通过pipe文--件发出请求,然后等待结果的返回,唯一稍微麻烦的一点就是访问这个大内存块时的同步问题;
对于以上所提的case1来说,因为属性表已经映射到本进程内,所以直接读取即可,但是需要注意不要读到脏数据(即属性项的serial的最低位为1时的属性值,见bionic/libc/include/sys/_system_properties.h中的定义)。
【注:文件bionic/libc/bionic/system_properties.c定义了函数__system_property_wait(),但并未发现在它在其他地方得到调用。进一步探索中...】
<END>