Detailed explanation of Android system property set/get

Set system properties

As mentioned in the previous article, setting system properties calls SystemProperties set("key", "value"); Just. Then start with this method.

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

This is just a check to see if the length of key and value exceeds 31 and 91, and then call native_. set

framework/base/core/jni/android_os_SystemProperties.cpp

static JNINativeMethod method_table[] = {
     ......
    // Associate SystemProperties Native in Java_ Set and Android_ os_ SystemProperties. SystemProperties in CPP_ Set method
    { "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;
    // If the keyJ from the Java layer is not empty, convert it to the key of the native layer
    if (keyJ == NULL) {
        jniThrowNullPointerException(env, "key must not be null.");
        return ;
    }
    key = env->GetStringUTFChars(keyJ, NULL);
    // If the valJ from the Java layer is not empty, convert it to the val of the native layer
    if (valJ == NULL) {
        val = "";       /* NULL pointer not allowed here */
    } else {
        val = env->GetStringUTFChars(valJ, NULL);
    }
    // Call cuttils / properties Property in H_ 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 shows that key and value are empty and then transformed from jstring to char*. Finally, property_ is called. set. This property_ Where did set come from? On Android_ os_ SystemProperties. At the top of CPP, we can see that the #include "cuts / properties. H" mentioned in the previous article originally had no actual actions in the Java layer, and finally called the methods provided by the Java layer and the native layer.

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. Nothing is done in C, so it is called directly__ system_property_set, but we are in properties C didn't find the implementation of this method. Fortunately, Google's code is very user-friendly, just in property_ There is a file corresponding to include on set. Then we opened it happily_ system_properties.h found that there was no statement in it__ system_property_set method, but don't worry. Turn up and you'll see #include < sys / system_properties.h>

bionic/libc/system_properties.cpp

int __system_property_set(const char *key, const char *value)
{
    // Check length
    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;
   
     // Create and empty a prop_msg, and then set cmd key value to it
    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);
    // Send a message to the property service of init process through socket  
    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;
}

The general flow chart is as follows:

Through the above source code, we can see:

  1. The Java layer simply judges the length of key and value and whether value is empty, and then calls the native method through JNI
  2. The native layer also determines whether it is empty and length, and then encapsulates a prop_msg and send it to the init process through socket

That's all for the analysis of attribute settings. The next article will analyze the initialization of system attributes, which will involve the receipt of prop by the init process_ MSG how to deal with it.

Get system properties

Unlike only one set method, there are several get methods, but their final implementation is the same. Here we take getBoolean as an example:

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

After checking the length, call the SystemProperties_ in the JNI layer. 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;
    // If the key is empty, skip to error and return the default value
    if (keyJ == NULL) {
        jniThrowNullPointerException(env, "key must not be null.");
        goto error;
    }

    key = env->GetStringUTFChars(keyJ, NULL);
    // Call property_get
    len = property_get(key, buf, "");
    // If the length is 1, 0 or n stands for false and 1 or y stands for true
    // If the length is greater than 1, then no,false,off stands for false, yes,true,on stands for 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;
}

All get methods in the Java layer are unified without property in the corresponding methods of JNI_ Get method, and then process the return value and convert it into boolean,int,long
All methods of the native layer also call property_get, and then convert the return value.

Here, we need to pay attention to property_ The get method returns the length of the value obtained by get, and the real value is through property_ The second parameter of 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;
    }
    // If the length of value is not greater than 0, the default value is returned
    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)
{
    //Find the attribute information from the attribute shared memory according to the attribute name
    const prop_info *pi = __system_property_find(name);
    //Read attribute value
    if (pi != 0) {
        return __system_property_read(pi, 0, value);
    } else {
        value[0] = 0;
        return 0;
    }
}

Find shared memory

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

Read attribute value

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

The general process of familiar setting is as follows:

  1. Although both the Java layer and the native layer provide multiple get methods, the final implementation is a property_get, and other methods convert the return value of the method
  2. Like set, the Javac layer does not do any actual operation, but also calls the native method through JNI
  3. The native layer reads properties directly from shared memory

Although the set and get methods have been introduced, I still feel confused. There are many questions,
Why set the attribute through socket when setting?
Process init received_ What did you do after MSG?
Why can I get it through shared memory?

Don't worry. Let's take a look at the framework of the attribute system, as shown below:

Just looking at the picture is not enough. Let's make a general introduction first. Let's take a look at the code in detail in the next article...

  • Three processes
    • The consumer process loads the shared memory into its own virtual address space and directly accesses these properties
    • The setter process also loads shared memory into its own virtual address space, but cannot write memory. When the system property needs to be added or modified, it sends the property to the property service through unix domain socket.
    • The property service runs in the init process. The init process first creates a shared memory area and saves a descriptor fd pointing to the area. The init process will the region by using map_ The mmap of the shared flag is mapped to its own virtual address space so that updates to the region are visible to any process. fd and area size are stored in a file named Android_ PROPERTY_ In the variable of workspace. Any other process can obtain fd and size through this variable, so that it can mmap this area into its own virtual address space.
  • Permanent properties file
    When the system is initialized, the attribute records are loaded from the permanent file and saved to the shared memory. Except for the owner, no other users of these files have write permission
  • Shared memory area
    All processes can read this area directly, but only init processes can modify it. The structure is shown in the figure below

Keywords: Java Android webview

Added by simmsy on Tue, 08 Mar 2022 03:37:07 +0200