简介
Property属性系统是 Android 中使用最广泛的进程间信息共享机制了。本质上属性(property)是一对键/值(key/value)组合,键和值都是字符串类型。Androd中非常多的应用程序和库直接或者间接的依赖于属性系统,并由此决定其运行期的行为。它的处理流程同android的其他模块一样,也分为服务端和客户端,property设置必须在服务端,读取直接在客户端。其特点可以总结为
- 启动启动后将init.rc、build.prop、default.prop等属性文件中加载进一块共享内存
- 属性文件的格式类似于map表,以key/value的形式存在,所有键值是以字线树的形式进行组织起来的
- 任何进程都会将共享内存映射到自己的内存中,进而可以读出值
- 修改变量不同进程只可以通过socket方式,向属性服务发出修改属性值的请求,而不能直接修改属性值
- 系统中只有一个实体可以设置修改属性值,即属性服务
字典树
属性在内存中是以字典树的形式进行存储的
- 首先是共享内存起始块prop_area。其中规定了byte_used、version、magic_number等等
- 紧接着是一个空白节点prop_bt,其作为整棵树的根节点
- 下面将依次存储相关属性
- 属性名将以‘.’符号为分割符被分割开来。比如ro.secure属性名就会被分割成“ro”和“secure”两部分,而且每个部分用一个prop_bt节点表达。
- 属性名中的这种‘.’关系被表示为父子关系,所以“ro”节点的children域,会指向“secure”节点,一个节点只有一个children域,如果它还有其他孩子,那些孩子将会和第一个子节点(比如secure节点)组成一棵二叉树。
- 当一个属性名对应的“字典树枝”都已经形成好后,会另外创建一个prop_info节点,专门表示这个属性,该节点就是“字典树叶”
使用方法
获取属性
getprop 属性名
设置属性
setprop 属性名 属性值
函数调用流程
获取所有属性
- property_list -call-> __system_property_foreach(void (*propfn)(const prop_info *pi, void *cookie)
- __system_property_foreach(void (*propfn)(const prop_info *pi, void *cookie) -call-> foreach_property()遍历所有属性
获取某个属性
说明:获取属性的流程不再走socket,而是直接从共享内存中读取
- int property_get-call->system_property_get(key, value)
- __system_property_find-call-> __system_property_find_compat(name)
设置属性
- property_set call-> __system_property_set(key, value)
- __system_property_set(key, value) -call-> send_prop_msg(&msg)
__system_property_set通过本地SOCKET: “/dev/socket/property_service” 与init进程中properties服务通讯。
Property调用流程
初始化一块共享内存
- void property_init(void) ==> init_property_area(void)
property_area_inited 初始化内存区域标志位检查
__system_property_area_init() ==> map_prop_area_rw()
创建并打开/tmp/__properties__文件;ftruncate(fd, PA_SIZE)文件大小指定为PA_SIZE;映射一段地址到文件并初始化内存;将内存地址__system_property_area__赋值到全局变量
init_workspace(workspace *w, size_t size) 设置waorspace结构体,只读打开PROP_FILENAME
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC)
property_area_inited = 1; 置位
加载所有默认属性
- load_all_props( ) ==> load_properties_from_file(const char *fn, const char *filter)
*read_file(fn, &sz) 读fn文件大小存入sz,开辟一块size+2的堆空间,首地址作为返回值return出去
void load_properties(char *data, const char *filter)
while ((eol = strchr(sol, ‘\n’))) 逐行解析
# 关键字符比较。处理:注释
import 关键字符比较。处理:导入其他.prop文件
= 关键字符比较。处理:按照key,value
property_set(key, value)
__system_property_add(const char *name, unsigned int namelen, const char *value, unsigned int valuelen)
prop_info *find_property(prop_bt *const trie, const char *name, uint8_t namelen, const char *value, uint8_t valuelen, bool alloc_if_needed)
核心函数,将prop按照map存储到对应的内存位置
解析加载init.rc中的service
init_parse_config_file(“/etc/init.rc”)
将某个动作添加到队列
queue_builtin_action(property_service_init_action, (char*)”property_service_init”);
在某个阶段出发某个service
action_for_each_trigger(“late-init”, action_add_queue_tail);
for(;;) 循环
如何实现共享
init_workspaced函数调用的时候,打开的句柄记录在pa_workspace.fd处,以后每当init进程执行socket命令,并调用service_start()时,执行:
1 | get_property_workspace(&fd, &sz); |
其实就是把 pa_workspace.fd 的句柄记入一个名叫“ ANDROID_PROPERTY_WORKSPACE ”的环境变量去。
android采用了gcc的constructor属性,该属性指明一个_libc_preinit函数,当bionic库被加载时将自动调用_libc_preinit,关键call过程如下:
1 | __libc_preinit -call-> __libc_init_common(*args)->__system_properties_init->map_prop_area |
map_prop_area( )
- get_fd_from_env(), 通过ANDROID_PROPERTY_WORKSPACE环境变量拿到fd
- map_fd_ro(const int fd),映射该内存区域到本地进程
属性特殊处理
ctl.开头的属性是控制属性,用于控制系统的本地服务
1 | ctl.xxx(start/stop/restart) servicename[:args] |
- ro.开头的属性不能被修改;
- net.开始的属性(除net.change外)设置, 将引发net.change=key的属性设置,被bionic/libc/netbsd/resolv/res_state.c中的代码处理(通过__system_property_find函数);
- persist.开始的属性,如果在init.rc和代码中设置, 将会被写到/data/property目录下;
rtos实现方式
介绍
自己在freertos的开发中,整体使用C语言,针对property的功能自己实现了一套相关代码。主要包括:get/set property;针对property的前置属性进行判断,ro/rw/presist等等
实现方式
- 前期准备
- 选择基础数据结构:map
- 选择语言:C
- 构建整体软件框架:rbtree–>map—>system_prop—>server_prop
- 在PC进行代码构建
- 基于开源代码rbtree的实现,封装出map接口
- 基于andriod的实现和提供的map接口封装system_prop
- 基于system_prop封装导出的API接口
- 测试代码功能
- 移植到开发板并调试
- 添加内存池,进行代码优化