Android Framework------之Property子系统

概述

  Property是Android系统中一个重要的概念,在Android系统内,主要用于系统配置,以及不同服务间的简单信息分享。比如设备名字,蓝牙名字,编译信息,网络dns地址,以及其他的一些基本信息。 除了简单的信息分享外,还有个功能是启动和停止系统服务。 通过设置ctl.start.xxx属性,来启动某个属性,或者设置service.xxx.exit来停止服务。

  Android的系统属性Property整体上看,是键值对保存, 即Key -- Value方式。在系统运行过程中,Property是以字典树的方式存储内存中。但是也有一些固定的,不能修改的属性是存储在磁盘文件中。这些文件中存储的属性,在运行过程中是不会有交互使用的。只是在Android系统启动之初把这些固定的,不能修改的属性加载到内存中。 之所以选择字典树作为Property存储的数据结构,主要原因可能是trie这种数据结构的特点:把具有相同前缀的字符存储在同一个节点中,后续不同的字符存储为此节点的子节点。这种存储方式,能够是字符的查找速度更快。

  Property服务的启动时在init进程中进行的。在init进程初始化其他服务的同时,尽量早地在启动property服务。 
下面简单介绍Property启动的过程流程,大致分为如下:

1. property_init();【system/core/init/init.c】 
2. property_load_boot_defaults();【system/core/init/init.c】 
3. property_service_init_action;【system/core/init/init.c】 
4. load_all_props; 【system/core/rootdir/init.rc】 
5. queue_property_triggers_action;【system/core/init/init.c】 
6. handle_property_set_fd();【system/core/init/init.c】

对于property模块而言,启动完成后,就是在一个死循环中,不停地检查是否有他服务设置属性,如果有的话,接收和处理设置的属性。在阅读源码过程中,有个概念值得提一下:内存屏障(memory barrier),以下来自百科, 也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,使得CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。

property模块的数据结构

Property的存储的模型大致如下:

 +-----+   children    +----+   children    +--------+
 |     |-------------->| ro |-------------->| secure |
 +-----+               +----+               +--------+
                       /    \                /   |
                 left /      \ right   left /    |  prop   +===========+
                     v        v            v     +-------->| ro.secure |
                  +-----+   +-----+     +-----+            +-----------+
                  | net |   | sys |     | com |            |     1     |
                  +-----+   +-----+     +-----+            +===========+

根据字典树的存储方式,property相关的重要数据结构都是和字典树相关,有如下几个:

 struct prop_bt

  描述每个节点的信息,对应的就是属性名字中的某一部分名字的信息。这些信息包括此节点字符串,左右兄弟节点地址,子节点地址。同时在结构体声明时,也同时声明了prop_bt节点是不能赋值的,只有在构造的时候才能给这些信息初始化。不能赋值操作是通过宏 DISALLOW_COPY_AND_ASSIGN(prop_bt)来实现的。

struct prop_area {}

  描述Property所在使用内存的信息,包括property的版本信息,和使用空间。

struct prop_info {}

  描述某一个property的信息,比如属性[sys.boot_completed]:[1], 这个数据结构就是描述这个属性的整体信息的,包括属性名,属性值,以及属性值得长度。在这个结构体中,有个属性是serial,这个属性用来表示属性值的长度。 serial用关键字volatile修饰,这个值是经常在变化的,每次要使用这个值得时候,都要从内存去读取,而不是从register或cache读取。 另外,个人认为,这个属性除了表示属性值的长度外,还是个同步锁,同时只允许一个进程对此属性进行操作。

总结一下,以属性[sys.boot_completed]:[1]为例,属性名字sys.boot_completed以‘.’为分隔符分成两部分,需要两个prop_bt去存储。最后,在保存值得时候创建prop_info去保存[sys.boot_completed]:[1]。最后把prop_info的地址放入最后一个节点prop_bt的prop中。

下面稍微具体点来介绍Property这个模块

先从流程开始介绍: 
1.property_init();【system/core/init/init.c】 
在Android启动时, 在Init进程中很早就被调用了,主要作用就是初始化Property的使用的内存空间。初始化的方式就是创建一个tmfs文件系统文件/dev/properties,然后把这个文件一共享内存的方式映射到内存中,映射到内存的大小是128KB。最后,在libc中导出了这块共享内存的地址。这样,就有两种方式可以与Property进行读写操作了,一种是就是通过文件读写;另外就是内存共享读写。使用property的地址对property进行读写主要是property_service,运行在init进程中。这个过程对应的代码如下:

static int init_property_area(void)
{
    if (property_area_inited) // flag
        return -1;

    if(__system_property_area_init()) //[bionic/libc/bionic/system_properties.cpp]
        return -1;

    if(init_workspace(&pa_workspace, 0))
        return -1;

    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);//fork a process, but this fd will be closed

    property_area_inited = 1;
    return 0;
}

2. property_load_boot_defaults();【system/core/init/init.c】

在init.rc文件进行解析之前,就要加载此函数。因为default.prop文件中包含了一些重要属性**———— 要查———— **。 这个函数主要是加载Android系统根目录的/default.prop文件。这个文件的生成过程在【/build/core/Makefile】中INSTALLED_DEFAULT_PROP_TARGET模块。加载主要过程简述如下: 
打开/default.prop文件, 读取过程中以行为单位进行分析(对应代码是while ((eol = strchr(sol, ‘\n‘))) {**}), 
然后找出‘=’两侧的内容,然后调用 property_set(key, value)完成属性赋值过程。property_set的实现如下:

  

3. property_service_init_action;【system/core/init/init.c】 
首先此函数是由queue_builtin_action(property_service_init_action, "property_service_init");调用触发的, 
这个调用是发生在init.rc文件开始解析之后,并且已经执行完成了early-init, init系列动作之后进行的。但是这个这个函数执行是发生在after-init之前进行的。其实在第一步成功执行之后,就已经可以开始进行属性的设置和查找。只不过,在这个函数之前发生的设置属性操作,基本来自于default.prop 和 init.rc。从这两个地方过来的属性设置,都是init进程解析文件,主动发起的设置,发生在init进程中。所以可以直接对Property所在的内存空间直接进行读写,添加或修改属性。这个函数的主要功能就是创建一个socket,然后其他进程想要设置属性的时候,就可以通过socket通信,把想要设置的属性发送过来,然后交由init进程代为设置。

void start_property_service(void)
{
    int fd;

    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL);
    if(fd < 0) return;
    fcntl(fd, F_SETFD, FD_CLOEXEC);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    listen(fd, 8);
    property_set_fd = fd;
}

这个函数内容比较简洁,虽然函数名字的意思是启动property服务,但是实际上property服务并不是一个单独的进程中,而是运行在当前进程--init中。这个函数执行完成后,会在/dev/socket/创建一个socket文件/dev/socket/property_service。以后其它进程中可以通过这个socket文件与init进程通信,通信的内容是要init进程代为设置property属性。后面会说明这个通信过程。

4. load_all_props; 【system/core/rootdir/init.rc】 

这个动作在函数的代码中是找不到的。它是init.rc中定义的一个命令,在late-init之后才会被触发。触发过程如下:

on load_all_props_action
    load_all_props
...
on late-init
    trigger early-fs
    trigger fs
    trigger post-fs
    trigger post-fs-data

    # Load properties from /system/ + /factory after fs mount. Place
    # this in another action so that the load will be scheduled after the prior
    # issued fs triggers have completed.
    trigger load_all_props_action
    ...

这个动作也是从文件中加载一些系统属性,和第二步中加载/default.prop方式一样,只不过这次加载的这些属性是要在/system, /factory文件系统挂载之后才能进行的。具体过程可以参考第二步.

5. queue_property_triggers_action;【system/core/init/init.c】 
当init进程执行到这儿的时候,所有系统必要的属性都已经基本设置完成。这里会逐一trigger监听属性的操作。和第二步中的最后一个动作的道理一样。

6. handle_property_set_fd();【system/core/init/init.c】 
这个函数是Property模块中最为重要的函数之一。因为所有其它进程想要设置属性,都是通过socket通信进行的,当init进程中监听到有新的消息发送过来时,就会调用此函数。init进程是通过Linux的poll机制去监听Property通信用的socket的文件。

    for(;;) {
...
        if (!property_set_fd_init && get_property_set_fd() > 0) {
            ufds[fd_count].fd = get_property_set_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            property_set_fd_init = 1;
        }
        ...
        nr = poll(ufds, fd_count, timeout);
        if (nr <= 0)
            continue;

        for (i = 0; i < fd_count; i++) {
            if (ufds[i].revents & POLLIN) {
                if (ufds[i].fd == get_property_set_fd())
                    handle_property_set_fd();
...
            }
        }
    }

在init进程执行到最后,开启了一个无限循环,在这个循环过程中使用poll机制监听了一共三个文件,而Property得socket通信文件/dev/socket/property_service就是其中之一。当有socket有新的消息过来时,就会调用handle_property_set_fd();函数。整个过程如上面这段代码所述。handle_property_set_fd函数的流程如下:

对于在init进程中设置属性过程基本如此。但是对于其它进程是如何把要设置的属性转为消息,并通过socket发送到init进程中的这个过程还没有接触到,在下面的内容property与其他模块的通信方式和通信对象中会说明

Property的查找

由前面的叙述,我们已经知道,Property的存储是用字典树这种数据结构来存储的。使用字典树存储的目的就是为了查找速度更快,那么我们先看些Property从字典树中的查找过程:

在Property模块中的一些注意事项:

问题1: Property本身的信息是使用prop_info这个结构体描述的,而存储时的信息是用prop_bt描述的,那么在存储过程中,如何把从socket接收到的信息到生成prop_info结构体的过程是怎样的?在存储时如何把结构体prop_info,和结构体prop_bt是如何存储的?在修改已有的属性时,如何从内存中找到所需要的属性的? 
答:在解答上面一系列的问题之前,我们先看过函数find_property()的流程: 这个问题可以分如下几个小点回答:

  1. 如何把socket信息转化为结构体prop_info的? 
       从socket发送过来的信息,封装成了msg结构体。这个结构体中包含了想要进行的操作是设置属性,还有>设置属性所需要的key,value值。属性设置所需要的两个变量都有了,下面创建属性信息prop_info结构体,和存储属性结构体prop_bt都是property服务的工作了。
  2. 如何从结构体prop_bt转化到prop_info的?to_prop_obj()函数 prop_bt是局部信息,prop_info是整体信息。说法不太好 
       find_property() 函数完成这个工作。【bionic/libc/bionic/system_properties.cpp】 
    从根节点开始查找,如果没有找到的话,不要再当前函数中为prop_info申请空间。先把要设置属性的名字开始分段,以‘.’为分隔符,查找是否存在对应的节点是否存在,如果存在,那就找名字第二部分对应的节点是否存在,以此类推。如果都存在的话,那么就说明这个属性已经存在了,这时候通过to_prop_obj()函数,把其对应的信息构造成prop_info返回。如果属性名字中,有其中一部分没有找到的话,那么就要申请内存空间,创建属性,并存储。
  3. 如何从结构体prop_info转化到prop_bt的? 
       find_prop_bt() 函数完成这个工作。【bionic/libc/bionic/system_properties.cpp】 
    流程图 
    无论是添加属性,更新现有属性都是通过find_property()这个函数中的分发去处理的。【bionic/libc/bionic>/system_properties.cpp】 
    把属性中目标名字和节点中的name做比较,按照字母升序排列,如果节点中的名字靠后,那么返回值是 < 0;反之,返回值 > 0; 如果属性中目标名字和节点的名字一样的话,那么返回值就是0. 这样比较后,如果返回值是大于0,那么就去属性字典树的右侧查找;反之,就去字典树的左侧查找。如果返回值是等于0, 那么意味着找到了此节点。

__system_property_add和__system_property_find的区别就是在查找节点过程中,如果不存在想要找的节点,是否要创建新的节点。__system_property_add是要创建。__system_property_find是不创建。

Property与其他模块的通信方式和通信对象

在Android系统中,和属性相关的操作只有两个:查找属性,读取属性和设置属性。没有删除属性操作。另外,查找属性发生在读取属性和设置属性的过程中,其它模块一般不会直接使用查找操作。在常用的读取属性和设置属性这两个操作中,读取属性是不涉及到进程间通信的。前面我们也说到了,Property属性是存储在内存中,并且进行了内存共享映射,所以在读取属性的时候,是直接从共享内存中读取的,就发生在当前进程中,没有用到进程间通信。而用到进程间通信的是设置属性操作。因为在设置属性时,可能会启动其它的服务,也可能会修改某些系统配置,这都需要进行权限检查,所以不允许进程直接对属性设置,而是通过socket通信,把要设置的属性发送给init进程,由init进程代为进行属性设置。 
     下面以设置属性为例,补充完成上面第六步中handle_property_set_fd()中接收消息之前的操作。 假设我们想要设置一个属性property_set("test.test.test", "1"), 首先找到property_set函数system/core/libcutils/properties.c 文件中。对于Java层中进行的属性设置,最终会通过JNI调用此函数,详细过程不再述说。property_set会调用Android系统库libc中的__system_property_set()函数在bionic/libc/bionic/system_properties.cpp文件中,在__system_property_set()函数中,把要设置的属性封装成为msg后,就开始通过socket发送这个消息。发送消息的函数是send_prop_msg(&msg);也在同一个文件。这个函数就是通信的重点。在send_prop_msg()函数中,主要是创建socket,并通过这个socket发送消息。所创建的socket要想和init进程通信,就必须知道init进程段接收端的socket文件。Android系统中,Property接收端socket文件是默认的路径/dev/socket/property_service文件。这个文件是由前面第三步中create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL);创建。在create_socket()函数是Android系统自定义的函数,默认的就是在/dev/socket/目录下创建指定的socket文件。到这儿,当前进程就找到了Property在init进程中接收端的socket文件,就可以通过socket发送信息,进行属性设置了。

时间: 2024-10-11 18:21:29

Android Framework------之Property子系统的相关文章

Android Framework 记录之二

原文地址:http://blog.csdn.net/banketree/article/details/24982021 接着上次的记录,续写. 23.services目录 文件 描述 class AlarmManagerService extends IAlarmManager.Stub { //定时管理服务 public class AppOpsService extends IAppOpsService.Stub {  // 程序选项服务 public class AppsLaunchFa

[Android FrameWork 6.0源码学习] View的重绘过程之WindowManager的addView方法

博客首页:http://www.cnblogs.com/kezhuang/p/ 关于Activity的contentView的构建过程,我在我的博客中已经分析过了,不了解的可以去看一下 <[Android FrameWork 6.0源码学习] Window窗口类分析> 本章博客是接着上边那篇博客分析,目的是为了引出分析ViewRootImpl这个类.现在只是分析完了Window和ActivityThread的调用过程 从ActivityThread到WindowManager再到ViewRoo

Android Framework 分析---消息机制Native层

在Android的消息机制中,不仅提供了供Application 开发使用的java的消息循环.其实java的机制最终还是靠native来实现的.在native不仅提供一套消息传递和处理的机制,还提供了自定义文件描述符的I/O时间的监听机制.下面我们从具体代码中分析一下. Native层的关键类: Looper.cpp.该类中提供了pollOnce 和wake的休眠和唤醒机制.同时在构造函数中也创建 管道 并加入epoll的机制中,来监听其状态变化. Looper::Looper(bool al

Android Framework 记录之一

简介 之前的研究太偏向应用层功能实现了,很多原理不了解没有深究,现在研究framework框架层了. 原文地址:http://blog.csdn.net/banketree/article/details/24718899 记录 1.下载源码,目录如下: 2.Android系统的层次如下: 3.项目目录简单分析如下: 4.telphony目录 文件 描述 CellIdentityCdma //描述电信通信标识 CellIdentityGsm 描述移动通信标识 CellIdentityLte 描述

.Net程序员玩转Android系列之二~Android Framework概要(1)

从windows操作系统说起 人们总是喜欢从将陌生的事物和自己所了解的东西关联起来,以加深对未知事物的了解,这一讲我们从windows操作系统说起,逐步引领带大家走入android的世界.写任何程序都需要知道程序运行的原理和环境,就比如开发winform程序,你至少需要知道操作系统的原理,CLR运行时,了解一些常用的C/C++库.诚然,你不需要全部了解得很清楚也能写出很不错的应用程序,但是你了解的越清楚,钻研得越透彻,那么你越能开发出适合于你所在平台的.更优化的应用程序.譬如在windows操作

Android Framework 分析---PackageManager 分析

在windowphone,ios和android中到目前为止,还是android的市场份额最大.个人认为除了google开源外,广大开发者早就了android的霸主地位.各位兄弟姐妹开发出各种各样的apk,才组成android的广阔天下.本篇主要分析一下android系统是针对处理这些apk的,主要涉及到pm这块的代码.分析这种底层服务,最好从android的开启启动流程中开始分析.因为这样才能更清楚的了解服务的启动流程. 1.在SystemServer.java 中启动PM android 开

如何从C++代码直接访问android framework层的WifiService

说到底,Java层的service就是就C++层的binder的封装,所以从原理上来讲通过C++代码直接访问android framework层的service是完全可能的,这篇文章以访问WifiService为例,讲解如何去实现这个功能. 费话少说,直接上代码: WifiTest.cpp #include <sys/types.h> #include <unistd.h> #include <grp.h> #include <binder/IPCThreadSt

Android属性动画Property Animation系列三之LayoutTransition(布局容器动画)

在上一篇中我们学习了属性动画的ObjectAnimator使用,不了解的可以看看 Android属性动画Property Animation系列一之ObjectAnimator.这一篇我们来学点新的东西.做项目的时候应该碰到这种问题:根据不同条件显示或者隐藏一个控件或者布局,我们能想到的第一个方法就是 调用View.setVisibility()方法.虽然实现了显示隐藏效果,但是总感觉这样的显示隐藏过程很僵硬,让人不是很舒服,那么有没有办法能让这种显示隐藏有个过渡的动画效果呢?答案是肯定的,不言

android framework层 学习笔记(一)

大体结构 最近在研究android framework层的开发.  先来一张frame work的源码结构图 1  api 文件夹  不清楚用途,该目录下只有一个current.txt 内容看上去像代码,先放一放 2  cmds 文件夹,顾名思义是android 所支持的 command (命令) 最明显就是里面第一个文件夹就是am ,对应着android中的am 命令. 其他的命令里面有C语言写的也有java+C的. 3    core  文件夹这个文件夹下面有四个文件 java jni re

Android Framework 初探

最近工作任务不忙,学习一下Android Framework方面的知识. 一.介绍,是什么 Android的Framework是直接应用之下的一层,叫做应用程序框架层.这一层是核心应用程序所使用的API框架,为应用层提供各种API,提供各种组件和服务来支持我们的Android开发,包括ActivityManager,WindowManager,ViewSystem等.下面贴一张学习Android时常见的Android系统架构图: 二.有什么 Android Framework框架包含了3个主要部