欢迎转载,转载请注明: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属性文件:

init进程【4】——属性服务_第1张图片

在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 mainon 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);
看到这里,接下来会调到那里去,相信大家应该都清楚了。。。


更多相关文章

  1. switchpreference属性
  2. 没事抽空学——常用界面组件属性
  3. Android 中RelativeLayout各个属性的含义
  4. 边缘图片android布局属性详解
  5. androidUI设计之旅 ----布局文件属性解析
  6. 理解Android进程创建流程
  7. TextView中ellipsize属性 + 走马灯效果

随机推荐

  1. 1 如何引用第三方滑动验证码
  2. spring-boot-2.0.3启动源码篇四 - run方
  3. 记一次用WPScan辅助***WordPress站点
  4. 机器学习方向企业面试题(三)
  5. Spring Cloud Netflix Hystrix介绍和使用
  6. Ocelot简易教程(四)之请求聚合以及服务发现
  7. 【jQuery基础入门】顶级对象$()和jQuery
  8. Ansible批量更新远程主机用户密码 - 运维
  9. 【DB笔试面试94】在MySQL中,VARCHAR与CHAR
  10. Oracle分区表之分区范围扫描(PARTITION RA