Fork me on GitHub
0%

Andriod Property工作机制

简介

Property属性系统是 Android 中使用最广泛的进程间信息共享机制了。本质上属性(property)是一对键/值(key/value)组合,键和值都是字符串类型。Androd中非常多的应用程序和库直接或者间接的依赖于属性系统,并由此决定其运行期的行为。它的处理流程同android的其他模块一样,也分为服务端和客户端,property设置必须在服务端,读取直接在客户端。其特点可以总结为

  • 启动启动后将init.rc、build.prop、default.prop等属性文件中加载进一块共享内存
  • 属性文件的格式类似于map表,以key/value的形式存在,所有键值是以字线树的形式进行组织起来的
  • 任何进程都会将共享内存映射到自己的内存中,进而可以读出值
  • 修改变量不同进程只可以通过socket方式,向属性服务发出修改属性值的请求,而不能直接修改属性值
  • 系统中只有一个实体可以设置修改属性值,即属性服务

字典树

属性在内存中是以字典树的形式进行存储的

  1. 首先是共享内存起始块prop_area。其中规定了byte_used、version、magic_number等等
  2. 紧接着是一个空白节点prop_bt,其作为整棵树的根节点
  3. 下面将依次存储相关属性
    • 属性名将以‘.’符号为分割符被分割开来。比如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调用流程

  1. 初始化一块共享内存

    • 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; 置位

  2. 加载所有默认属性

    • 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存储到对应的内存位置

  3. 解析加载init.rc中的service

    init_parse_config_file(“/etc/init.rc”)

  4. 将某个动作添加到队列

    queue_builtin_action(property_service_init_action, (char*)”property_service_init”);

  5. 在某个阶段出发某个service

    action_for_each_trigger(“late-init”, action_add_queue_tail);

  6. for(;;) 循环

如何实现共享

init_workspaced函数调用的时候,打开的句柄记录在pa_workspace.fd处,以后每当init进程执行socket命令,并调用service_start()时,执行:

1
2
3
get_property_workspace(&fd, &sz);                                          
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);

其实就是把 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等等

实现方式

  1. 前期准备
    • 选择基础数据结构:map
    • 选择语言:C
    • 构建整体软件框架:rbtree–>map—>system_prop—>server_prop
  2. 在PC进行代码构建
    • 基于开源代码rbtree的实现,封装出map接口
    • 基于andriod的实现和提供的map接口封装system_prop
    • 基于system_prop封装导出的API接口
  3. 测试代码功能
  4. 移植到开发板并调试
  5. 添加内存池,进行代码优化

参考文章