🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
我们知道,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; } ~~~ 至此,属性服务器就介绍完了。总体来说,还算比较简单。