我们知道,Windows平台上有一个叫注册表的东西。注册表可以存储一些类似key/value的键值对。一般而言,系统或某些应用程序会把自己的一些属性存储在注册表中,即使下次系统重启或应用程序重启,它还能够根据之前在注册表中设置的属性,进行相应的初始化工作。Android平台也提供了一个类型机制,可称之为属性服务(property service)。应用程序可通过这个属性机制,查询或设置属性。读者可以用adb shell登录到真机或模拟器上,然后用getprop命令查看当前系统中有哪些属性。即如我的HTC G7测试结果,如图3-2所示:(图中只显示了部分属性)
:-: ![](http://img.blog.csdn.net/20150802095033071?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图3-2 HTC G7属性示意图
这个属性服务是怎么实现的呢?下面来看代码,其中与init.c和属性服务有关的代码有下面两行:
~~~
property_init();
property_set_fd = start_property_service();
~~~
分别来看看它们。
1. 属性服务初始化
(1)创建存储空间
先看property_init函数,代码如下所示:
**property_service.c**
~~~
void property_init(void)
{
init_property_area();//初始化属性存储区域
//加载default.prop文件
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
~~~
在properyty_init函数中,先调用init_property_area函数,创建一块用于存储属性的存储区域,然后加载default.prop文件中的内容。再看init_property_area是如何工作的,它的代码如下所示:
**property_service.c**
~~~
static int init_property_area(void)
{
prop_area *pa;
if(pa_info_array)
return -1;
/*
初始化存储空间,PA_SIZE是这块存储空间的总大小,为32768字节,pa_workspace
为workspace类型的结构体,下面是它的定义:
typedef struct {
void *data; //存储空间的起始地址
size_tsize; //存储空间的大小
int fd; //共享内存的文件描述符
} workspace;
init_workspace函数调用Android系统提供的ashmem_create_region函数创建一块
共享内存。关于共享内存的知识我们在第7章会接触,这里,只需把它当做一块普通的内存就
可以了。
*/
if(init_workspace(&pa_workspace, PA_SIZE))
return -1;
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
//在32768个字节的存储空间中,有PA_INFO_START(1024)个字节用来存储头部信息
pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);
pa =pa_workspace.data;
memset(pa, 0, PA_SIZE);
pa->magic = PROP_AREA_MAGIC;
pa->version = PROP_AREA_VERSION;
//__system_property_area__这个变量由bionic libc库输出,有什么用呢?
__system_property_area__ = pa;
return0;
}
~~~
上面的内容比较简单,不过最后的赋值语句可是大有来头。__system_property_area__是bionic libc库中输出的一个变量,为什么这里要给它赋值呢?
原来,虽然属性区域是由init进程创建,但Android系统希望其他进程也能读取这块内存里的东西。为做到这一点,它便做了以下两项工作:
- 把属性区域创建在共享内存上,而共享内存是可以跨进程的。这一点,已经在上面的代码中见到了,init_workspace函数内部将创建这个共享内存。
- 如何让其他进程知道这个共享内存呢?Android利用了gcc的constructor属性,这个属性指明了一个__libc_prenit函数,当bionic libc库被加载时,将自动调用这个__libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作。
(2)客户端进程获取存储空间
关于上面的内容,来看相关代码:
**libc_init_dynamic.c**
~~~
//constructor属性指示加载器加载该库后,首先调用__libc_prenit函数。这一点和Windows上
//动态库的DllMain函数类似
void __attribute__((constructor))__libc_prenit(void);
void __libc_prenit(void)
{
......
__libc_init_common(elfdata); //调用这个函数
......
}
~~~
__libc_init_common函数为:
**libc_init_common.c**
~~~
void __libc_init_common(uintptr_t *elfdata)
{
......
__system_properties_init();//初始化客户端的属性存储区域
}
~~~
**system_properties.c**
~~~
int __system_properties_init(void)
{
prop_area *pa;
int s,fd;
unsigned sz;
char*env;
.....
//还记得在启动zygote一节中提到的添加环境变量的地方吗?属性存储区域的相关信息
//就是在那儿添加的,这里需要取出来使用了。
env =getenv("ANDROID_PROPERTY_WORKSPACE");
//取出属性存储区域的文件描述符。关于共享内存的知识,第7章中将会进行介绍。
fd =atoi(env);
env =strchr(env, ',');
if(!env) {
return -1;
}
sz =atoi(env + 1);
//映射init创建的那块内存到本地进程空间,这样本地进程就可以使用这块共享内存了。
//注意,映射的时候指定了PROT_READ属性,所以客户端进程只能读属性,而不能设置属性。
pa =mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
if(pa== MAP_FAILED) {
return -1;
}
if((pa->magic != PROP_AREA_MAGIC) || (pa->version !=PROP_AREA_VERSION)) {
munmap(pa, sz);
return -1;
}
__system_property_area__ = pa;
return0;
}
~~~
上面代码中很多地方和共享内存有关,在第7章中会对与共享内存有关问题进行介绍,读者也可先行学习有关共享内存的知识。
总之,通过这种方式,客户端进程可以直接读取属性空间,但没有权限设置属性。客户端进程又是如何设置属性呢?
2. 启动属性服务器
(1)启动属性服务器
init进程会启动一个属性服务器,而客户端只能通过和属性服务器交互才能设置属性。先来看属性服务器的内容,它由start_property_service函数启动,代码如下所示:
**Property_servie.c**
~~~
int start_property_service(void)
{
intfd;
/*
加载属性文件,其实就是解析这些文件中的属性,然后把它设置到属性空间中去。Android系统
一共提供了四个存储属性的文件,它们分别是:
#definePROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
*/
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
//有一些属性是需要保存到永久介质上的,这些属性文件则由下面这个函数加载,这些文件
//存储在/data/property目录下,并且这些文件的文件名必须以persist.开头。这个函数
//很简单,读者可自行研究。
load_persistent_properties();
//创建一个socket,用于IPC通信。
fd =create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
if(fd< 0) return -1;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
listen(fd, 8);
returnfd;
}
~~~
属性服务创建了一个用来接收请求的socket,可这个请求在哪里被处理呢?事实上,在init中的for循环那里已经进行相关处理了。
(2)处理设置属性请求
接收请求的地方是在init进程中,代码如下所示:
**init.c::main函数片断**
~~~
if (ufds[1].revents == POLLIN)
handle_property_set_fd(property_set_fd);
~~~
当属性服务器收到客户端请求时,init会调用handle_property_set_fd进行处理。这个函数的代码如下所示:
**property_service.c**
~~~
void handle_property_set_fd(int fd)
{
prop_msg msg;
int s;
int r;
intres;
structucred cr;
structsockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
//先接收TCP连接
if ((s= accept(fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
//取出客户端进程的权限等属性。
if(getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
......
return;
}
//接收请求数据
r = recv(s,&msg, sizeof(msg), 0);
close(s);
......
switch(msg.cmd) {
casePROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
/*
如果是ctl开头的消息,则认为是控制消息,控制消息用来执行一些命令,例如用
adb shell登录后,输入setprop ctl.start bootanim就可以查看开机动画了,
关闭的话就输入setpropctl.stop bootanim,是不是很有意思呢?
*/
if(memcmp(msg.name,"ctl.",4) == 0) {
if (check_control_perms(msg.value, cr.uid, cr.gid)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
}
......
}else {
//检查客户端进程是否有足够的权限
if (check_perms(msg.name, cr.uid, cr.gid)) {
//然后调用property_set设置。
property_set((char*) msg.name, (char*) msg.value);
}
......
}
break;
default:
break;
}
}
~~~
当客户端的权限满足要求时,init就调用property_set进行相关处理,这个函数比较简单,代码如下所示:
**property_service.c**
~~~
int property_set(const char *name, const char*value)
{
prop_area *pa;
prop_info *pi;
intnamelen = strlen(name);
intvaluelen = strlen(value);
......
//从属性存储空间中寻找是否已经存在该属性
pi =(prop_info*) __system_property_find(name);
if(pi!= 0) {
//如果属性名以ro.开头,则表示是只读的,不能设置,所以直接返回。
if(!strncmp(name, "ro.", 3)) return -1;
pa= __system_property_area__;
//更新该属性的值
update_prop_info(pi, value, valuelen);
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
}else {
//如果没有找到对应的属性,则认为是增加属性,所以需要新创建一项。注意,Android支持
//最多247项属性,如果目前属性的存储空间中已经有247项,则直接返回。
pa= __system_property_area__;
if(pa->count == PA_COUNT_MAX) return -1;
pi= pa_info_array + pa->count;
pi->serial = (valuelen << 24);
memcpy(pi->name, name, namelen + 1);
memcpy(pi->value, value, valuelen +1);
pa->toc[pa->count] =
(namelen << 24) | (((unsigned) pi) - ((unsigned) pa));
pa->count++;
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
}
//有一些特殊的属性需要特殊处理,这里,主要是以net.change开头的属性。
if(strncmp("net.", name, strlen("net.")) == 0) {
if(strcmp("net.change", name) == 0) {
return 0;
}
property_set("net.change", name);
} elseif (persistent_properties_loaded &&
strncmp("persist.", name,strlen("persist.")) == 0) {
//如果属性名以persist.开头,则需要把这些值写到对应文件中去。
write_persistent_property(name, value);
}
/*
还记得init.rc中的下面这句话吗?
on property:persist.service.adb.enable=1
startadbd
当persist.service.adb.enable属性置为1后,就会执行start adbd这个command,
这是通过property_changed函数来完成的,它非常简单,读者可以自己阅读。
*/
property_changed(name, value);
return0;
}
~~~
好,属性服务端的工作已经了解了,下面看客户端是如何设置属性的。
(3)客户端发送请求
客户端通过property_set发送请求,property_set由libcutils库提供,代码如下所示:
**properties.c**
~~~
int property_set(const char *key, const char*value)
{
prop_msg msg;
unsigned resp;
......
msg.cmd = PROP_MSG_SETPROP;//设置消息码为PROP_MSG_SETPROP。
strcpy((char*) msg.name, key);
strcpy((char*) msg.value, value);
//发送请求
returnsend_prop_msg(&msg);
}
static int send_prop_msg(prop_msg *msg)
{
int s;
int r;
//建立和属性服务器的socket连接
s =socket_local_client(PROP_SERVICE_NAME,
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM);
if(s< 0) return -1;
//通过socket发送出去
while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
if((errno == EINTR) || (errno == EAGAIN)) continue;
break;
}
if(r== sizeof(prop_msg)) {
r= 0;
} else{
r= -1;
}
close(s);
returnr;
}
~~~
至此,属性服务器就介绍完了。总体来说,还算比较简单。
- 前言
- 第1章 阅读前的准备工作
- 1.1 系统架构
- 1.1.1 Android系统架构
- 1.1.2 本书的架构
- 1.2 搭建开发环境
- 1.2.1 下载源码
- 1.2.2 编译源码
- 1.3 工具介绍
- 1.3.1 Source Insight介绍
- 1.3.2 Busybox的使用
- 1.4 本章小结
- 第2章 深入理解JNI
- 2.1 JNI概述
- 2.2 学习JNI的实例:MediaScanner
- 2.3 Java层的MediaScanner分析
- 2.3.1 加载JNI库
- 2.3.2 Java的native函数和总结
- 2.4 JNI层MediaScanner的分析
- 2.4.1 注册JNI函数
- 2.4.2 数据类型转换
- 2.4.3 JNIEnv介绍
- 2.4.4 通过JNIEnv操作jobject
- 2.4.5 jstring介绍
- 2.4.6 JNI类型签名介绍
- 2.4.7 垃圾回收
- 2.4.8 JNI中的异常处理
- 2.5 本章小结
- 第3章 深入理解init
- 3.1 概述
- 3.2 init分析
- 3.2.1 解析配置文件
- 3.2.2 解析service
- 3.2.3 init控制service
- 3.2.4 属性服务
- 3.3 本章小结
- 第4章 深入理解zygote
- 4.1 概述
- 4.2 zygote分析
- 4.2.1 AppRuntime分析
- 4.2.2 Welcome to Java World
- 4.2.3 关于zygote的总结
- 4.3 SystemServer分析
- 4.3.1 SystemServer的诞生
- 4.3.2 SystemServer的重要使命
- 4.3.3 关于 SystemServer的总结
- 4.4 zygote的分裂
- 4.4.1 ActivityManagerService发送请求
- 4.4.2 有求必应之响应请求
- 4.4.3 关于zygote分裂的总结
- 4.5 拓展思考
- 4.5.1 虚拟机heapsize的限制
- 4.5.2 开机速度优化
- 4.5.3 Watchdog分析
- 4.6 本章小结
- 第5章 深入理解常见类
- 5.1 概述
- 5.2 以“三板斧”揭秘RefBase、sp和wp
- 5.2.1 第一板斧--初识影子对象
- 5.2.2 第二板斧--由弱生强
- 5.2.3 第三板斧--破解生死魔咒
- 5.2.4 轻量级的引用计数控制类LightRefBase
- 5.2.5 题外话-三板斧的来历
- 5.3 Thread类及常用同步类分析
- 5.3.1 一个变量引发的思考
- 5.3.2 常用同步类
- 5.4 Looper和Handler类分析
- 5.4.1 Looper类分析
- 5.4.2 Handler分析
- 5.4.3 Looper和Handler的同步关系
- 5.4.4 HandlerThread介绍
- 5.5 本章小结
- 第6章 深入理解Binder
- 6.1 概述
- 6.2 庖丁解MediaServer
- 6.2.1 MediaServer的入口函数
- 6.2.2 独一无二的ProcessState
- 6.2.3 时空穿越魔术-defaultServiceManager
- 6.2.4 注册MediaPlayerService
- 6.2.5 秋风扫落叶-StartThread Pool和join Thread Pool分析
- 6.2.6 你彻底明白了吗
- 6.3 服务总管ServiceManager
- 6.3.1 ServiceManager的原理
- 6.3.2 服务的注册
- 6.3.3 ServiceManager存在的意义
- 6.4 MediaPlayerService和它的Client
- 6.4.1 查询ServiceManager
- 6.4.2 子承父业
- 6.5 拓展思考
- 6.5.1 Binder和线程的关系
- 6.5.2 有人情味的讣告
- 6.5.3 匿名Service
- 6.6 学以致用
- 6.6.1 纯Native的Service
- 6.6.2 扶得起的“阿斗”(aidl)
- 6.7 本章小结
- 第7章 深入理解Audio系统
- 7.1 概述
- 7.2 AudioTrack的破解
- 7.2.1 用例介绍
- 7.2.2 AudioTrack(Java空间)分析
- 7.2.3 AudioTrack(Native空间)分析
- 7.2.4 关于AudioTrack的总结
- 7.3 AudioFlinger的破解
- 7.3.1 AudioFlinger的诞生
- 7.3.2 通过流程分析AudioFlinger
- 7.3.3 audio_track_cblk_t分析
- 7.3.4 关于AudioFlinger的总结
- 7.4 AudioPolicyService的破解
- 7.4.1 AudioPolicyService的创建
- 7.4.2 重回AudioTrack
- 7.4.3 声音路由切换实例分析
- 7.4.4 关于AudioPolicy的总结
- 7.5 拓展思考
- 7.5.1 DuplicatingThread破解
- 7.5.2 题外话
- 7.6 本章小结
- 第8章 深入理解Surface系统
- 8.1 概述
- 8.2 一个Activity的显示
- 8.2.1 Activity的创建
- 8.2.2 Activity的UI绘制
- 8.2.3 关于Activity的总结
- 8.3 初识Surface
- 8.3.1 和Surface有关的流程总结
- 8.3.2 Surface之乾坤大挪移
- 8.3.3 乾坤大挪移的JNI层分析
- 8.3.4 Surface和画图
- 8.3.5 初识Surface小结
- 8.4 深入分析Surface
- 8.4.1 与Surface相关的基础知识介绍
- 8.4.2 SurfaceComposerClient分析
- 8.4.3 SurfaceControl分析
- 8.4.4 writeToParcel和Surface对象的创建
- 8.4.5 lockCanvas和unlockCanvasAndPost分析
- 8.4.6 GraphicBuffer介绍
- 8.4.7 深入分析Surface的总结
- 8.5 SurfaceFlinger分析
- 8.5.1 SurfaceFlinger的诞生
- 8.5.2 SF工作线程分析
- 8.5.3 Transaction分析
- 8.5.4 关于SurfaceFlinger的总结
- 8.6 拓展思考
- 8.6.1 Surface系统的CB对象分析
- 8.6.2 ViewRoot的你问我答
- 8.6.3 LayerBuffer分析
- 8.7 本章小结
- 第9章 深入理解Vold和Rild
- 9.1 概述
- 9.2 Vold的原理与机制分析
- 9.2.1 Netlink和Uevent介绍
- 9.2.2 初识Vold
- 9.2.3 NetlinkManager模块分析
- 9.2.4 VolumeManager模块分析
- 9.2.5 CommandListener模块分析
- 9.2.6 Vold实例分析
- 9.2.7 关于Vold的总结
- 9.3 Rild的原理与机制分析
- 9.3.1 初识Rild
- 9.3.2 RIL_startEventLoop分析
- 9.3.3 RIL_Init分析
- 9.3.4 RIL_register分析
- 9.3.5 关于Rild main函数的总结
- 9.3.6 Rild实例分析
- 9.3.7 关于Rild的总结
- 9.4 拓展思考
- 9.4.1 嵌入式系统的存储知识介绍
- 9.4.2 Rild和Phone的改进探讨
- 9.5 本章小结
- 第10章 深入理解MediaScanner
- 10.1 概述
- 10.2 android.process.media分析
- 10.2.1 MSR模块分析
- 10.2.2 MSS模块分析
- 10.2.3 android.process.media媒体扫描工作的流程总结
- 10.3 MediaScanner分析
- 10.3.1 Java层分析
- 10.3.2 JNI层分析
- 10.3.3 PVMediaScanner分析
- 10.3.4 关于MediaScanner的总结
- 10.4 拓展思考
- 10.4.1 MediaScannerConnection介绍
- 10.4.2 我问你答
- 10.5 本章小结