欢迎转载,转载请注明:http://blog.csdn.net/zhgxhuaa
Android中的属性主要用来保存一些全局性的信息,这里可以理解为Android中的“注册表”。Android中的属性服务只针对系统开发者使用,并不对应用开发者开发,这通过SystemProperties是hide的可以看出。下面让我们一起来剖析属性服务。
初始化属性空间
在init进程启动一文中我们讲到,init的其中一个作用就是启动系统属性服务。在init进程的main()函数中有这样一句:
@system/core/init/init.c
property_init();//属性服务初始化
property_init()的实现如下:
@system/core/init/property_service.c
void property_init(void) { init_property_area(); }
从字面意思来看init_property_area是用来初始化属性存储区域,让我们来看一下:
@system/core/init/property_service.c
static int init_property_area(void) { if (property_area_inited)//属性区域已初始化则直接返回,不再初始化 return -1; if(__system_property_area_init())//通过mmap创建共享内存 return -1; if(init_workspace(&pa_workspace, 0))//初始化pa_workspace return -1; fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); property_area_inited = 1; return 0; }
通过上面的代码我们可以知道,属性服务是创建在共享内存上的,通过共享内存实现跨进程的访问。那共享内存是如何创建的呢?来看一下__system_property_area_init的实现:
@/bionic/libc/bionic/system_properties.c
int __system_property_area_init() { return map_prop_area_rw(); }
static int map_prop_area_rw() { prop_area *pa; int fd; int ret; /* dev is a tmpfs that we can use to carve a shared workspace * out of, so let's do that... */ fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);//打开property_filename文件,即:/dev/__properties__ if (fd < 0) { if (errno == EACCES) { /* for consistency with the case where the process has already * mapped the page in and segfaults when trying to write to it */ abort(); } return -1; } ret = fcntl(fd, F_SETFD, FD_CLOEXEC);//这里设置为FD_CLOEXEC表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递给exec创建的新进程 if (ret < 0) goto out; if (ftruncate(fd, PA_SIZE) < 0)//更改fd指向文件的大小为PA_SIZE(128 * 1024)大小 goto out; pa_size = PA_SIZE;//设置属性空间的大小为PA_SIZE(128 * 1024) pa_data_size = pa_size - sizeof(prop_area);//属性空间中可以用来保存属性的区域的大小,prop_area用来保存属性空间自身的一些信息 compat_mode = false; pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//映射属性文件到进程 if(pa == MAP_FAILED) goto out; memset(pa, 0, pa_size);//初始化属性区域 pa->magic = PROP_AREA_MAGIC; pa->version = PROP_AREA_VERSION; /* reserve root node */ pa->bytes_used = sizeof(prop_bt); /* plug into the lib property services */ __system_property_area__ = pa; close(fd); return 0; out: close(fd); return -1; }
上面的内容比较简单,不过最后的赋值语句可是大有来头。_system_property_area_是bionic libc库中输出的一个变量,为什么这里要给她赋值呢?
原来,虽然属性区域是由init进程创建的,但是Android系统希望其他进程也能够读取这块内存的东西。为了做到这一点,它便做了一下两项工作:
- 把属性区域创建在共享内存上,而共享内存是可以跨进程的。
- 如何让其他进程知道这个共享内存呢?Android利用gcc的constructor属性,这个属性指明了一个_libc_prenit函数,当bionic libc库被加载时,将自动调用这个_libc_prenit,这个函数内部完成共享内存到本地进程的映射工作。(这一段说明转自:《深入理解Android:卷1》)
关于上面的内容来看下面的代码:
@/bionic/libc/bionic/libc_init_dynamic.cpp
// 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. __attribute__((constructor)) static void __libc_preinit() { // Read the kernel argument block pointer from TLS. void* tls = const_cast<void*>(__get_tls()); KernelArgumentBlock** args_slot = &reinterpret_cast<KernelArgumentBlock**>(tls)[TLS_SLOT_BIONIC_PREINIT]; KernelArgumentBlock* args = *args_slot; // Clear the slot so no other initializer sees its value. // __libc_init_common() will change the TLS area so the old one won't be accessible anyway. *args_slot = NULL; __libc_init_common(*args); // Hooks for the debug malloc and pthread libraries to let them know that we're starting up. pthread_debug_init(); malloc_debug_init(); }
@/bionic/libc/bionic/libc_init_common.cpp
void __libc_init_common(KernelArgumentBlock& args) { // Initialize various globals. environ = args.envp; errno = 0; __libc_auxv = args.auxv; __progname = args.argv[0] ? args.argv[0] : "<unknown>"; __abort_message_ptr = args.abort_message_ptr; // AT_RANDOM is a pointer to 16 bytes of randomness on the stack. __stack_chk_guard = *reinterpret_cast<uintptr_t*>(getauxval(AT_RANDOM)); // Get the main thread from TLS and add it to the thread list. pthread_internal_t* main_thread = __get_thread(); main_thread->allocated_on_heap = false; _pthread_internal_add(main_thread); __system_properties_init(); // Requires 'environ'. }
@/bionic/libc/bionic/system_properties.c
int __system_properties_init() { return map_prop_area(); }
static int map_prop_area() { bool fromFile = true; int result = -1; int fd; int ret; fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd >= 0) { /* For old kernels that don't support O_CLOEXEC */ ret = fcntl(fd, F_SETFD, FD_CLOEXEC); if (ret < 0) goto cleanup; } if ((fd < 0) && (errno == ENOENT)) { /* * For backwards compatibility, if the file doesn't * exist, we use the environment to get the file descriptor. * For security reasons, we only use this backup if the kernel * returns ENOENT. We don't want to use the backup if the kernel * returns other errors such as ENOMEM or ENFILE, since it * might be possible for an external program to trigger this * condition. */ fd = get_fd_from_env(); fromFile = false; } if (fd < 0) { return -1; } struct stat fd_stat; if (fstat(fd, &fd_stat) < 0) { goto cleanup; } if ((fd_stat.st_uid != 0) || (fd_stat.st_gid != 0) || ((fd_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) || (fd_stat.st_size < sizeof(prop_area)) ) { goto cleanup; } pa_size = fd_stat.st_size; pa_data_size = pa_size - sizeof(prop_area); prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0); if (pa == MAP_FAILED) { goto cleanup; } if((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION && pa->version != PROP_AREA_VERSION_COMPAT)) { munmap(pa, pa_size); goto cleanup; } if (pa->version == PROP_AREA_VERSION_COMPAT) { compat_mode = true; } result = 0; __system_property_area__ = pa; cleanup: if (fromFile) { close(fd); } return result; }
对比上面map_prop_area_rw()和map_prop_area()这两个方法,我们可以发现他们在打开文件和进行映射时的不同。在map_prop_area_rw()中:
static int map_prop_area_rw() { ...... fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444); ...... pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//映射属性文件到进程 ...... }
在map_prop_area()中:
static int map_prop_area() { ...... fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); ...... prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0); ...... }
map_prop_area_rw()是init进程初始化属性服务时调用到的;map_prop_area()则是其他进程通过bionic初始化时调用的。通过对比,也说明只有init进程才拥有属性的写权限,其他进程只能读。好了,到这里又引出来另外一个问题,其他进程要想设置属性,应该怎么做呢?
属性服务
启动属性服务
在init进程的main()中,我们可以看见如下语句:
@system/core/init/init.c
queue_builtin_action(property_service_init_action, "property_service_init");
这句话的意思是启动action链表中的property服务:
static int property_service_init_action(int nargs, char **args) { /* read any property files on system or data and * fire up the property service. This must happen * after the ro.foo properties are set above so * that /data/local.prop cannot interfere with them. */ start_property_service(); return 0; }
start_property_service()的实现如下:
@system/core/init/property_service.c
void start_property_service(void) { int fd; load_properties_from_file(PROP_PATH_SYSTEM_BUILD); load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT); load_override_properties(); /* Read persistent properties after all default values have been loaded. */ load_persistent_properties(); fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0); if(fd < 0) return; fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, O_NONBLOCK); listen(fd, 8); property_set_fd = fd; }
关于这段代码做一些说明:
在Android中定义了5个存储属性的文件,它们分别是:
@/bionic/libc/includes/sys/_system_properties.h
#define PROP_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" #define PROP_PATH_FACTORY "/factory/factory.prop"
load_persistent_properties()用来加载persist开头的属性文件,这些属性文件是需要保存到永久介质上的,这些属性文件存储在/data/property目录下,并且文件名必须以“psesist."开头,下面是我的手机上的persist属性文件:
在start_property_service()的最后创建了一个名为"property_service"的socket,启动监听,并将socket的句柄保存在property_set_fd中。
处理设置属性请求
接收属性设置请求的地方在init进程中:
nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i++) { if (ufds[i].revents == POLLIN) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd()) handle_keychord(); else if (ufds[i].fd == get_signal_fd()) handle_signal(); } } }
可以看到,在init接收到其他进程设置属性的请求时,会调用handle_property_set_fd()函数进程处理:
@system/core/init/property_service.c
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); char * source_ctx = NULL; if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) { return; } /* Check socket options here */ if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { close(s); ERROR("Unable to receive socket options\n"); return; } r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0)); if(r != sizeof(prop_msg)) { ERROR("sys_prop: mis-match msg size received: %d expected: %d errno: %d\n", r, sizeof(prop_msg), errno); close(s); return; } switch(msg.cmd) { case PROP_MSG_SETPROP: msg.name[PROP_NAME_MAX-1] = 0; msg.value[PROP_VALUE_MAX-1] = 0; if (!is_legal_property_name(msg.name, strlen(msg.name))) { ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name); close(s); return; } getpeercon(s, &source_ctx); if(memcmp(msg.name,"ctl.",4) == 0) { // Keep the old close-socket-early behavior when handling // ctl.* properties. close(s); if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) { 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, source_ctx)) { property_set((char*) msg.name, (char*) msg.value); } else { ERROR("sys_prop: permission denied uid:%d name:%s\n", cr.uid, msg.name); } // Note: bionic's property client code assumes that the // property server will not close the socket until *AFTER* // the property is written to memory. close(s); } freecon(source_ctx); break; default: close(s); break; } }
从上面的代码可以看出在进行一系列检查和判断后,最后悔调用property_set()函数设置属性,其实现如下:
int property_set(const char *name, const char *value) { prop_info *pi; int ret; size_t namelen = strlen(name); size_t valuelen = strlen(value); if (!is_legal_property_name(name, namelen)) return -1; if (valuelen >= PROP_VALUE_MAX) return -1; pi = (prop_info*) __system_property_find(name); if(pi != 0) { /* ro.* properties may NEVER be modified once set */ if(!strncmp(name, "ro.", 3)) return -1; __system_property_update(pi, value, valuelen); } else { ret = __system_property_add(name, namelen, value, valuelen); if (ret < 0) { ERROR("Failed to set '%s'='%s'\n", name, value); return ret; } } /* 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. */ write_persistent_property(name, value); } else if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) { selinux_reload_policy(); } property_changed(name, value); return 0; }
从上面的代码可以看出:首先会判断所设置属性的属性名和属性值是否超过限制,这个限制定义如下:
@bionic/libc/includes/sys/system _properties.h
#define PROP_NAME_MAX 32 #define PROP_VALUE_MAX 92
可以看出属性名最长不能超过32个字符;属性值最长不能超过92个字符,到这里我们也可以根据之前map_prop_area_rw()中的信息推断出,系统总共可以支持多少个属性:
pa_size = PA_SIZE;//设置属性空间的大小为PA_SIZE(128 * 1024) pa_data_size = pa_size - sizeof(prop_area);//属性空间中可以用来保存属性的区域的大小,prop_area用来保存属性空间自身的一些信息 compat_mode = false;
按照这里的推断,系统支持1000多个属性的样子。这里与之前比较老的Android版本有所不同,在老的Android版本中系统最多支持的属性个数为247个。
在判断文新添加属性的合法性以后接下来会判断该属性是否以存在,如果已存在则更新属性的值即可,如果不存在则增加新的属性。
然后还有剩下的一些其他判断,这里就不再赘述。到时最后一句property_changed()引起了我的注意:
property_changed(name, value);
看到这里,你有没有想起些什么?是的,init.rc中类似于下面这样的句子就是在这里通过property_changed()触发的。
on property:vold.decrypt=trigger_restart_min_framework class_start main on property:vold.decrypt=trigger_restart_framework class_start main class_start late_start
OK,到这里属性服务相关的东东基本介绍完了。接下来简单介绍一下如何设置系统属性。
设置系统属性
C代码中属性的设置是由libcutils库提供的,代码如下:
@system/core/libutils/properties.c
int property_set(const char *key, const char *value) { return __system_property_set(key, value); }
@/bionic/libc/bionic/system_property
int __system_property_set(const char *key, const char *value) { int err; prop_msg msg; 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; memset(&msg, 0, sizeof msg); msg.cmd = PROP_MSG_SETPROP; strlcpy(msg.name, key, sizeof msg.name); strlcpy(msg.value, value, sizeof msg.value); err = send_prop_msg(&msg); if(err < 0) { return err; } return 0; }
注意这里的msg.cmd = PROP_MSG_SETPROP,send_prop_msg()的实现如下:
static int send_prop_msg(prop_msg *msg) { struct pollfd pollfds[1]; struct sockaddr_un addr; socklen_t alen; size_t namelen; int s; int r; int result = -1; s = socket(AF_LOCAL, SOCK_STREAM, 0); if(s < 0) { return result; } 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; if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen)) < 0) { close(s); return result; } r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0)); if(r == sizeof(prop_msg)) { // We successfully wrote to the property server but now we // wait for the property server to finish its work. It // acknowledges its completion by closing the socket so we // poll here (on nothing), waiting for the socket to close. // If you 'adb shell setprop foo bar' you'll see the POLLHUP // once the socket closes. Out of paranoia we cap our poll // at 250 ms. pollfds[0].fd = s; pollfds[0].events = 0; r = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */)); if (r == 1 && (pollfds[0].revents & POLLHUP) != 0) { result = 0; } else { // Ignore the timeout and treat it like a success anyway. // The init process is single-threaded and its property // service is sometimes slow to respond (perhaps it's off // starting a child process or something) and thus this // times out and the caller thinks it failed, even though // it's still getting around to it. So we fake it here, // mostly for ctl.* properties, but we do try and wait 250 // ms so callers who do read-after-write can reliably see // what they've written. Most of the time. // TODO: fix the system properties design. result = 0; } } close(s); return result; }
设置属性的请求在这里被通过socket发送出去。
Java层设置属性的方法位于SystemProperties类中,在该类中实现了一系列set/get方法,Java代码中就是通过这些set/get方法设置和读取系统属性的。
@/framework/base/core/ java/android/os/SystemProperties.java
/** * Set the value for the given key. * @throws IllegalArgumentException if the key exceeds 32 characters * @throws IllegalArgumentException if the value exceeds 92 characters */ public static void set(String key, String val) { if (key.length() > PROP_NAME_MAX) { throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); } if (val != null && val.length() > PROP_VALUE_MAX) { throw new IllegalArgumentException("val.length > " + PROP_VALUE_MAX); } native_set(key, val); }
注意到这里最终会调用到native的set方法。
private static native void native_set(String key, String def);
看到这里,接下来会调到那里去,相信大家应该都清楚了。。。