《深入理解Android(卷1)》笔记 3.第三章 深入理解init
2012-12-03 17:00 ...平..淡... 阅读(861) 评论(0) 编辑 收藏 举报知识点7:属性服务(与注册表机制机制类似)
在init.c中和属性服务相关的代码如下:
//调用property_init_action方法,该方法调用property_init方法 queue_builtin_action(property_init_action, "property_init"); //调用property_service_init_action方法,该方法会调用start_property_service方法 queue_builtin_action(property_service_init_action, "property_service_init");
下面来分析它们。
1. 属性服务的初始化
1.1创建存储空间
首先看property_init方法
void property_init(void) { init_property_area(); //初始化属性存储区域 load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT); //加载default.prop文件 }
property_init方法首先调用init_property_area()方法创建一块用于存储属性的储存区域,然后调用load_properties_from_file方法加载default.prop文件中的内容。
init_property_area方法
staticintinit_property_area(void) { prop_area *pa; if(pa_info_array) return -1; /* a. PA_SIZE描述存储空间的总大小,为32768字节,pa_workspace是workspace结构体的对象。 workspace结构体定义: typedef struct { void *data; size_t size; int fd; }workspace; b. init_workspace函数调用ashmem_create_region函数创建一块共享内存。 */ if(init_workspace(&pa_workspace,PA_SIZE)) return -1; //设置共享内存执行结束后关闭 fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); //在PA_SIZE大小的存储空间中,有PA_INFO_START(1024)个字节用来存储头部信息。 pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START); pa = pa_workspace.data; memset(pa, 0, PA_SIZE); //初始化内存地址中所有值为0 pa->magic = PROP_AREA_MAGIC; pa->version = PROP_AREA_VERSION; //设置属性共享内存版本号 /* plug into the lib property services */ __system_property_area__ = pa; //(e)很重要 property_area_inited = 1; return 0; }
(e) __system_property_area__是bionic libc库中的一个变量,是属性空间的入口地址。
虽然属性区域是init进程创建的,但是android希望其他进程也能读取这块内存中的东西。为了达到此目的,需要做一下工作:
(1)把属性区域创建在共享内存中上,因为共享内存是跨进程的。(这个共享内存由init_workspace函数内部调用的ashmem_create_region函数创建)
(2)让其他进程知道这个共享内存。
方法: 原理:利用gcc的constructor属性,这个属性指明了一个__libc_prenit函数(这个函数内部就将完成共享内存到本地进程的映射工作)。 用法:当bionic libc库被加载时,将自动调用__libc_prenit函数。
这样在bionic libc动态库被装载时,系统属性缓冲区地址就被确定了,后续的API调用就能找对位置了。
1.2 客户端进程获取储存空间
/* We flag the __libc_preinit function as a constructor to ensure * that its address is listed in libc.so's .init_array section. * This ensures that the function is called by the dynamic linker * as soon as the shared library is loaded. */ void __attribute__((constructor)) __libc_prenit(void); //---------------------------------------------------- void __libc_prenit(void) { ... __libc_init_common(elfdata); //调用此函数 ... } //---------------------------------------------------- void __libc_init_common(uintptr_t *elfdata) { int argc = *elfdata; char** argv = (char**)(elfdata + 1); char** envp = argv + argc + 1; pthread_attr_t thread_attr; static pthread_internal_t thread; static void* tls_area[BIONIC_TLS_SLOTS]; /* setup pthread runtime and maint thread descriptor */ unsigned stacktop = (__get_sp() & ~(PAGE_SIZE - 1)) + PAGE_SIZE; unsigned stacksize = 128 * 1024; unsigned stackbottom = stacktop - stacksize; pthread_attr_init(&thread_attr); pthread_attr_setstack(&thread_attr, (void*)stackbottom, stacksize); _init_thread(&thread, gettid(), &thread_attr, (void*)stackbottom); __init_tls(tls_area, &thread); /* clear errno - requires TLS area */ errno = 0; /* set program name */ __progname = argv[0] ? argv[0] : "<unknown>"; /* setup environment pointer */ environ = envp; /* setup system properties - requires environment */ __system_properties_init(); //初始化客户端的属性存储空间 } //------------------------------------------------------ int __system_properties_init(void) { prop_area *pa; int s, fd; unsigned sz; char *env; if(__system_property_area__ != ((void*) &dummy_props)) { return 0; } //取出前面曾添加的环境变量(属性存储区域的相关信息就是在那儿添加的)。 env = getenv("ANDROID_PROPERTY_WORKSPACE");//获取环境变量值 if (!env) { return -1; } fd = atoi(env);//获取fd env = strchr(env, ','); if (!env) { return -1; } sz = atoi(env + 1);//获取size
/*
映射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; return 0; }
通过这种方式,客户端可以直接读取属性空间,但没有权限设置属性。客户端进程又是如何设置属性的呢?客户端只能通过与属性服务器交互来设置属性。
如何交互呢,可以归纳成:
属性客户端
调用property_set("ct1.start","bootanim")方法,该方法调用send_prop_msg方法通过普通的TCP(SOCK_STREAM)进行通讯。
服务端
a. init.c的main方法中,调用start_property_service方法(b) 和 handle_property_set_fd方法(c)。 b. 创建SOCK_STREAM套接字;调用listen方法监听 c. 接收socket连接请求;接收请求数据;处理属性请求数据(switch代码段)
下面先来分析服务端的执行过程。
2.属性服务器的分析
2.1 启动属性服务器
init进程会启动一个属性服务器,而客户端只能通过与属性服务器交互来设置属性。属性服务器由start_property_service函数启动。
void start_property_service(void) { int fd; /*
加载属性文件,其实就是解析这些文件中的属性,然后把它设置到属性空间中去。以下为android提供是四个存储属性文件: /default.prop(已在property_init方法中加载过了) 、/system/build.prop、/system/default.prop、/data/local.prop */ load_properties_from_file(PROP_PATH_SYSTEM_BUILD);// "/system/build.prop" load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);//"/system/default.prop" load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);//"/data/local.prop" // 有一些属性是需要保存到永久介质上的,由下面这个函数加载。这些文件存储在/data/property目录下,文件名必须以persist.开头。 load_persistent_properties(); //创建一个socket,用于IPC通信。 fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);//"property_service" if(fd < 0) return; fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, O_NONBLOCK); listen(fd, 8); property_set_fd = fd; }
这个socket在哪里被处理呢?事实上是在init中的for循环处已经进行相关处理了。在2.2中分析。
2.2 处理设置属性请求
接收请求的地方在init进程for循环中。
if (ufds[i].revents == POLLIN) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd();
当属性服务器接收到客户端请求后,会调用handle_property_set_fd方法进行处理。代码如下:
void handle_property_set_fd() { prop_msg msg; int s; int r; int res; struct ucred cr; struct sockaddr_un addr; socklen_t addr_size = sizeof(addr); socklen_t cr_size = sizeof(cr); //先接收TCP连接 if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) { return; } /*取出客户端进程的权限等属性*/ if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { close(s); ERROR("Unable to recieve socket options\n"); return; } //接收请求数据 r = recv(s, &msg, sizeof(msg), 0); close(s); if(r != sizeof(prop_msg)) { ERROR("sys_prop: mis-match msg size recieved: %d expected: %d errno: %d\n", r, sizeof(prop_msg), errno); return; } switch(msg.cmd) { case PROP_MSG_SETPROP: msg.name[PROP_NAME_MAX-1] = 0; msg.value[PROP_VALUE_MAX-1] = 0; /* 如果是ct1开头的消息,则认为是控制消息,控制消息用来执行一些命令。例如用adb shell登陆后,输入setprop ct1.start bootanim就可以查看开机动画了,如果要关闭就输入setprop ct1.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 { ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n", msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid); } } else { //检查客户端是否有足够的权限 if (check_perms(msg.name, cr.uid, cr.gid)) { //然后调用property_set设置 property_set((char*) msg.name, (char*) msg.value); } else { ERROR("sys_prop: permission denied uid:%d name:%s\n", cr.uid, msg.name); } } break; default: break; } }
然后当客户端的权限满足要求时,init就调用property_set进行相关处理。代码如下:
int property_set(const char *name, const char *value) { prop_area *pa; prop_info *pi; int namelen = strlen(name); int valuelen = strlen(value); if(namelen >= PROP_NAME_MAX) return -1; if(valuelen >= PROP_VALUE_MAX) return -1; if(namelen < 1) return -1; //从属性存储空间中寻找是否已经存在该属性。 pi = (prop_info*) __system_property_find(name); if(pi != 0) { //如果属性名以ro.开头,则表示是只读的,不能设置,所以直接返回。 /* ro.* properties may NEVER be modified once set */ 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项属性,如果达到上限,则直接返回。 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 name starts with "net." treat as a DNS property. */ if (strncmp("net.", name, strlen("net.")) == 0) { if (strcmp("net.change", name) == 0) { return 0; } /* * The 'net.change' property is a special property used track when any * 'net.*' property name is updated. It is _ONLY_ updated here. Its value * contains the last updated 'net.*' property. */ property_set("net.change", name); } else if (persistent_properties_loaded && strncmp("persist.", name, strlen("persist.")) == 0) { /* * Don't write properties to disk until after we have read all default properties * to prevent them from being overwritten by default values. */ //如果属性以persist.开头,则需要把这些值写到对应的文件中去 write_persistent_property(name, value); } /*init.rc中执行start adbd命令的话,如下: on property:persist.service.adb.enable=1 start adbd 则会调用property_changed方法来完成。 */ property_changed(name, value); return 0;
属性服务器的工作介绍完毕了,下面再看客户端是如何执行的。
3.客户端发送请求
客户端通过property_set发送请求(此 property_set与上面的 property_set不同,它是由libcutils库提供的),代码如下:
int property_set(const char *key, const char *value) { prop_msg msg; unsigned resp; if(key == 0) return -1; if(value == 0) value = ""; if(strlen(key) >= PROP_NAME_MAX) return -1; if(strlen(value) >= PROP_VALUE_MAX) return -1; msg.cmd = PROP_MSG_SETPROP; //设置消息码为PROP_MSG_SETPROP. strcpy((char*) msg.name, key); strcpy((char*) msg.value, value); return send_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); return r; }
over...