SystemProperty小结(Android4.2)

       Android提供了系统属性值,既可以在java中使用,也可以C/C++中使用,非常方便。本文主要学习相关的一些知识,帮助我们更好地理解和使用系统属性。

       本文基于Android4.2、MTK6572平台的代码进行分析。

       主要涉及的内容包括它的数据结构,如何实现读和写,具体某个属性值的产生等问题。

一、数据结构

        每一个系统属性值作为一条记录,保存在prop_info这个结构体中:其中,name和value的最大长度分别为32和92,再加上4个字节的serial,每个系统属性占用128个字节的空间。系统目前最大支持375个系统属性(从代码注释中看到原来是247个,可能是MTK为了增加自己的一些系统属性,把这个值增大了)。

struct prop_info {
    char name[PROP_NAME_MAX];
    unsigned volatile serial;
    char value[PROP_VALUE_MAX];
};

struct prop_area {
    unsigned volatile count;
    unsigned volatile serial;
    unsigned magic;
    unsigned version;
    unsigned reserved[4];
    unsigned toc[1];
};

#define PROP_NAME_MAX   32
#define PROP_VALUE_MAX  92

#define PA_COUNT_MAX 375

         在prop_area结构体中,count代表当前系统属性的总数,magic和version都是固定的值,reserverd是保留字,暂时无用,最值得一提的就是toc这个数组。这块存储系统属性的空间一共占用了49664个字节。紧接着toc数组之后的是在prop_area结构体中未定义的,它们分别连续地存放了375个系统属性(申请了可以存放376个属性的空间,最后一块空间应该没有用到)。

         toc这个数组对应了后面的系统属性。通过toc[i]就能够找到prop_info i。但是,toc[i]中存放的并不是指向prop_info i的指针,而是记录了两个信息:对应prop_info i的name的长度以及它相对于prop_area首地址的偏移量。toc存储的是unsigned int,占用4个字节,其中,最高字节保存name的长度(name最大长度为92,用8位来表示绰绰有余),较低的三个字节存储偏移量(最大偏移量为49664,用24位来表示也足够了),通过这个偏移量就能够找到对应的prop_info。

        为何需要保存name的长度呢?如果不用保存它的话,其实我们可以完全不需要toc这个数组,我们一样能够通过计算偏移量找到每个prop_info。保存name的长度其实是为了提高查找系统属性的效率:我们在get/set某个系统属性时,都需要尽快根据该系统属性的name查找到它对应的prop_info结构。那么一般地,我们可能需要遍历这块空间(从0到count依次根据toc找到prop_info),然后将我们的name和该prop_info的name进行依次字符串的比较,如果相等,则查找成功,结束。而现在,我们是这样查找的:也需要依次遍历这些属性值,但是我们并不是直接将我们的name和该prop_info的name进行依次字符串的比较,而是先比较toc中存储的prop_info的长度与我们的name的长度是否相同,这是一个int型的比较,相比与字符串的比较,快了很多,如果name长度相等,我们才会进一步比较name是否完全相同,这样能够避免很多不必要的字符串比较,因此查找效率得到提高。

         在Android4.4中,对这部分数据结构又进行了优化,采用类似二叉树的结构来存储属性信息,查找效率又得到了提高。

SystemProperty小结(Android4.2)_第1张图片

二、实现框架

       系统属性的使用非常方便,既可以在java中用,也可以在C/C++中方便地使用。java中的调用接口是SystemProperties.get和SystemProperties.set;C/C++中的接口是property_get和property_set。java层之所以能够调用,还是透过jni把C/C++中的方法进行了封装,本质上这些最终都是调用property_get和property_set这两个方法。而这两个方法的实现就要依赖libc和init中对系统属性的实现,所以我们主要看这部门是如何实现的。

SystemProperty小结(Android4.2)_第2张图片

      首先应该明确,对系统属性的操作,核心操作是有get和set两种。但是,只有几乎所有进程都可以get某个系统属性,但是只有init进程才能够直接set某个系统属性,而其它进程只能间接地通过init来set,而init进程会在真正set之前,进行一些权限的检查,这样我们就不能随意地set系统属性了。

      下面的分析中,我们来分析以下几个问题:前面提到的prop_area空间是如何分配的;为何只有init进程具有set的权限;set和get的具体过程;

      1.prop_area空间是如何分配的,为何只有init进程具有set的权限?

      init进程创建了一个文件,并设置这个文件的大小为49664B。它以可读可写的方式打开这个文件,并持有这个文件句柄,这个文件句柄是私有的,其它进程获取不到;它又另外以只读的权限打开这个文件,并把这个句柄存储在环境变量中,其它进程可以获取到,所以其它进程可读但不可写;其它进程要想写,必须通过socket通信的方式请求init去执行写操作,这样,init就能对权限进行严格的审查了。下面来分析代码吧:

typedef struct {
    void *data;
    size_t size;
    int fd;
} workspace;

//这里传入的size大小即为49664
static int init_workspace(workspace *w, size_t size)
{
    void *data;
    int fd;

    //首先,以可读可写的方式打开/dev/__properties__这个文件(如果文件不存在,则创建之)
    fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);
    //下面这个函数的功能是裁剪某个文件的大小为固定大小,这样之后,这个文件的大小就为49664B了
    ftruncate(fd, size);
    //这个方法只是把这个文件映射到我们的进程空间中,这样的好处是我们可以像操作内存一样操作这个文件,这里的data就是真正存放我们系统属性值的空间
    //留意,这里映射时是可读可写的,所以init进程一定不能把这个data公开出去;另外,MAP_SHARED表明,对这块内存的修改会同步到对应的文件中。
    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    //关闭文件,目的是为了以另一种只读的方式重新打开文件
    close(fd);

    //重新以只读的方式打开这个文件,当然这里是只读的
    fd = open("/dev/__properties__", O_RDONLY);
    //unlink类似remove,执行之后,其实我们在/dev目录下,已经看不到__properties__这个文件了,这样能够避免其它程序再open这个文件
    //但是这块空间还没有被释放,仍然可以使用,直到close这个fd,但是似乎不需要close了,知道手机关机
    unlink("/dev/__properties__");

    //这里不止保存了最重要的data区域,还保存了fd和size,其它进程可以通过下面的get_property_workspace来获得fd和size,得到了fd就能够操作这个文件(只读方式)
    w->data = data;
    w->size = size;
    w->fd = fd;
    return 0;
}


static int init_property_area(void)
{
   ......
   init_workspace(&pa_workspace, PA_SIZE);
   ......
}

void get_property_workspace(int *fd, int *sz)
{
    *fd = pa_workspace.fd;
    *sz = pa_workspace.size;
}

在init.c的service_start方法中,通过调用get_property_workspace来得到文件/dev/__properties__的id,并将其设置到环境变量中
get_property_workspace(&fd, &sz);
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);

在system_properties.c的__system_properties_init方法中会从环境变量中将fd和size取出,从而能够读这个文件:
int __system_properties_init(void)
{
    env = getenv("ANDROID_PROPERTY_WORKSPACE");
    fd = atoi(env);
    env = strchr(env, ',');
    sz = atoi(env + 1);
    
    pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
}

下图中,fd=8,size=49664



2.对读写的异步控制

/*
** Rules:
** - there is only one writer, but many readers
** - prop_area.count will never decrease in value
** - once allocated, a prop_info's name will not change
** - once allocated, a prop_info's offset will not change
** - reading a value requires the following steps
**   1. serial = pi->serial
**   2. if SERIAL_DIRTY(serial), wait*, then goto 1
**   3. memcpy(local, pi->value, SERIAL_VALUE_LEN(serial) + 1)
**   4. if pi->serial != serial, goto 2
**
** - writing a value requires the following steps
**   1. pi->serial = pi->serial | 1
**   2. memcpy(pi->value, local_value, value_len)
**   3. pi->serial = (value_len << 24) | ((pi->serial + 1) & 0xffffff)
**
*/
文件中的注释已经很清晰了,这里简单解释以下:

只有一个程序写,但是有很多程序读;

系统属性值的数量只会不断增加,一旦某个属性值写入,是无法将它删除的,且已经写入的属性值的偏移量也不会改变;

按照如下方式控制读写:

写的过程--先将serial与1或(pi->serial = (valuelen << 24) 所以serial的最高一个字节存储的是value的长度,而其余24位则为0),这样最低位为1,表示正在写,那么它就是dirty的,此时不应该读;写结束之后,再+1则最低位为0,那就不再是dirty的,可以读了。

读的过程--先看最低位是否为1(1则表示dirty),那么这是就要陷入while循环,一直等;循环出来后,就可以读了,可是如果读完之后发现serial跟之前的不一样了,那么说明在读的时候又有更新了,因此还得重新读,不能退出for循环。

要了解控制读写的细节,还请留意__system_property_read方法中的__futex_wait和update_prop_info方法中的__futex_wake的使用。

另:当某个系统属性是第一次add进来时,可以直接写,不用这么复杂,而只有更新某个系统属性时,才需要注意控制异步的过程。


3.利用socket通信set系统属性

对这一块不是很了解,给出大致流程:

property_service.c中的start_property_service方法创建服务端的socket。

void start_property_service(void)
{
    ......
    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
    listen(fd, 8);
}


system_properties.c中的send_prop_msg方法连接到server端,并发送消息

static int send_prop_msg(prop_msg *msg)
{
	struct sockaddr_un addr;

    s = socket(AF_LOCAL, SOCK_STREAM, 0);

    memset(&addr, 0, sizeof(addr));
    namelen = strlen(property_service_socket);
    strlcpy(addr.sun_path, property_service_socket, sizeof addr.sun_path);
    addr.sun_family = AF_LOCAL;
    alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;

    connect(s, (struct sockaddr *) &addr, alen);
    send(s, msg, sizeof(prop_msg), 0);
	......
}
至于服务端何时会处理client的请求,不很了解,服务端(init进程)在一个无限for循环中会不断地探测是否有消息来到,如果有,则会调用handle_property_set_fd()去处理系统属性的set:

nr = poll(ufds, fd_count, timeout);
handle_property_set_fd();





三、属性值的产生和获取

四、eng版本中对开发者开放权限


你可能感兴趣的:(SystemProperty小结(Android4.2))