前几天在分析Camera的时候,有一段这样的代码 property_get("service.camera.hw", value,"base");从字面猜测这是获取属性值,但是怎么去获取?属性值又在哪设置的?决定等把Camera HAL理完了之后,把这个也分析一下。今天就粗略的看了并总结如下。
属性(property)是一对键/值(key/value)组合,键和值都是字符串类型。Androd中非常多的应用程序和库直接或者间接的依赖于属性系统,并由此决定其运行期的行为。它的处理流程同android的其他模块一样,也分为服务端和客户端,property设置必须在服务端,读取直接在客户端。
架构图:
服务端 :
我们就来看系统的守护进程init,服务断就在这个进程里,分析它的main()@system/core/init/init.c函数。
1. 创建文件目录,打开中断设备,读取/init.rc,/init.**.rc。
这些都跟我们要分析的关系不大,但是还是要说明一下读取.rc文件的过程:
init有三个全局列表service_list,action_list,queue_list
当读取文件扫描到以on开头的一行数据时,将紧跟on后面的数据和下面几行数据组装成struct action@system/core/init/init.h变量,并加到action_list列表里。以service开头的就组装成strcut service@system/core/init/init.h变量,并加到service_list列表里。service_list记录了系统所有服务的列表;action_list记录了系统动作列表,但是相应的命令是不会运行的,除非将元素移动到queue_list列表;queue_list记录了将要执行的动作列表。
2. queue_builtin_action和action_for_each_trigger的调用
其中跟property有关的调用如下:
queue_builtin_action(property_init_action,"property_init");
queue_builtin_action(property_service_init_action,"property_service_init");
把第一个(函数)和第二个参数(名字)组装成一个action变量加到action_list和queue_list列表。
action_for_each_trigger("init", action_add_queue_tail);在action_list列表里查找以init为名的action变量,找到后将它加到queue_list列表里
3. 进入死循环for(;;)
4. execute_one_command和restart_processes的调用
执行所有在queue_list列表里的action,其中就有调用property_init_action和property_service_init_action它们分别调用property_init@system/core/init/Porpertys_services.c和start_property_servicet@system/core/init/Porpertys_services.c。运行service_list里的服务。
(1) 申请共享内存ashmem_create_region("system_properties", PA_SIZE),共享内存的参数保存在pa_workspace,共享内存分两部分,__system_property_area__指向内存起始地址,pa_info_array指向内存起始地址加PA_INFO_START的地址。__system_property_area__用来记录属性列表的统计信息,pa_info_array就是记录属性列表的地方,属性的个数最多为PA_COUNT_MAX。其中涉及到的宏定义如下:
#define PA_COUNT_MAX 24 #define PA_INFO_START 1024 #define PA_SIZE 32768
共享内存的结构图:
(2) 从下面宏定义的文件及PERSISTENT_PROPERTY_DIR指定的目录下以persist.开头的文件中读取属性列表并储存在共享内存中。
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop" #define PROP_PATH_SYSTEM_BUILD "/system/build.prop" #define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop" #define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop" #define PERSISTENT_PROPERTY_DIR "/data/property"
文件内容的一般格式,从system/build.prop截的图
(3) 创建本地Socket,fd = socket(PF_UNIX, SOCK_STREAM, 0),路径是ANDROID_SOCKET_DIR/PROP_SERVICE_NAME。绑定路径监听Socket(非阻塞)。
#define PROP_SERVICE_NAME "property_service" #define ANDROID_SOCKET_DIR "/dev/socket"
(4)启动service_list列表里的服务,在启动服务之前,将共享内存的句柄(当然是dup之后的)和大小保存在服务进程的环境变量里。
sprintf(tmp, "%d,%d", dup(fd), sz); add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);//ENV[n] = tmp; execve(svc->args[0], (char**)svc->args, (char**) ENV)
5. 使用select函数,等待客户端Socket的连接。
有连接到来,调用handle_property_set_fd函数处理,首先accept获取连接,getsockopt(s, SOL_SOCKET, SO_PEERCRED,&cr, &cr_size)获取到客户端权限;再接受数据,判断命令类型,现只接受一种命令PROP_MSG_SETPROP(修改property),在修改之前要判断客户端有没有相应的权限。命令有两种类型:''ctrl:start netd(服务名)''/''ctrl:stopnetd(服务名)'';''service.video.path(property名字)=normal(property值)'';第一种是用来启动停止服务的,与我们要分析的关系不大,第二种就是property的设置了调用property_set。
6. property_set@system/core/init/Property_service.c
先根据property名字从pa_info_array指向的内存中查找,若查找到更新内存中的值,否则在内存的后面添加。并查找此property设置有没有相应的action,若存在则加到queue_list列表里,比如前面的init.rc文件有内容:
on property:persist.service.adb.enable=1 start adbd当调用property_set(“persist.service.adb.enable”,”1”)时,由于action_list存在这个action,将它加入到queue_list,就会触发这个action的动作start adbd(启动adbd服务)。 这里有几种特殊的情况:以ro.开头的property不可以修改,只能初始化一次;以net.开头的property同时设置''net.change=name'';以persist.开头的property同时创建以PERSISTENT_PROPERTY_DIR/name命名的文件并写入它的值。
客户端:
JAVA程序可以通过SystemProperties@framework/base/core/java/anroid/os/SystemProperties.java类来修改和获得property,其实质还是调用到c++ 空间的property_get/property_set(system/core/libcutils/Properties.c)
1. 获得property共享内存地址和大小
在客户端进程初始化libc库的时候都会调用_libc_preint@bionic\libc\bionic\libc_init_dynamic.c(动态加载)或_libc_init@bionic\libc\bionic\libc_init_static.c(静态加载),这两个函数都会调用_libc_init_common@bionic/libc/bionic/libc_init_common.c,进一步调用__system_properties_init@bionic/libc/bionnic/System_properties.c。获取环境变量值getenv("ANDROID_PROPERTY_WORKSPACE"),得到内存句柄和大小,使用mmap得到内存地址 __system_property_area__。
2. propety_get 直接在共享内存查找,这个不需要通过服务端。
3. propety_set 连接上服务端socket,发送到服务端处理。