我们都知道,在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_area static 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进程的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=1 start 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; }