android 中属性设置

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文件中定义);这种通知的媒介是一个unix socket: ANDROID_RESERVED_SOCKET_PREFIX(/dev/socket/)/property_service (见文件system/core/libcutils/socket_local_client.[ch]中的函数socket_local_client()和文件system/core/libcutils/properties.c中的函数static send_prop_msg(prop_msg *msg));
case 2: 对于Linux上的android虚拟机(定义了HAVE_SYSTEM_PROPERTY_SERVER)来说,由于存在一个专用的“system property server”,所以通过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_”开头)共享。以下不考虑这类情况。


case 1下,客户端拥有属性的本地存储,所以获取属性值很简单:直接到相关存储区寻找即可:
   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 :   void  property_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_t size):
             创建临时文件 /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(int nargs, char **args)中,会调用system/core/init/property_service.c : void start_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,属性的修改很简单:发出一个请求,然后等待返回结果。
对于以上所提的case 1来说,因为属性列表已经映射到本进程,故本可以直接修改其值,但麻烦在于要把这个事件通知给init进程去执行相关动作。实际上是通过以下过程实现的:
1)发送“修改属性”请求给init进程,通道是以上所提到过的unix socket文件/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. 属性的读取
对于以上所提的case 2来说,读取属性是很简单的:即通过pipe文件发出请求,然后等待结果的返回,唯一稍微麻烦的一点就是访问这个大内存块时的同步问题;
对于以上所提的case 1来说,因为属性表已经映射到本进程内,所以直接读取即可,但是需要注意不要读到脏数据(即属性项的serial的最低位为1时的属性值,见bionic/libc/include/sys/_system_properties.h中的定义)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值