美文网首页源码分析
Android系统属性set/get详解

Android系统属性set/get详解

作者: leon__zhao | 来源:发表于2018-01-24 22:37 被阅读0次

    基于 Android 6.0 的源码,分析 set 和 get 系统属性

    设置系统属性

    上一篇文章提到,设置系统属性调用 SystemProperties.set("key", "value"); 即可。那么就从这个方法开始。

    framework/base/core/java/android/os/SystemProperties.java

        public static final int PROP_NAME_MAX = 31;
        public static final int PROP_VALUE_MAX = 91;
    
        ......
    
        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);
        }
    

    这里只是校验一下 key 和 value 的长度是否超过 31 和 91, 然后调用 native_set

    framework/base/core/jni/android_os_SystemProperties.cpp

    static JNINativeMethod method_table[] = {
         ......
        // 关联 SystemProperties.java 中的 native_set 与 android_os_SystemProperties.cpp 中 SystemProperties_set 方法
        { "native_set", "(Ljava/lang/String;Ljava/lang/String;)V",
          (void*) SystemProperties_set },
        ......
    };
    
    
    static void SystemProperties_set(JNIEnv *env, jobject clazz,
                                          jstring keyJ, jstring valJ)
    {
        int err;
        const char* key;
        const char* val;
        // 如果来自 Java 层的 keyJ 不为空则将它转化为 native 层的 key
        if (keyJ == NULL) {
            jniThrowNullPointerException(env, "key must not be null.");
            return ;
        }
        key = env->GetStringUTFChars(keyJ, NULL);
        // 如果来自 Java 层的 valJ 不为空则将它转化为 native 层的 val
        if (valJ == NULL) {
            val = "";       /* NULL pointer not allowed here */
        } else {
            val = env->GetStringUTFChars(valJ, NULL);
        }
        // 调用 cutils/properties.h 中的 property_set
        err = property_set(key, val);
    
        env->ReleaseStringUTFChars(keyJ, key);
    
        if (valJ != NULL) {
            env->ReleaseStringUTFChars(valJ, val);
        }
    
        if (err < 0) {
            jniThrowException(env, "java/lang/RuntimeException",
                              "failed to set system property");
        }
    }
    

    android_os_SystemProperties.cpp 中显示对 key 和 value 判空然后由 jstring 转化为 char*,最后调用 property_set。这个 property_set 是哪里来的呢?在 android_os_SystemProperties.cpp 的顶部我们看到上篇提到的 #include "cutils/properties.h" 原来 Java 层其实没什么实际的动作,最后还是调用和 native 层提供的方法。

    system/core/cutils/properties.c

    #include <sys/_system_properties.h>
    
    int property_set(const char *key, const char *value)
    {
        return __system_property_set(key, value);
    }
    

    properties.c 中 什么都没干,直接调用了 __system_property_set,但是我们在 properties.c 没找到该方法的实现,还好 Google 的代码非常人性化,就在 property_set 的上面有 include 对应文件。然后我们兴高采烈的打开 _system_properties.h 发现里面根本没有申明 __system_property_set 方法,不过不要急,往上翻你会看到 #include <sys/system_properties.h>

    bionic/libc/system_properties.cpp

    int __system_property_set(const char *key, const char *value)
    {
        // 判空,校验长度
        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;
       
         // 创建并清空一个prop_msg ,然后给它设置 cmd key value
        prop_msg msg;
        memset(&msg, 0, sizeof msg);
        msg.cmd = PROP_MSG_SETPROP;
        strlcpy(msg.name, key, sizeof msg.name);
        strlcpy(msg.value, value, sizeof msg.value);
        // 通过socket向init进程的属性服务发送消息  
        const int err = send_prop_msg(&msg);
        if (err < 0) {
            return err;
        }
        return 0;
    }
    
    
    
    static int send_prop_msg(const prop_msg *msg)
    {
        const int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
        if (fd == -1) {
            return -1;
        }
    
        const size_t namelen = strlen(property_service_socket);
    
        sockaddr_un addr;
        memset(&addr, 0, sizeof(addr));
        strlcpy(addr.sun_path, property_service_socket, sizeof(addr.sun_path));
        addr.sun_family = AF_LOCAL;
        socklen_t alen = namelen + offsetof(sockaddr_un, sun_path) + 1;
        if (TEMP_FAILURE_RETRY(connect(fd, reinterpret_cast<sockaddr*>(&addr), alen)) < 0) {
            close(fd);
            return -1;
        }
    
        const int num_bytes = TEMP_FAILURE_RETRY(send(fd, msg, sizeof(prop_msg), 0));
    
        int result = -1;
        if (num_bytes == 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.
            pollfd pollfds[1];
            pollfds[0].fd = fd;
            pollfds[0].events = 0;
            const int poll_result = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
            if (poll_result == 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(fd);
        return result;
    }
    

    大致流程图下图:

    SystemProperties.set.png

    通过上面的源码,我们可以看到:

    1. Java层只是做了简单的判断 key 和 value 的长度,以及 value 是否为空,然后通过 JNI 调用 native 的方法
    2. native 层也进行了是否为空和长度判断,然后封装了一个 prop_msg 并通过 socket 发送给 init 进程

    对于属性设置的分析暂时就到这里,下一篇文章会分析系统属性的初始化,里面会涉及到 init 进程收到 prop_msg 具体会如何处理。

    获取系统属性

    不同于 set 方法只有一个,有好几个 get 方法,不过几个方法最终的实现都一样,这里我们以 getBoolean 为例:

    framework/base/core/java/android/os/SystemProperties.java

        public static boolean getBoolean(String key, boolean def) {
            if (key.length() > PROP_NAME_MAX) {
                throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
            }
            return native_get_boolean(key, def);
        }
    

    校验长度之后调用到 JNI 层中的 SystemProperties_get_boolean

    framework/base/core/jni/android_os_SystemProperties.cpp

    static jboolean SystemProperties_get_boolean(JNIEnv *env, jobject clazz,
                                          jstring keyJ, jboolean defJ)
    {
        int len;
        const char* key;
        char buf[PROPERTY_VALUE_MAX];
        jboolean result = defJ;
        // 如果 key  为空则跳到 error 返回默认值
        if (keyJ == NULL) {
            jniThrowNullPointerException(env, "key must not be null.");
            goto error;
        }
    
        key = env->GetStringUTFChars(keyJ, NULL);
        // 调用 property_get
        len = property_get(key, buf, "");
        // 如果长度为1,则 0 或者 n 代表 false,1 或者 y 代表 true
        // 如果长度大于1,则 no,false,off 代表false,yes,true,on 代表 true
        if (len == 1) { 
            char ch = buf[0];
            if (ch == '0' || ch == 'n')
                result = false;
            else if (ch == '1' || ch == 'y')
                result = true;
        } else if (len > 1) {
             if (!strcmp(buf, "no") || !strcmp(buf, "false") || !strcmp(buf, "off")) {
                result = false;
            } else if (!strcmp(buf, "yes") || !strcmp(buf, "true") || !strcmp(buf, "on")) {
                result = true;
            }
        }
    
        env->ReleaseStringUTFChars(keyJ, key);
    
    error:
        return result;
    }
    

    Java 层的各个 get 方法,在 JNI 对应的方法中统一掉了用 property_get 方法,然后对返回值进行处理,转换成 boolean,int,long..
    native 层的各个方法,同样也是调用了 property_get,然后对返回值进行转换。

    这里需要注意的是 property_get 方法返回的是 get 到的 value 的长度,真正的 value 是通过 property_get 的第二个参数得到的。

    system/core/cutils/properties.c

    int property_get(const char *key, char *value, const char *default_value)
    {
        int len;
        
        len = __system_property_get(key, value);
        if(len > 0) {
            return len;
        }
        // 如果 value 的长度不大于 0 则返回默认值
        if(default_value) {
            len = strlen(default_value);
            if (len >= PROPERTY_VALUE_MAX) {
                len = PROPERTY_VALUE_MAX - 1;
            }
            memcpy(value, default_value, len);
            value[len] = '\0';
        }
        return len;
    }
    

    bionic/libc/system_properties.cpp

    int __system_property_get(const char *name, char *value)
    {
        //根据属性名称从属性共享内存中查找属性信息
        const prop_info *pi = __system_property_find(name);
        //读取属性值
        if (pi != 0) {
            return __system_property_read(pi, 0, value);
        } else {
            value[0] = 0;
            return 0;
        }
    }
    

    查找共享内存

    bionic/libc/system_properties.cpp

    const prop_info *__system_property_find(const char *name)
    {
        if (__predict_false(compat_mode)) {
            return __system_property_find_compat(name);
        }
        return find_property(root_node(), name, strlen(name), NULL, 0, false);
    }
    
    
    static const prop_info *find_property(prop_bt *const trie, const char *name,
            uint8_t namelen, const char *value, uint8_t valuelen,
            bool alloc_if_needed)
    {
        if (!trie) return NULL;
    
        const char *remaining_name = name;
        prop_bt* current = trie;
        while (true) {
            const char *sep = strchr(remaining_name, '.');
            const bool want_subtree = (sep != NULL);
            const uint8_t substr_size = (want_subtree) ?
                sep - remaining_name : strlen(remaining_name);
    
            if (!substr_size) {
                return NULL;
            }
    
            prop_bt* root = NULL;
            uint_least32_t children_offset = atomic_load_explicit(&current->children, memory_order_relaxed);
            if (children_offset != 0) {
                root = to_prop_bt(&current->children);
            } else if (alloc_if_needed) {
                uint_least32_t new_offset;
                root = new_prop_bt(remaining_name, substr_size, &new_offset);
                if (root) {
                    atomic_store_explicit(&current->children, new_offset, memory_order_release);
                }
            }
    
            if (!root) {
                return NULL;
            }
    
            current = find_prop_bt(root, remaining_name, substr_size, alloc_if_needed);
            if (!current) {
                return NULL;
            }
    
            if (!want_subtree)
                break;
    
            remaining_name = sep + 1;
        }
    
        uint_least32_t prop_offset = atomic_load_explicit(&current->prop, memory_order_relaxed);
        if (prop_offset != 0) {
            return to_prop_info(&current->prop);
        } else if (alloc_if_needed) {
            uint_least32_t new_offset;
            prop_info* new_info = new_prop_info(name, namelen, value, valuelen, &new_offset);
            if (new_info) {
                atomic_store_explicit(&current->prop, new_offset, memory_order_release);
            }
    
            return new_info;
        } else {
            return NULL;
        }
    }
    

    读取属性值

    bionic/libc/system_properties.cpp

    int __system_property_read(const prop_info *pi, char *name, char *value)
    {
        if (__predict_false(compat_mode)) {
            return __system_property_read_compat(pi, name, value);
        }
    
        while (true) {
            uint32_t serial = __system_property_serial(pi); // acquire semantics
            size_t len = SERIAL_VALUE_LEN(serial);
            memcpy(value, pi->value, len + 1);
            // TODO: Fix the synchronization scheme here.
            // There is no fully supported way to implement this kind
            // of synchronization in C++11, since the memcpy races with
            // updates to pi, and the data being accessed is not atomic.
            // The following fence is unintuitive, but would be the
            // correct one if memcpy used memory_order_relaxed atomic accesses.
            // In practice it seems unlikely that the generated code would
            // would be any different, so this should be OK.
            atomic_thread_fence(memory_order_acquire);
            if (serial ==
                    load_const_atomic(&(pi->serial), memory_order_relaxed)) {
                if (name != 0) {
                    strcpy(name, pi->name);
                }
                return len;
            }
        }
    }
    

    熟悉设置大概流程如下:

    SystemProperties.get.png
    1. 虽然 Java 层和 native 层都提供了多个 get 方法,但是最终实现都是一个(property_get),其他方法都是对该方法的返回值进行了转换
    2. 同 set 一样 Javac 层没做什么实际的操作,也是通过 JNI 调用到 native 方法
    3. native 层直接从共享内存中读取属性

    虽然 set 和 get 方法介绍完了,但是感觉还是一脸懵逼。有很多疑问,
    set 的时候为什么设置的时候要通过 socket 设置属性?
    init 进程 收到 prop_msg 后又做了什么?
    get 的时候为什么可以可以通过共享内存获取?

    不急,我们先看下属性系统的框架,如下图:

    property_frame.png

    光看图有点意犹未尽,先大概介绍一下就当预告了,下篇文章详细看代码。。。

    • 三个进程
      • consumer 进程将共享内存加载到自己的虚拟地址空间,并直接访问这些属性
      • setter 进程同样将共享内存加载到自己的虚拟地址空间,但是不能写内存。当需要增加或者修改系统属性时,它将该属性通过 unix domain socket 发送至 property 服务。
      • property 服务运行在 init 进程中,init 进程首先创建一个共享内存区域,并保存一个指向该区域的描述符 fd 。init 进程将该区域通过使用 MAP_SHARED 标志的 mmap 映射至自己的虚拟地址空间,这样,对于任何进程该区域的更新都是可见的。fd 和区域大小被存储在一个名为 ANDROID_PROPERTY_WORKSPACE 的变量中。任何其他进程都可以通过这个变量来获得 fd 和尺寸,这样就可以 mmap 这个区域到自己的虚拟地址空间中。
    • 永久属性文件
      系统初始化时,从永久文件中加载属性记录,并将它们保存到共享内存中。这些文件除了所有者,其他用户都没有可写权限
    • 共享内存区域
      所有进程都可以直接读取这块区域,但是只有 init 进程可以修改。结构如下图


      sharedmemory.png

    【参考】
    http://blog.sina.com.cn/s/blog_6b936f150101jhfl.html
    http://blog.csdn.net/yangwen123/article/details/8936555
    http://blog.csdn.net/ameyume/article/details/8056492
    http://blog.csdn.net/jscese/article/details/18700903

    相关文章

      网友评论

        本文标题:Android系统属性set/get详解

        本文链接:https://www.haomeiwen.com/subject/ajyxaxtx.html