Android系統(tǒng)屬性set/get詳解

基于 Android 6.0 的源碼,分析 set 和 get 系統(tǒng)屬性

設(shè)置系統(tǒng)屬性

上一篇文章提到,設(shè)置系統(tǒng)屬性調(diào)用 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, 然后調(diào)用 native_set

framework/base/core/jni/android_os_SystemProperties.cpp

static JNINativeMethod method_table[] = {
     ......
    // 關(guān)聯(lián) 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 不為空則將它轉(zhuǎn)化為 native 層的 key
    if (keyJ == NULL) {
        jniThrowNullPointerException(env, "key must not be null.");
        return ;
    }
    key = env->GetStringUTFChars(keyJ, NULL);
    // 如果來自 Java 層的 valJ 不為空則將它轉(zhuǎn)化為 native 層的 val
    if (valJ == NULL) {
        val = "";       /* NULL pointer not allowed here */
    } else {
        val = env->GetStringUTFChars(valJ, NULL);
    }
    // 調(diào)用 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 轉(zhuǎn)化為 char*,最后調(diào)用 property_set。這個 property_set 是哪里來的呢?在 android_os_SystemProperties.cpp 的頂部我們看到上篇提到的 #include "cutils/properties.h" 原來 Java 層其實沒什么實際的動作,最后還是調(diào)用和 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 中 什么都沒干,直接調(diào)用了 __system_property_set,但是我們在 properties.c 沒找到該方法的實現(xiàn),還好 Google 的代碼非常人性化,就在 property_set 的上面有 include 對應(yīng)文件。然后我們興高采烈的打開 _system_properties.h 發(fā)現(xiàn)里面根本沒有申明 __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;
   
     // 創(chuàng)建并清空一個prop_msg ,然后給它設(shè)置 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進(jìn)程的屬性服務(wù)發(fā)送消息  
    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 調(diào)用 native 的方法
  2. native 層也進(jìn)行了是否為空和長度判斷,然后封裝了一個 prop_msg 并通過 socket 發(fā)送給 init 進(jìn)程

對于屬性設(shè)置的分析暫時就到這里,下一篇文章會分析系統(tǒng)屬性的初始化,里面會涉及到 init 進(jìn)程收到 prop_msg 具體會如何處理。

獲取系統(tǒng)屬性

不同于 set 方法只有一個,有好幾個 get 方法,不過幾個方法最終的實現(xiàn)都一樣,這里我們以 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);
    }

校驗長度之后調(diào)用到 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 返回默認(rèn)值
    if (keyJ == NULL) {
        jniThrowNullPointerException(env, "key must not be null.");
        goto error;
    }

    key = env->GetStringUTFChars(keyJ, NULL);
    // 調(diào)用 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 對應(yīng)的方法中統(tǒng)一掉了用 property_get 方法,然后對返回值進(jìn)行處理,轉(zhuǎn)換成 boolean,int,long..
native 層的各個方法,同樣也是調(diào)用了 property_get,然后對返回值進(jìn)行轉(zhuǎn)換。

這里需要注意的是 property_get 方法返回的是 get 到的 value 的長度,真正的 value 是通過 property_get 的第二個參數(shù)得到的。

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 則返回默認(rèn)值
    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)
{
    //根據(jù)屬性名稱從屬性共享內(nèi)存中查找屬性信息
    const prop_info *pi = __system_property_find(name);
    //讀取屬性值
    if (pi != 0) {
        return __system_property_read(pi, 0, value);
    } else {
        value[0] = 0;
        return 0;
    }
}

查找共享內(nèi)存

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;
        }
    }
}

熟悉設(shè)置大概流程如下:

SystemProperties.get.png
  1. 雖然 Java 層和 native 層都提供了多個 get 方法,但是最終實現(xiàn)都是一個(property_get),其他方法都是對該方法的返回值進(jìn)行了轉(zhuǎn)換
  2. 同 set 一樣 Javac 層沒做什么實際的操作,也是通過 JNI 調(diào)用到 native 方法
  3. native 層直接從共享內(nèi)存中讀取屬性

雖然 set 和 get 方法介紹完了,但是感覺還是一臉懵逼。有很多疑問,
set 的時候為什么設(shè)置的時候要通過 socket 設(shè)置屬性?
init 進(jìn)程 收到 prop_msg 后又做了什么?
get 的時候為什么可以可以通過共享內(nèi)存獲取?

不急,我們先看下屬性系統(tǒng)的框架,如下圖:

property_frame.png

光看圖有點意猶未盡,先大概介紹一下就當(dāng)預(yù)告了,下篇文章詳細(xì)看代碼。。。

  • 三個進(jìn)程
    • consumer 進(jìn)程將共享內(nèi)存加載到自己的虛擬地址空間,并直接訪問這些屬性
    • setter 進(jìn)程同樣將共享內(nèi)存加載到自己的虛擬地址空間,但是不能寫內(nèi)存。當(dāng)需要增加或者修改系統(tǒng)屬性時,它將該屬性通過 unix domain socket 發(fā)送至 property 服務(wù)。
    • property 服務(wù)運(yùn)行在 init 進(jìn)程中,init 進(jìn)程首先創(chuàng)建一個共享內(nèi)存區(qū)域,并保存一個指向該區(qū)域的描述符 fd 。init 進(jìn)程將該區(qū)域通過使用 MAP_SHARED 標(biāo)志的 mmap 映射至自己的虛擬地址空間,這樣,對于任何進(jìn)程該區(qū)域的更新都是可見的。fd 和區(qū)域大小被存儲在一個名為 ANDROID_PROPERTY_WORKSPACE 的變量中。任何其他進(jìn)程都可以通過這個變量來獲得 fd 和尺寸,這樣就可以 mmap 這個區(qū)域到自己的虛擬地址空間中。
  • 永久屬性文件
    系統(tǒng)初始化時,從永久文件中加載屬性記錄,并將它們保存到共享內(nèi)存中。這些文件除了所有者,其他用戶都沒有可寫權(quán)限
  • 共享內(nèi)存區(qū)域
    所有進(jìn)程都可以直接讀取這塊區(qū)域,但是只有 init 進(jìn)程可以修改。結(jié)構(gòu)如下圖


    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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,544評論 19 139
  • 1:InputChannel提供函數(shù)創(chuàng)建底層的Pipe對象 2: 1)客戶端需要新建窗口 2)new ViewRo...
    自由人是工程師閱讀 5,712評論 0 18
  • 最近買了很多書來看,每本書都被譽(yù)為某段時間的暢銷書榜首,昨天同事來找我借書,順便討論看書的收獲,他提了這樣一個問題...
    倚欄觀雪閱讀 2,049評論 0 5
  • 折折騰騰了這么久。 愿你能遇見這樣的一個人,Ta能讓你不用再咬著牙逞強(qiáng),也不會再讓你憋著淚去倔犟。 從此, 你們雖...
    阿寧zZ閱讀 298評論 0 0
  • oio_be90閱讀 223評論 0 0

友情鏈接更多精彩內(nèi)容