《深入理解Android 卷1》读书笔记 (一)—— Android Init之属性服务 (property_service)
本文的大体流程还是按照书本上来,分三段。
(一)从Main开始到service start
(二)zygote restart
(三)属性服务 (property_service)
由于本文内容较长,重新组织了下文章结构,将原文一分为三。
(三)属性服务 (property_service)
老实说,觉得自己讲不好这部分,建议读者参考《深入理解Android 卷1》 或者网上
其他文章。此处,我只是略提一下。
看到这个property,让我想起了注册表,也想起来以前工作中保存用户设置数据的部分。
涉及到了NAND Flash,ubifs等。当然,此处我们讲的这个property就不提那么多了。
1. 数据在NAND Flash里面,以便下次开机后能得到之前保存的数据。而进程访问
这些数据之前,有做mmap的动作,将数据映射到内存。
2.设置property,有C/S架构组成。客户端的程序位于properties.c,服务端的程序位于
property_service.c。
(1)Init.c的main函数中,我们先看到:property_init();
void property_init(void)
{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;
}
其中,__system_property_area_init 的实现如下:
int __system_property_area_init()
{return map_prop_area_rw();
}
static int map_prop_area_rw()
{prop_area *pa;int fd;int ret;/* dev is a tmpfs that we can use to carve a shared workspace* out of, so let's do that...*/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;if (ftruncate(fd, PA_SIZE) < 0)goto out;pa_size = PA_SIZE;pa_data_size = pa_size - sizeof(prop_area);compat_mode = false;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 */__system_property_area__ = pa;close(fd);return 0;out:close(fd);return -1;
}
看到open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC |O_EXCL, 0444);
和 pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
我们知道文件property_filename (即 /dev/__properties__)被打开,并以读写,可共享的方式
映射到了内存。(其他全局变量,如pa_data_size和__system_property_area__ 暂放置一边,
也不细节的讲述get_fd_from_env() )
static int init_workspace(workspace *w, size_t size)
{void *data;int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);if (fd < 0)return -1;w->size = size;w->fd = fd;return 0;
}
看到init_workspace以只读的方式打开 /dev/__properties__文件。
在main函数中接下来看到的是:property_load_boot_defaults
void property_load_boot_defaults(void)
{load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
static void load_properties_from_file(const char *fn)
{char *data;unsigned sz;data = read_file(fn, &sz);if(data != 0) {load_properties(data);free(data);}
}
反正就是将文件/default.pro 里面的properties 加载到内存。
(2) 接下来是:queue_builtin_action(property_service_init_action, "property_service_init");
和 queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
按照上面对service start的分析,我们知道property_service_init_action 和
queue_property_triggers_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();return 0;
}
void start_property_service(void)
{int fd;load_properties_from_file(PROP_PATH_SYSTEM_BUILD);load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);load_override_properties();/* Read persistent properties after all default values have been loaded. */load_persistent_properties();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;
}
int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid)
{struct sockaddr_un addr;int fd, ret;char *secon;fd = socket(PF_UNIX, type, 0);if (fd < 0) {ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));return -1;}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;}secon = NULL;if (sehandle) {ret = selabel_lookup(sehandle, &secon, addr.sun_path, S_IFSOCK);if (ret == 0)setfscreatecon(secon);}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(secon);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;
}
通过load properties这样的函数,将一些文件里面的property加载到内存,然后创建了一个socket,
其名为ANDROID_SOCKET_DIR"/PROP_SERVICE_NAME",也即 "/dev/socket/property_service“
然后调用listen监听这个socket,并将创建的socket描述符赋值给全局变量property_set_fd 。
至于queue_property_triggers_action, 请一层层看进去,会看到有函数处理init.rc里面
<propert_name>=<property_value> 这样的键值对 (字符串比较)。至于所谓的trigger,
其实跟上面对action的分析类似,在此就不赘述了。
(3)那么现在我们来看看我们调用property_set或者property_get的流程。
此处以property_set (platform/system/core/libcutils/properties.c)为例。
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)
{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;
}
我们看到,是要发送PROP_MSG_SETPROP这个消息的,现在得弄清给谁发。
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));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;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)) {// 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.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 {// 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(s);return result;
}
这个全局变量:"/dev/socket/" PROP_SERVICE_NAME; 即 "/dev/socket/"”property_service“,
当然其实就是"/dev/socket/property_service“。
然后调用了poll函数。此处调用poll的原因,我看了对应地方的代码注释,自己不甚了解,就
不忽悠了。咱们继续忽悠后面的。 调用了send后,会发生什么呢?
我们将回到init.c的main函数中的for循环中。里面的那个poll就是来处理这个的。
nr = poll(ufds, fd_count, timeout);if (nr <= 0)continue;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();}}
通过比较两个fd,发现将会执行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;}/* Check socket options here */if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {close(s);ERROR("Unable to receive socket options\n");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: %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;getpeercon(s, &source_ctx);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);} 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;}
}
会调用property_service.c里面的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(namelen >= PROP_NAME_MAX) return -1;if(valuelen >= PROP_VALUE_MAX) return -1;if(namelen < 1) 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'", 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);} else if (strcmp("selinux.reload_policy", name) == 0 &&strcmp("1", value) == 0) {selinux_reload_policy();}property_changed(name, value);return 0;
}
void property_changed(const char *name, const char *value)
{if (property_triggers_enabled)queue_property_triggers(name, value);
}
《深入理解Android 卷1》读书笔记 (一)—— Android Init之属性服务 (property_service)相关推荐
- 《Java核心技术 卷Ⅰ》读书笔记一
Java核心技术·卷 I(原书第10版) 作者: [美] 凯.S.霍斯特曼(Cay S. Horstmann) 出版社: 机械工业出版社 原作名: Core Java Volume I - Funda ...
- 第一行代码Android第二章读书笔记
第一行代码Android第二章读书笔记 Activity 1.1 手动创建活动 1.2 Toast和Menu/销毁活动 Intent 2.1 显示/隐式 2.2 传递/返回数据 活动的生命周期 3.1 ...
- Android 12 init(3) 属性服务
文章托管在gitee上 Android Notes , 同步csdn 本文基于Android12 分析 在 init 的启动第二阶段,启动属性服务线程,提供相关属性服务,给其他进程提供设置属性的支持, ...
- Android智能指针——读书笔记
目录结构 目录结构 参考资料 概述 背景知识 GC经典问题 轻量级指针 实现原理分析 构造函数 析构函数 应用实例分析 强指针和弱指针 强指针的实现原理分析 增加对象的弱引用计数 增加对象的强引用计数 ...
- 《深入理解Linux内核》 读书笔记
深入理解Linux内核 读书笔记 一.概论 操作系统基本概念 多用户系统 允许多个用户登录系统,不同用户之间的有私有的空间 用户和组 每个用于属于一个组,组的权限和其他人的权限,和拥有者的权限不一样. ...
- Android进阶之光 读书笔记
第一章, Android 5.6.7新特性 1.RecycleView的自定义分割线 public class DividerItemDecoration extends RecycleView.It ...
- Android群英传读书笔记——第十二章:Android 5.X新特性详解
第十二章目录 12.1 Android5.X UI设计初步 12.1.1 材料的形态模拟 12.1.2 更加真实的动画 12.1.3 大色块的使用 12.2 Material Design主题 12. ...
- 【读书笔记《Android游戏编程之从零开始》】6.Android 游戏开发常用的系统控件(TabHost、ListView)...
3.9 TabSpec与TabHost TabHost类官方文档地址:http://developer.android.com/reference/android/widget/TabHost.htm ...
- Android进阶之光读书笔记——第三章:View体系与自定义View
第三章 View体系与自定义View 本章将介绍Android中十分重要的View,在多本书中View是必讲的一节,Android群英传就讲了不少的View的知识,那么在这里我们再去复习一遍吧 3.1 ...
最新文章
- 14. 函数返回值为引用?
- 转:谷歌离线地图基础
- asp.net core2.2 多用户验证和授权
- android里R.layout.的问题
- HTML5游戏开发系列教程5(译)
- Tensflow学习笔记(一)——TF生成并查看数据
- 创建图表_三种建立Excel图表的方法,谁用谁知道
- GPS经纬度转84坐标系
- MATLAB公式希腊字母表
- 微信推送封面尺寸_微信公众平台图片尺寸是多少?
- java pow_Java pow()方法
- Django新增数据
- java 将html转为word导出 (富文本内容导出word)
- 手机怎么用外嵌字幕_剪映教程大全:剪映加字幕、设置封面、变速等教程详解!...
- (zhuan)富文本 Attributes 下划线、删除线等
- Linux2--修改root密码,文件操作
- Error opening data file Tesseract-OCR\tessdata/eng.traineddata问题 解决
- android设备压差表怎么校准,MY-DJ101
- Linux的 常用命令
- 05- 线性回归算法 (LinearRegression) (算法)