Android属性服务分析(property service)
1.首先init()进程在启动的过程对属性服务做了如下操作:
1)property_init(); //创建属性存储空间
2)property_load_boot_defaults(); //从 “/default.prop”中加载默认属性
3)queue_builtin_action(property_service_init_action, “property_service_init”); //初始化属性服务,创建名为“property_service”的socket,该socket文件存放在“/dev/socket/”目录下。然后绑定socket,开始监听。
4)queue_builtin_action(queue_property_triggers_action, “queue_property_triggers”); //首先将“queue_property_triggers”插入到执行action队列,这个action对应的执行函数位“queue_property_triggers_action”
2.文件系统-知识补充:
1)tmpfs文件系统
tmpfs是一种虚拟的内存文件系统,它会将所有的文件存储在虚拟内存中,并且tmpfs下的所有文件均为临时性文件,tmpfs是一个独立的文件系统,不是快设备,只要挂载即可使用。它驻留在RAM中,大小自动变化,断电后tmpfs的内容就会消失。
2)devpts文件系统
devpts文件系统为伪终端(pty-pseudo-terminal slave)提供了一个标准接口,它的标准挂接点是/dev/pts。伪终端是成对的逻辑终端设备(即master主和slave从设备,对master的操作会反应到slave上)
3)proc文件系统
proc是一个非常重要的文件系统,它可以看做是内核内部数据的接口,通过它可以获取系统的信息,同时也能在运行时修改特定的参数,只需要在对应的文件中添加新的值即可,如果修改失败,则只能重新启动设备。
4)sysfs文件系统
与proc文件系统相似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂载到/sys目录下。sysfs文件系统是linux2.6内核引入,它把连接到系统上的设备和总线组织为一个分级的文件,使得他们可以在用户空间存取。
在init进程开始时,创建了三个文件夹,分别为/dev,/proc和/sys。之后又创建了/dev/pts和/dev/socket两个文件夹,分别用于伪终端和用于与服务通信的socket。以上四中文件系统均被挂载到制定的目录上。
3.下面针对上面四点逐一解析;
1)创建属性存储空间
void property_init(void){ init_property_area();}
init_property_area()函数如下:
static int init_property_area(void){ if (property_area_inited) return -1; if(__system_property_area_init()) return -1; if(init_workspace(&pa_workspace, 0)) return -1; fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); property_area_inited = 1; return 0;}
其中property_area_inited标记属性存储区域是否初始化,__system_property_area_init()函数如下,该函数位于“bionic/libc/bionic/system_properties.cpp”中:
int __system_property_area_init(){ return map_prop_area_rw();}
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); 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; } // TODO: Is this really required ? Does android run on any kernels that // don't support O_CLOEXEC ? const int ret = fcntl(fd, F_SETFD, FD_CLOEXEC); if (ret < 0) { close(fd); return -1; } if (ftruncate(fd, PA_SIZE) < 0) { close(fd); return -1; } 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;}
由于dev是tmpfs系统,所以可以用来开辟一块共享存储空间。map_prop_area_rw()函数首先创建属性文件“/dev/_properties_”,大小PA_SIZE为128*1024,然后将该文件的对象映射进内存。由于属性是存放在一个混合的线索/二进制树结构(prop_bt)中,所以在内存区域中new了一个prop_area的头节点,并赋给了变量”_system_property_area_“.
之后,init_workspace()函数初始化工作空间,其中workspace的结构体如下:
typedef struct { size_t size; int fd;} workspace;
此时,fd为之间创建的属性文件的open操作的句柄。size为初始化指定大小。最后将“property_area_inited”置为1,至此,属性存储空间开辟完成。
4.从 “/default.prop”中加载默认属性
void property_load_boot_defaults(void){ load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);}
其中PROP_PATH_RAMDISK_DEFAULT文件为”/default.prop”
static void load_properties_from_file(const char *fn, const char *filter){ char *data; unsigned sz; data = read_file(fn, &sz); if(data != 0) { load_properties(data, filter); free(data); }}
read_file()即我们平时普通的读文件操作。load_properties函数采用递归的方式解析读取内容中的key-value对。次函数不予解释。
5.初始化属性服务
queue_builtin_action(property_service_init_action, "property_service_init");
这里,queue_builtin_action的第一个参数是一个函数,第二个参数是action的名称以及命令队列中的名称。
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(); if (get_property_set_fd() < 0) { ERROR("start_property_service() failed\n"); exit(1); } return 0;}
如注释所说,读取系统中所有的属性文件,启动属性服务。
void start_property_service(void){ int fd; fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL); if(fd < 0) return; fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, O_NONBLOCK); listen(fd, 8); property_set_fd = fd;}
创建与属性服务通信的socket,创建函数如下:
/* * create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR * ("/dev/socket") as dictated in init.rc. This socket is inherited by the * daemon. We communicate the file descriptor's value via the environment * variable ANDROID_SOCKET_ENV_PREFIX ("ANDROID_SOCKET_foo"). */ int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid, const char *socketcon){ struct sockaddr_un addr; int fd, ret; char *filecon; if (socketcon) setsockcreatecon(socketcon); fd = socket(PF_UNIX, type, 0); if (fd < 0) { ERROR("Failed to open socket '%s': %s\n", name, strerror(errno)); return -1; } if (socketcon) setsockcreatecon(NULL); memset(&addr, 0 , sizeof(addr)); addr.sun_family = AF_UNIX; snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s", name); ret = unlink(addr.sun_path); if (ret != 0 && errno != ENOENT) { ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno)); goto out_close; } filecon = NULL; if (sehandle) { ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK); if (ret == 0) setfscreatecon(filecon); } ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr)); if (ret) { ERROR("Failed to bind socket '%s': %s\n", name, strerror(errno)); goto out_unlink; } setfscreatecon(NULL); freecon(filecon); chown(addr.sun_path, uid, gid); chmod(addr.sun_path, perm); INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n", addr.sun_path, perm, uid, gid); return fd;out_unlink: unlink(addr.sun_path);out_close: close(fd); return -1;}
在“/dev/socket”目录下创建一个Unix domain socket(同一台主机进程间通信,不需要网络协议,打包拆包等操作,只是将应用层数据从一个进程拷贝到另一个进程)Unix Domain Socket有SOCK_DGRAM或SOCK_STREAM两种工作模式,类似于UDP和TCP。
UNIX Domain socket与网络socket类似,可以与网络socket对比应用。不同如下:
1.address family为AF_UNIX(PF_UNIX的宏)
2.因为应用与IPC,所以Unix Domain socket不需要IP和端口,取而代之的是文件路径表示网络地址
用socket函数创建套接字之后,要绑定该socket和路径(此处有待斟酌),最后监听该端口
6.将“queue_property_triggers”插入到执行action队列
static int queue_property_triggers_action(int nargs, char **args){ queue_all_property_triggers(); /* enable property triggers */ property_triggers_enabled = 1; return 0;}
queue_all_property_triggers()函数如下:
void queue_all_property_triggers(){ 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:"))) { /* parse property name and value syntax is property:<name>= */ const char* name = act->name + strlen("property:"); const char* equals = strchr(name, '='); if (equals) { char prop_name[PROP_NAME_MAX + 1]; char value[PROP_VALUE_MAX]; int length = equals - name; if (length > PROP_NAME_MAX) { ERROR("property name too long in trigger %s", act->name); } else { int ret; memcpy(prop_name, name, length); prop_name[length] = 0; /* does the property exist, and match the trigger value? */ ret = property_get(prop_name, value); if (ret > 0 && (!strcmp(equals + 1, value) || !strcmp(equals + 1, "*"))) { action_add_queue_tail(act); } } } } }}
该函数主要将解析init.rc文件得到的action列表中的property_action插入到action队列的末尾。
以上即为init进程中属性服务的解释,写这篇博客的目的主要是刚开始学习android源代码,找到方法才是最重要的。并以此鼓励自己。
更多相关文章
- 深入源码分析non-sdk并绕过Android(安卓)9.0反射限制
- Android(安卓)WebView中软键盘会遮挡输入框相关问题
- Android(安卓)XML属性在文档中的位置
- 实现activity全屏显示
- Android设置EditText输入类型:setInputType()方法和android:input
- Android动画知识汇总
- Android(安卓)Netd
- Android(安卓)解决自定义控件布局中match_parent属性无效
- Task和Activity相关的一些属性