Android 属性系统 详解

Posted Jason_Wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 属性系统 详解相关的知识,希望对你有一定的参考价值。

android中保存了很多系统属性值,比如手机的操作系统版本号ro.build.version.release,SDK版本号ro.build.vrsion.sdk,芯片型号ro.chipname;用于配置USB连接类型的persist.sys.usb.config;Android虚拟机ART相关的配置dalvik.vm.image-dex2oat-Xms。这些系统属性由init进程的一个服务property_service负责管理,在手机启动时,该服务负责将所有手机里的属性文件中的属性加载到共享内存中,从而可以为不同的进程访问。从功能上来看,Android的属性系统类似于Windows的注册表,都是用于保存系统的参数与配置。

属性值中ro开头的表示read-only,这种属性值一旦被赋值一次之后,都不能被改写;而以persist开头的属性值表示持久化的数据,会将其保存到/data/property目录下。其他类型的属性除了位于属性文件中的属性值之外,手机一旦关机这些属性值都会丢失,不能持久存在

接下来,我们主要从三个方面来详细的分析下Android的属性系统(Properties System)是如何工作的?

  • 属性系统是如何设计的
  • Property Service的启动与初始化
  • 系统属性的获取与修改

属性系统是如何设计的

下图是Android属性系统的原理示意图。init进程启动时,将/dev/__properties__这个设备文件以共享内存的方式映射到内存中,这样由init启动的进程都可以直接来访问这个共享内存区域了。接着,会将所有位于磁盘中的的系统属性加载到该内存区域。这样,当进程A读取系统属性时,直接从该内存区域读取。而如果要修改某个属性,则需要通过一个Unix Domain Socket(/dev/socket/property_service),然后由init进程将该值写入到内存区域。这样就确保属性系统始终只有一个writer,但可以同时有多个reader。

接下来,就来看一看属性服务的启动与初始化。

Property Service的启动与初始化

init进程启动,分为两个阶段,阶段一主要是添加脚本执行环境,以及初始化必要的文件系统目录。接着通过execv来执行一个参数为--second-stage的新的init进程Image以取代之前的init进程Image。

execv系统调用:https://support.sas.com/documentation/onlinedoc/sasc/doc/lr2/execv.htm

执行第二阶段的初始化时,开始初始化属性系统,主要分为以下几个步骤:

  • 初始化属性共享内存;
  • 加载系统默认属性值;
  • 启动属性服务,监听属性修改请求命令;
  • 从磁盘中加载所有属性值到内存;

    int main(int argc, char** argv) 
        ...

        bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);

        // Get the basic filesystem setup we need put together in the initramdisk  on / and then we'll let the rc file figure out the rest.
        if (is_first_stage) 
            mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
            mkdir("/dev/pts", 0755);
            mkdir("/dev/socket", 0755);
            mount("devpts", "/dev/pts", "devpts", 0, NULL);
            mount("proc", "/proc", "proc", 0, NULL);
            mount("sysfs", "/sys", "sysfs", 0, NULL);
        


        if (!is_first_stage) 
            // 初始化属性系统,将/dev/__properties__映射到共享内存
            property_init();
            ....
        

        if (is_first_stage) 
            if (restorecon("/init") == -1) 
                ERROR("restorecon failed: %s\\n", strerror(errno));
                security_failure();
            
            // 执行第二阶段初始化
            char* path = argv[0];
            char* args[] =  path, const_cast<char*>("--second-stage"), nullptr ;
            if (execv(path, args) == -1) 
                ERROR("execv(\\"%s\\") failed: %s\\n", path, strerror(errno));
                security_failure();
            
        

        ....
        restorecon("/dev/__properties__");
        ....
        // 加载系统默认属性值
        property_load_boot_defaults();
        // 启动属性服务
        start_property_service();
        // 解析初始化脚本
        init_parse_config_file("/init.rc");

    

初始化属性系统共享内存

初始化属性系统的共享内存首先要创建一个内存区域,并将文件/dev/__properties__映射到该进程内存区域,这样由init启动的其他进程都可以共享该内存了。



    void property_init() 
        if (property_area_initialized) 
            return;
        

        property_area_initialized = true;

        if (__system_property_area_init()) 
            return;
        

        pa_workspace.size = 0;
        pa_workspace.fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
        if (pa_workspace.fd == -1) 
            ERROR("Failed to open %s: %s\\n", PROP_FILENAME, strerror(errno));
            return;
        
    

创建一个可读写的内存区域,并将属性设备文件映射到对应的内存空间:



    int __system_property_area_init()
    
        return map_prop_area_rw();
    

    //创建一个可读写的内存区域
    static int map_prop_area_rw()
    
        /* dev is a tmpfs that we can use to carve a shared workspace
         * out of, so let's do that...
         */
        const int fd = open(property_filename,
                            O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
        ....

        pa_size = PA_SIZE;
        pa_data_size = pa_size - sizeof(prop_area);
        compat_mode = false;
        // 创建一个共享内存区域,将文件映射到该内存区域
        void *const memory_area = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (memory_area == MAP_FAILED) 
            close(fd);
            return -1;
        

        prop_area *pa = new(memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);

        /* plug into the lib property services */
        __system_property_area__ = pa;

        close(fd);
        return 0;
    

加载系统默认属性值

完成了属性系统的内存初始化后,加载#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"中的属性值到内存区域:


    void property_load_boot_defaults() 
        load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
    

    static void load_properties_from_file(const char* filename, const char* filter) 
        Timer t;
        std::string data;
        if (read_file(filename, &data)) 
            data.push_back('\\n');
            load_properties(&data[0], filter);
        
    

启动属性服务,监听写操作

启动属性系统服务,其实就是创建一个名为property_service的socket,用于监听来自进程写系统属性的请求,同时注册在该socket上注册一个EPOLL时间的回调处理函数handle_property_set_fd



    void start_property_service() 
        property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                        0666, 0, 0, NULL);
        if (property_set_fd == -1) 
            ERROR("start_property_service socket creation failed: %s\\n", strerror(errno));
            exit(1);
        

        listen(property_set_fd, 8);

        register_epoll_handler(property_set_fd, handle_property_set_fd);
    

EPOLL一旦有收到来自进程的写属性请求,就调用handle_property_set_fd,该函数首先接受socket绑定请求,接着接收消息数据,最后调用property_set设置属性:


    static void handle_property_set_fd()
    
        prop_msg msg;
        // 接受bind请求
        if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) 
            return;
        
        ....
        // 接受消息数据
        r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
        ....
        switch(msg.cmd) 
        case PROP_MSG_SETPROP:
            msg.name[PROP_NAME_MAX-1] = 0;
            msg.value[PROP_VALUE_MAX-1] = 0;
            ....
            if(memcmp(msg.name,"ctl.",4) == 0) 
                close(s);
                if (check_control_mac_perms(msg.value, 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, 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);
                
            
            freecon(source_ctx);
            break;

        default:
            close(s);
            break;
        
    

加载系统属性文件

属性系统初始化的最后一步就是将位于磁盘中的属性文件加载到共享内存区域。除了default.prop属性文件之外,Android还有如下几个属性文件:


    #define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
    #define PROP_PATH_VENDOR_BUILD     "/vendor/build.prop"
    #define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"
    #define PROP_PATH_FACTORY          "/factory/factory.prop"

那么,加载是在哪里执行的?在初始化脚本init.rc里有两个执行动作用于加载系统属性文件,这两个动作会触发调用函数 load_system_propsload_persist_props


    # Load properties from /system/ + /factory after fs mount.
    on load_system_props_action
        load_system_props

    on load_persist_props_action
        load_persist_props
        start logd
        start logd-reinit


加载系统编译属性以及厂商编译相关的属性:


    void load_system_props() 
        load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
        load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
        load_properties_from_file(PROP_PATH_FACTORY, "ro.*");
        load_recovery_id_prop();
    

从文件目录/data/property/中加载系统持久化属性:


    void load_persist_props(void) 
        load_override_properties();
        /* Read persistent properties after all default values have been loaded. */
        load_persistent_properties();
    

访问系统属性值

Android提供了两种访问系统属性的方法,一种是通过JAVA Framework层的代码SystemProperties.java中提供的接口,由于该类接口并没有放入到SDK中,因此APP开发并不能使用此接口;一个是通过C++来访问,在代码中包含/android/system/core/include/cutils/properties.h头文件即可使用property_get/property_set来获取或者设置系统属性值了。

  • Java: /android/frameworks/base/core/java/android/os/SystemProperties.java
  • C++: /android/system/core/include/cutils/properties.h

实质上,两种方法最后都是通过bionic的LIBC库中的API来访问系统属性。

获取系统属性

调用libcutils中的property_get:


    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(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中的__system_property_get



    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;
        
    

源码:/android/bionic/libc/bionic/system_properties.cpp

写系统属性

写系统属性与读属性不一样的地方在于需要通过一个socket来发送写请求。首先是通过libcutils库中的property_set来设置属性:


    int property_set(const char *key, const char *value)
    
        return __system_property_set(key, value);
    

调用bionic中的__system_property_set


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

        const int err = send_prop_msg(&msg);
        if (err < 0) 
            return err;
        

        return 0;
    

向系统属性服务的socket/dev/socket/property_service发送写属性请求:

  • 获取一个本地socket文件描述符,并与系统属性服务的socket进行连接;
  • 连接成功后,向系统属性服务socket发送消息;
  • 等待系统属性服务返回写属性结果;

    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;
        
        ....
        // send message to socket
        const int num_bytes = TEMP_FAILURE_RETRY(send(fd, msg, sizeof(prop_msg), 0));

        int result = -1;
        // get the result from server
        if (num_bytes == sizeof(prop_msg)) 
            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 timeout
                result = 0;
            
        

        close(fd);
        return result;
    

系统属性服务监听到有消息时,调用回调函数handle_property_set_fd进行属性的修改:


    int property_set(const char* name, const char* value) 
        int rc = property_set_impl(name, value);
        if (rc == -1) 
            ERROR("property_set(\\"%s\\", \\"%s\\") failed\\n", name, value);
        
        return rc;
    

调用property_set_impl修改系统属性:

  • 从属性数据结构prop_area中查找给定的属性,返回prop_info;
  • 如果当前存在该属性值,则直接更新,否则需要新建该属性prop_info;
  • 对于persist的属性值,需要保存到文件/data/property中;

    static int property_set_impl(const char* name, const char* value) 
        size_t namelen = strlen(name);
        size_t valuelen = strlen(value);
        ....
        prop_info* 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 
            int rc = __system_property_add(name, namelen, value, valuelen);
            if (rc < 0) 
                return rc;
            
        
        /* 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;
            
            ....
            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);
        
        property_changed(name, value);
        return 0;
    

如果系统属性已经加载完,则将修改后的persist系统属性保存到文件/data/property:


    static void write_persistent_property(const char *name, const char *value)
    
        char tempPath[PATH_MAX];
        char path[PATH_MAX];
        int fd;

        snprintf(tempPath, sizeof(tempPath), "%s/.temp.XXXXXX", PERSISTENT_PROPERTY_DIR);
        fd = mkstemp(tempPath);
        if (fd < 0) 
            ERROR("Unable to write persistent property to temp file %s: %s\\n", tempPath, strerror(errno));
            return;
        
        write(fd, value, strlen(value));
        fsync(fd);
        close(fd);

        snprintf(path, sizeof(path), "%s/%s", PERSISTENT_PROPERTY_DIR, name);
        if (rename(tempPath, path)) 
            unlink(tempPath);
            ERROR("Unable to rename persistent property file %s to %s\\n", tempPath, path);
        
    

那么,Android是如何来管理系统属性值的了?这些属性有着怎么样的数据结构?大家可以参考/android/bionic/libc/bionic/system_properties.cpp 看下代码实现。

以上是关于Android 属性系统 详解的主要内容,如果未能解决你的问题,请参考以下文章

Android 属性系统 详解

Android SystemProperties系统属性详解

Android SystemProperties系统属性详解

Android零基础入门第80节:Intent 属性详解(下)

android动画详解六 XML中定义动画

Android中selector使用详解