在init进程的main函数中会有一步操作,启动property service
void start_property_service() {
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);
if (property_set_fd == -1) {
ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
exit(1);
}
listen(property_set_fd, 8);
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
可以看到这里会险创建一个socket,用于IPC,这里创建的套接字跟在写init.rc启动脚本中写的socket root root 666这种是一样的,都是属于Unix domain的套接字,这些套接字文件都存储在/dev/socket下面,我们熟悉的vold使用的套接字也是存放这这个下面。创建好之后就会去监听该socket的fd。然后注册监听的回调函数handle_property_set_fd,这个回调函数也是从消息中解析出cmd,然后做对应的操作:
static void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
char * source_ctx = NULL;
struct pollfd ufds[1];
const int timeout_ms = 2 * 1000; /* Default 2 sec timeout for caller to send property. */
int nr;
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;
}
ufds[0].fd = s;
ufds[0].events = POLLIN;
ufds[0].revents = 0;
nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
if (nr == 0) {
ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
close(s);
return;
} else if (nr < 0) {
ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
close(s);
return;
}
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
r, sizeof(prop_msg), strerror(errno));
close(s);
return;
}
//这里解析cmd来区别需要做怎样的操作,这里肯定会有疑惑,为什么只有setprop这个命令,主要是getprop是不需要通过property service的,get就直接读取而已,而只有setprop才会通过property service进行操作
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);
if(memcmp(msg.name,"ctl.",4) == 0) {//比较property的前四个字节是否是ctl.
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);//处理控制ctl消息
} 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, 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;
}
}注意:在surfaceflinger中使用property_set("ctl.start", "bootanim");来打开bootanimation这个服务
handle_control_message的实现如下:
void handle_control_message(const char *msg, const char *arg)
{
if (!strcmp(msg,"start")) {
msg_start(arg);
} else if (!strcmp(msg,"stop")) {
msg_stop(arg);
} else if (!strcmp(msg,"restart")) {
msg_restart(arg);
} else {
ERROR("unknown control msg '%s'\n", msg);
}
}如果是start就会继续调用msg_start("bootanimation"),会去根据解析init.rc得到的所有service的结果去找到bootanimation这个service,然后启动。停止这个service也是类似的原理。
这样来讲,能通过ctl.开头的property控制的service,必须是在init.rc中确实是申明过的才能使用这种property去控制。到此基本上就讲清除了bootanimation的打开和关闭机制。
接下来讨论一下property的存储机制,不太想trace整个加载属性文件的过程,property在内存中存储使用的是字典树的数据结构,它的优点是利用字符串的公共前缀来减少查询时间,这估计就是使用字典树的根本原因吧,因为在property中是存在很多开头相同属性,如http://ro.xxx这种
大致就画一张图来表明这种数据结构的特性,例如我们有如下几个property:
ro.abc.def=1
ro.hhh.def=2
sys.os.ccc=3
rs.ppp.qqq=4
在这样一个树中,跟每个节点产生直接关系的有三个,左兄弟、有兄弟和子节点
兄弟节点间的关系就是字典排序,左<中<右
这里还是借用网络上看到别人画的一张图:

但是要注意一点就是一个节点的右节点的左节点并不一定是它本身
最后讲一下如何修改手机或者其他设备的ro.serialno串号这个property,因为是一个只读属性,所以直接setprop是无效的。先给出结论,要修改此property需要在bootloader中写入androidboot.serialno这个bootarg的值。因为boot会将这些bootargs交给kernel,并存在/proc/cmdline中,开机之后property service就会去读取这个键值对,然后做两下property名字的转换最终得到ro.serialno这个property。就这么简单,但是不是原厂估计也没办法修改boot传给kernel的bootargs的值吧。