init進程【4】——屬性服務

歡迎轉載,轉載請註明: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);
看到這裏,接下來會調到那裏去,相信大家應該都清楚了。。。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章