我们都知道,在Windows平台上有一个注册表管理器,注册表的内容采用key-value键值对的形式来记录用户、软件的一些使用信息。即使系统或者软件重启,它还是能够根据之前在注册表中的记录,进行相应的初始化工作。

那么在Android平台上,也有类似的机制,称之为属性服务(property service)。应用程序可以通过这个属性机制,查询或者设置相应的属性。我们可以使用getprop命令来查看当前系统中都有哪些属性。比如我的红米手机,如图1所示(图中只显示了部分属性)。


图1

创建属性内容存储文件

属性服务的初始化工作主要是在init进程的main函数中完成。代码路径system\core\init\init.c。init的main函数中首先调用property_init()函数创建存储文件,代码如下:

void property_init(void){    init_property_area();}static int init_property_area(void){// 判断是否已经初始化过了    if (property_area_inited)        return -1;// 打开/dev/__properties__设备,然后mmap一块大小为(128 * 1024)的内存出来    if(__system_property_area_init())        return -1;/*                 pa_workspace是个全局变量,结构如下所示typedef struct {    size_t size;共享内存的大小    int fd;共享内存的文件描述符                 } workspace;                 init_workspace函数打开设备/dev/__properties__,将文件描述符赋值给fd,size清零。        */    if(init_workspace(&pa_workspace, 0))        return -1;    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);// 初始化完毕    property_area_inited = 1;    return 0;}


再来看看__system_property_area_init函数具体做了什么事情,__system_property_area_init->map_prop_area_rw:

static int map_prop_area_rw(){    prop_area *pa;    int fd;    int ret;    /* #define PROP_FILENAME "/dev/__properties__" static char property_filename[PATH_MAX] = PROP_FILENAME; 这里就是打开/dev/__properties__文件,感情property area是用共享文件来实现的 指定了O_CREAT标志,如果文件不存在则创建之     */    fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC |            O_EXCL, 0444);    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);    if (ret < 0)        goto out;/*#define PA_SIZE         (128 * 1024)改变文件大小为PA_SZIE*/    if (ftruncate(fd, PA_SIZE) < 0)        goto out;/*并不是整个pa都用来记录属性内容的,那么其前面sizeof(prop_area)的大小是用来记录这个pa的一些基本信息,例如,特征值、版本号等等。紧接着prop_area结构后面的data内存才是真正用来记录属性信息的。struct prop_area {    unsigned bytes_used;    unsigned volatile serial;    unsigned magic;    unsigned version;    unsigned reserved[28];    char data[0];}; */    pa_size = PA_SIZE;    pa_data_size = pa_size - sizeof(prop_area);    compat_mode = false;/*在init进程中将/dev/__properties__文件映射成一块可读可写的内存注意,我们在后面会看到,在其他客户端进程都是以只读的形式访问pa的,如果客户端要修改或者新增属性,那么需要进程间通信,最后都是由init进程来完成的*/    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 最后将pa复制给全局变量__system_property_area__*/    __system_property_area__ = pa;    close(fd);    return 0;out:    close(fd);    return -1;}

上面的内容比较简单,不过最后的一个赋值语句__system_property_area__ = pa;比较特殊。__system_property_area__变量是在bioniclibc库中定义的一个全局变量。这里为什么要赋值给它呢?

原来,虽然属性区域是由init进程在初始化的时候创建的,但是Android希望其他进程也能读取这块内存里面的东西,为了做到这一点,它便做了如下两项工作:

1.属性区域通过文件形式实现进程间共享数据。

2.如何让其他进程也去读取这个文件呢?Android利用了gcc的constructor属性,这个属性指明了一个__libc_prenit函数,当bionic libc库被加载时,将自动调用这个__libc_prenit,这个函数内部就将完成共享文件到本地进程的映射工作。


客户端进程映射属性文件

直接看代码吧,bionic\libc\bionic\libc_init_dynamic.cpp

// constructor属性指示加载器加载该库后,首先调用__libc_preinit函数。这一点和windows// 上动态库的DllMain函数类似__attribute__((constructor)) static void __libc_preinit() {…__libc_init_common(*args);…}void __libc_init_common(KernelArgumentBlock& args) { …  __system_properties_init(); // 初始化话客户端属性区域.}__system_properties_init –> map_prop_areastatic int map_prop_area(){    bool fromFile = true;    int result = -1;    int fd;int ret;// 打开已经存在的文件/dev/__properties__    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)) {        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);// mmap将文件映射为内存,此处为PROT_READ形式,客户端进程对属性文件是只读// 的,而不能对其进行设置    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;}

加载属性文件并启动属性服务

接着看init进程的初始化代码

// 如果启动模式不是charger,那么就加载/default.prop属性文件if (!is_charger)        property_load_boot_defaults();

最后调用queue_builtin_action(property_service_init_action,"property_service_init");完成属性服务器的启动过程。

property_service_init_action-> start_property_service:

void start_property_service(void){int fd;/*#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"#define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"*/    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);// 如果是调试模式,就加载// #define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"load_override_properties();/* Read persistent properties after all default values have been loaded. 有一些属性是需要保存到永久介质上的,这些属性文件则由下面这个函数加载,这些文件存储在/data/property目录下,并且这些文件的文件名必须以persist.开头。*/    load_persistent_properties();// 创建一个socket,用于ipc通信 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;}

init进程处理设置属性请求

设置属性的请求都是在init进程的main函数的一个for循环中进行处理的。下面我们继续看代码:

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

上面start_property_service函数中创建了一个用于通信的socket,最后赋值给全局变量property_set_fd,此处就是判断收到的信息是否是是属性服务器的socket。

int get_property_set_fd(){    return property_set_fd;}

如果是属性服务器的通信socket,那么就调用handle_property_set_fd函数来处理消息,并设置相应的属性。

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;// 接收TCP连接    if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {        return;    }/* Check socket options here 取出socket的可选内容,可能包括客户端进程的权限等属性*/    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {        close(s);        ERROR("Unable to receive socket options\n");        return;    }// 接收socket的主体数据    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);/*如果是ctl开头的消息,则认为是控制消息,控制消息用来执行一些命令,例如用adb shell登录后,输入setprop ctl.start bootanim就可以查看开机动画了,如果要关闭就输入setprop.stop bootanim就可以了。*/        if(memcmp(msg.name,"ctl.",4) == 0) {            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 {/*不是ctl开头的属性,则首先检查其权限。例如,设置net.开头的属性需要AID_SYSTEM权限,log.开头的属性需要AID_SHELL属性等。*/            if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) {// 最后通过property_set函数设置客户端需要设置的属性                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;    }}

当客户端的权限满足要求时,init进程就调用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.开头的属性表明是只读的,直接返回*/        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) {// 如果属性名是以persist.开头的,表明该属性是永久的,那么需要把属性写// 到相应文件中去        write_persistent_property(name, value);    } else if (strcmp("selinux.reload_policy", name) == 0 &&               strcmp("1", value) == 0) {// 与selinux相关        selinux_reload_policy();}/*init.rc中有如下这句话:on property:persist.service.adb.enable=1start adbd待persist.service.adb.enable属性设置为1后,就执行start adbd这个command,这就是通过property_changed函数来完成的。*/    property_changed(name, value);    return 0;}

property_changed函数其主要实现如下,property_changed-> queue_property_triggers:

void queue_property_triggers(const char *name, const char *value){    struct listnode *node;    struct action *act;    list_for_each(node, &action_list) {        act = node_to_item(node, struct action, alist);        if (!strncmp(act->name, "property:", strlen("property:"))) {            const char *test = act->name + strlen("property:");            int name_length = strlen(name);            if (!strncmp(name, test, name_length) &&                    test[name_length] == '=' &&                    (!strcmp(test + name_length + 1, value) ||                     !strcmp(test + name_length + 1, "*"))) {                action_add_queue_tail(act);            }        }    }}

客户端设置属性

客户端通过property_set发送设置属性的请求,代码如下所示:property_set -> __system_property_set

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;}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));// #define PROP_SERVICE_NAME "property_service"// static const char property_service_socket[] = "/dev/socket/" PROP_SERVICE_NAME;    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;// 会连接上init进程里面的socket    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)) {        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 {            result = 0;        }    }    close(s);    return result;}









更多相关文章

  1. android基础2——android工程目录结构
  2. Android(安卓)Q 带来哪些变化
  3. Android持久化技术之文件的读取与写入实例详解
  4. Android中XML的命名空间、自定义属性xmlns:app="http://schemas.
  5. android MultiDex multidex原理下超出方法数的限制问题(三)
  6. Android中贝塞尔曲线的绘制方法示例代码
  7. 【Android】 Tablayout、XTablayout、SlidingTablayout的比较
  8. 第一次尝试翻译本书
  9. 在android中开发快速地搜索手机文件引擎实例

随机推荐

  1. Android(安卓)Studio编译中“png-crunche
  2. Android学习笔记 --- Terms and installa
  3. Android(安卓)uiautomator 使用入门官方
  4. Android的权限permission
  5. android在java代码中绘制矩形框
  6. Android(安卓)ListView(Selector 背景图片
  7. 第12天android:短信发送+测试使用
  8. Mac下获取android studio keystore的SHA1
  9. android 开发上传图片遇到返回 FileNotFo
  10. Android之获取手机信息