属性服务property service

大家都知道,在windows中有个注册表,里面存储的是一些键值对。注册表的作用就是:系统或者应用程序将自己的一些属性存储在注册表中,即使系统或应用程序重启,它还能够根据之前在注册表中设置的属性,进行相应的初始化工作。Android平台也提供了类似的机制,那就是“属性服务”。我们可以使用adb shell登录真机或模拟器后,使用getprop命令来查看当前系统中有哪些属性。

在init.c中与属性服务有关的代码有:

①Property_init();

②if (!is_charger)

property_load_boot_defaults();

③queue_builtin_action(property_service_init_action, "property_service_init");

3.1 属性服务的初始化

1、创建属性空间

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;

}

这里详细分析一下__system_property_area_init()函数的功能。

这个函数调用了map_prop_area_rw()函数:

static int map_prop_area_rw()

{

/*设备是一个临时文件系统,所以我们可以在其中挖出一块区域

* 用作共享内存。

*/

const int fd = open(property_filename,

O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);  //创建or打开属性文件

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); //表示在调用exec相关函数时,文件会关闭。

if (ret < 0) {

close(fd);

return -1;

}

if (ftruncate(fd, PA_SIZE) < 0) { //改变文件的大小为PA_SIZE

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);   //★使用mmap将刚创建的属性文件映射为共享内存,该内存可读可写。

if (memory_area == MAP_FAILED) {

close(fd);

return -1;

}

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

/* 将我们创建的属性区域嵌入到系统库函数属性中*/

__system_property_area__ = pa;

close(fd);

return 0;

}

当init进程创建完属性文件后,就会根据设备是否处于充电状态来决定是否加载属性表。如果不处于充电状态,那么就调用函数:

property_load_boot_defaults(); //加载default.prop文件

到这里属性服务的初始化工作就做完了。不过上面的有一条代码__system_property_area__ = pa; 并不像它看起来那么简单!

__system_property_area__ 是bionic libc库中输出的一个变量,那么这里为什么要给它赋值呢?原来,虽然属性区域是由init进程创建的,但是Android系统希望其他进程也能访问这个区域,所以它做了如下两个工作:

①把属性区域创建在共享内存上,这样就可以跨进程访问了。

②如何让其他进程知道这个共享内存呢?Android利用了gcc的constructor属性,这个属性指明了一个__libc_prenit函数,当bionic libc库被加载时,将自动调用这个__libc_prenit函数,在这个函数里面完成共享内存到本地进程的映射工作。详细内容请看下面。

2、客户端进程获取属性空间

在Libc_init_dynamic.c中定义了__libc_prenit函数,这个函数调用了__libc_init_common(KernelArgumentBlock& args)函数。具体代码如下:

__attribute__((constructor)) static void __libc_preinit() {

// Read the kernel argument block pointer from TLS.

void** tls = __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();

}

__libc_init_common函数代码如下:

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

//在TLS(安全套阶层协议)中获取主线程,并将其加入到线程列表中

pthread_internal_t* main_thread = __get_thread();

_pthread_internal_add(main_thread);

//初始化客户端的属性存储区域,关联到前面的系统属性存储区域上!这就实现了客户端进程直接读取系统属性存储区域的数据。但我这里有一个问题:实现关联过后,客户端进程对属性区域是拥有读写权限的,但在2.0的代码里却只有读的权限,这是为什么呢?

__system_properties_init(); // Requires 'environ'.

}

3.2 属性服务器的分析

1、启动属性服务器

属性服务器的启动由init进程完成,就是前面提及的queue_builtin_action(property_service_init_action, "property_service_init");

它其实就是执行start_property_service函数:

void start_property_service(void)

{

int fd;

/*加载三个属性文件。Android系统共提供了4中属性文件:

* PROP_PATH_SYSTEM_BUILD:  “/system/build/prop”

* PROP_PATH_SYSTEM_DEFAULT:  “/system/default.prop”

* PROP_PATH_FACTORY:  ?

* PROP_PATH_LOCAL_OVERRIDE:  “/data/local.prop”

*如果属性文件以ro.开头,表示只读;以persist开头,表示永久;

*如果属性名称以“net.”开头,当设置这个属性时,“net.change”属性将会自*动设置,以加入到最后修改的属性名。

*/

load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);

load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT, NULL);

load_properties_from_file(PROP_PATH_FACTORY, "ro.");

load_override_properties(); //加载/data/local.prop属性文件

/* 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, NULL);

if(fd < 0) return;

fcntl(fd, F_SETFD, FD_CLOEXEC);

fcntl(fd, F_SETFL, O_NONBLOCK); //设置为非阻塞模式

//开始监听,最多同时监听8个socket

listen(fd, 8);

property_set_fd = fd;

}

总结:属性服务在加载了一些属性文件之后,就创建了一个用来接收请求的socket。那么这个socket是用来干什么的呢?或者说,它是如何处理这个socket的呢?

在上面的最后一行代码中,将该socket号赋给了property_set_fd。我们在init进程的for循环中找到了处理这个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;

if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {

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: %zu errno: %d\n",

r, sizeof(prop_msg), errno);

close(s);

return;

}

//根据消息cmd的种类来分类处理socket请求,目前就一种

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);  //得到一个peersocket的上下文

/*如果是以ctl开头的消息,则认为是控制消息,控制消息用来执行一些命令。如使用adb shell登录手机后,输入setprop ctl.start bootanim就可以查看开机动画了。

*/

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); //handle msg其实就是去全局services_list列表中找到msg中提及的service,然后进行操作~

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

}

}

从上面看出当init进程接收到客户端关于属性服务的socket请求时,就会根据请求的种类进行区别处理。在满足权限检测后,就调用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); //如果是persist,就需要写入文件

} else if (strcmp("selinux.reload_policy", name) == 0 &&

strcmp("1", value) == 0) {

selinux_reload_policy();

}

/*调用此函数,完成属性的更改。其实就是将此次动作加入到全局property队列中,等待系统处理。这里提个问题,系统什么时候会处理全局队列中的动作呢?(答案参考init进程分析)*/

property_changed(name, value);

return 0;

}

到此,属性服务端的工作就分析完了。下面看看客户端是如何设置属性的。

3、客户端发送属性服务请求

客户端通过properties.c文件中的property_set函数发送请求。需要注意的是,在该文件中,根据预定义情况,分别定义了3种不同的property_set函数。在Android手机中,使用的是第一种:

int property_set(const char *key, const char *value)

{

return __system_property_set(key, value);

}

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); //★调用此函数向init进程发送请求

if (err < 0) {

return err;

}

return 0;

}

static int send_prop_msg(const prop_msg *msg)

{

const int fd = socket(AF_LOCAL, SOCK_STREAM, 0);

if (fd < 0) {

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;

}

const int num_bytes = TEMP_FAILURE_RETRY(send(fd, msg, sizeof(prop_msg), 0)); //发送msg

int result = -1;

if (num_bytes == 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.

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

return result;

}

这里对num_bytes的处理进行说明:如果send函数返回的数值等于我们发送的msg的大小,那么就表示我们发送“成功”了。不过,“发送属性msg”成功并不意味着“属性修改”成功了。为什么呢?因为Init进程是单线程的,它接收我们的msg后,可能还在忙于处理其他的事务,而不能立即完成属性的更改。所以,我们需要等待它一段时间,让init进程真正地完成属性修改服务。那么什么时候才能确定它完成了呢?根据属性服务的设定,一旦init完成属性更改,就会关闭客户端本次请求socket,所以我们在客户端添加了poll函数来接收init进程的返回信息(此函数轮询250ms)。这里比较有趣的是,无论poll的返回信息是什么,系统都假设它成功地完成了属性更改——系统设计员在说了一大堆理由后,最后说了一句:系统属性设计还需完善~~。

至此整个属性服务就分析完毕了。相对来说,比init.rc的分析要简单多了!

Android2.2源码属性服务分析的更多相关文章

  1. Android2.2源码init机制分析

    1 源码分析必备知识 1.1 linux内核链表 Linux内核链表的核心思想是:在用户自定义的结构A中声明list_head类型的成员p,这样每个结构类型为A的变量a中,都拥有同样的成员p,如下: ...

  2. 【一起学源码-微服务】Nexflix Eureka 源码十:服务下线及实例摘除,一个client下线到底多久才会被其他实例感知?

    前言 前情回顾 上一讲我们讲了 client端向server端发送心跳检查,也是默认每30钟发送一次,server端接收后会更新注册表的一个时间戳属性,然后一次心跳(续约)也就完成了. 本讲目录 这一 ...

  3. 安卓图表引擎AChartEngine(二) - 示例源码概述和分析

    首先看一下示例中类之间的关系: 1. ChartDemo这个类是整个应用程序的入口,运行之后的效果显示一个list. 2. IDemoChart接口,这个接口定义了三个方法, getName()返回值 ...

  4. 第九节:从源码的角度分析MVC中的一些特性及其用法

    一. 前世今生 乍眼一看,该标题写的有点煽情,最近也是在不断反思,怎么能把博客写好,让人能读下去,通俗易懂,深入浅出. 接下来几个章节都是围绕框架本身提供特性展开,有MVC程序集提供的,也有其它程序集 ...

  5. HTTP请求库——axios源码阅读与分析

    概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的 ...

  6. Netty源码解析---服务端启动

    Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ...

  7. 如何实现一个HTTP请求库——axios源码阅读与分析 JavaScript

    概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的 ...

  8. 【一起学源码-微服务】Nexflix Eureka 源码十三:Eureka源码解读完结撒花篇~!

    前言 想说的话 [一起学源码-微服务-Netflix Eureka]专栏到这里就已经全部结束了. 实话实说,从最开始Eureka Server和Eureka Client初始化的流程还是一脸闷逼,到现 ...

  9. 【一起学源码-微服务】Ribbon源码五:Ribbon源码解读汇总篇~

    前言 想说的话 [一起学源码-微服务-Ribbon]专栏到这里就已经全部结束了,共更新四篇文章. Ribbon比较小巧,这里是直接 读的spring cloud 内嵌封装的版本,里面的各种config ...

随机推荐

  1. PAT (Basic Level) Practise (中文)- 1015. 德才论 (25)

    http://www.patest.cn/contests/pat-b-practise/1015 宋代史学家司马光在<资治通鉴>中有一段著名的“德才论”:“是故才德全尽谓之圣人,才德兼亡 ...

  2. Java Object类 instanceof关键字 练习:判断是否为同一人 集合按照人的年龄排序,如果年龄相同按名字的字母顺序升序 Comparator比较器

    package com.swift; public class Same_Person_Test { public static void main(String[] args) { /* * Obj ...

  3. iOS开发之MVVM在项目中的应用

    今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  4. 01windows常用命令及批处理

    1. 概述 复制内容:右键弹出快捷菜单,选择"标记(K)",然后选中所需要的内容,然后右键即可 粘贴内容:右键弹出快捷菜单,选择"粘贴(P)" 命令参数的路径: ...

  5. 图解Disruptor框架(一):初识Ringbuffer

    图解Disruptor框架(一):初识Ringbuffer 概述 1. 什么是Disruptor?为什么是Disruptor? Disruptor是一个性能十分强悍的无锁高并发框架.在JUC并发包中, ...

  6. python向上取整 向下取整

    向上取整 ceil() 函数返回数字的向上取整整数,就是返回大于等于变量的最近的整数. ceil()是不能直接访问的,需要导入 math 模块. import math math.ceil( x ) ...

  7. redis集群监控之Redis-monitor部

    为了对以后有可能面临的redis集群监控做准备,这两天在准备这方面的事情,现在将其中的过程记录一下. 首先是“Ronney-Hua”的这篇文章对三中开源监控软件做了对比 文章地址:https://bl ...

  8. HUD:2853-Assignment(KM算法+hash)

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=2853 Assignment Time Limit: 2000/1000 MS (Java/Others) ...

  9. Wannafly挑战赛21 机器人

    从前在月球上有一个机器人.月球可以看作一个 n*m 的网格图,每个格子有三种可能:空地,障碍,机器人(有且仅有一个),现在地面指挥中心想让机器人在月球上行走,每次可以发送一个指令,为 U-往上走.D- ...

  10. bash函数定义/使用/传参…

    函数:function, 功能     过程式编程,代码重用         模块化编程         简洁             语法:         function f_name {    ...