mmap那些事之android property实现

来源:互联网 发布:mac怎么切换文件 编辑:程序博客网 时间:2024/04/30 00:03

mmap的概论

mmap的一大应用就是将内核空间的一段内存映射到各个应用程序的各自的应用地址空间中,然后各个应用程序都可以访问这段内存空间,这就是所谓的内存共享实现进程间的信息的交互。类似于内核的读写锁一样,应用进程对共享内存的访问分为两种:一种是读,一种是写。所有进程的读可以同时并发的访问同一个内存地址,但写跟读是互斥的,即我在读某个内存地址的时候,不能有写的操作,写操作相对于读操作有更高的优先权。并且所有进程对同一个地址的写操作都是互斥的。所以共享内存的实现关键是访问的同步控制。

那么android的property的实现则是利用mmap实现内存共享的一个经典应用实例。android的property为了实现各进程对共享内存写操作的同步,他规定所有对属性变量的写操作请求都会通过socket通讯发送至init后台服务,由init后台服务来处理所有进程的属性变量写请求,这样尽管各个进程可以随意甚至并发的调用设置属性变量的接口,但实际对共享内存进行写操作时,则只会有init后台服务一个入口,在这个init后台服务入口里,实现序列化的写操作,这样就保证了对共享内存的写操作的互斥。

至于属性读跟属性写的互斥则会在后面的代码中有详细介绍

android的property的实现

android的属性系统对应的共享内存所属的文件是/dev/__properties__,在讲详细实现之前,我们先看下该段共享内存映射到各个应用程序的地址空间的分布情况。先看/init进程,执行命令:cat /proc/1/maps


从上面黑色高亮部分,我们看出,这段共享内存映射到/init进程的[b6fef000,b6ff7000]地址空间,该地址空间是属于应用空间(<3G),并且映射的空间大小是32KB(0x8000).我们应该容易想到,所有使用或支持android 属性的进程,都应该映射文件/dev/__properties__对应的共享内存,为了证实这个问题,我们可以看下netd进程的地址空间分布,执行:cat /proc/77(netd pid)/maps:


从以上映射分布情况,我们可以看出,/dev/__properties__被映射到[402c9000,402d1000]虚拟地址空间,大小同样是32KB

再看busybox中的sh进程的maps,则发现并没有/dev/__properties__文件的映射,所以busybox的sh是不支持android的属性操作的。


再看android的shell进程/system/bin/sh,他就是包含了对/dev/__properties__文件的映射,所以and'r'oid的shell(即ash)是支持属性操作的。


那么应用程序是在什么地方对/dev/__properties__文件进行映射的呢?

在这之前先说明下共享内存的数据结构,先上图:



主要有三个重要的数据结构:

static workspace pa_workspace


static prop_info *pa_info_array;


extern prop_area *__system_property_area__;


他们之间的关系如上图所示,__system_property_area__是共享内存的头,长度为8个长字,toc数据的构成为(4个字节):高位字节是属性名字的长度,低三位字节是对应的属性info结构相对共享内存开始抵制的偏移量。pa_info_array是属性info的数组,每个成员的长度是固定为128bytes,所以整个属性空间字节长度为:

/* (8 header words + 247 toc words) = 1020 bytes */
/* 1024 bytes header and toc + 247 prop_infos @ 128 bytes = 32640 bytes */

在应用空间第一次映射共享内存的代码如下:


line184展开如下:


至此init进程已经建立了与/dev/__properties__文件所拥有的共享内存的关联,这个时候/dev/__properties__文件在内核空间只是为该init进程分配了一段大小为32KB的虚拟地址,由于还没有实际的属性写操作发生,所以这个时候还未分配实际的物理内存跟这段虚拟地址对应起来,关于这个过程的详细信息,我以后会详细的解说。

由于当前只是为init进程建立了共享内存的映射,当前我更关心的是,其他进程是怎么样来映射这段共享内存的呢?

问题的关键在system/core/init/init.c文件中的void service_start(struct service *svc, const char *dynamic_args)函数的如下片段:


line322 将共享内存对应的文件句柄和共享内存大小保存在ANDROID_PROPERTY_WORKSPACE环境变量中。这样所有init进程的子进程都可以取得这两个值,而这两个值为其他进程映射这块共享内存,已经提供足够条件。

那么其他进程是如何使用这两个值的呢?见如下代码段:


以上函数在c库被加载的时候就会被调用,所以所有依赖于c库且是init进程的子进程(或子子进程)的应用进程都可以针对属性系统进行操作。说到这,回到之前我们看到的busybox的shell为什么没有映射这个共享内存,因为它没有依赖android的libc库,所以不支持android的属性操作。

前面说过,属性的多个读操作可以并发进行,但读操作和写操作则只能序列化的进行。详细说明如下,首先见属性的写操作如下:


关键是上面的line379行,展开如下:


需要注意的是,上面在开始更新属性值时,会将每个属性对应的serial值的bit0置成1,操作完后会将serial+1,这样的话,属性写操作完成后,serial值总是一个偶数,并且更新完成后,该serial值相对于更新前的serial值>=1

结合属性的读操作函数:


结合上面函数的注释,应该不难明白属性读操作跟属性写操作是如何实现互斥的。

下一篇,我将详细讲下tmpfs文件系统下的/dev/__properties__文件是如何响应mmap系统调用的。