1) 系统一启动就会从若干属性脚本文件中加载属性内容;
2) 系统中的所有属性(key/value)会存入同一块共享内存中;
3) 系统中的各个进程会将这块共享内存映射到自己的内存空间,这样就可以直接读取属性内容了;
4) 系统中只有一个实体可以设置、修改属性值,它就是属性服务(Property Service);
5) 不同进程只可以通过socket方式,向属性服务Service发出修改属性值的请求,而不能直接修改属性值;
6) 共享内存中的键值内容会以一种字典树的形式进行组织。
2018082118102975.pnginit进程里的Property Service
是在init 进程里面poll 检测来自其他进程的设置消息
初始化属性共享内存
在init进程的main()函数里,辗转打开了一个内存文件“/dev/properties”,并把它设定为128KB大小,接着调用mmap()将这块内存映射到init进程空间了。这个内存的首地址被记录在system_property_area全局变量里,以后每添加或修改一个属性,都会基于这个system_property_area变量来计算位置。
为什么要两次open那个/dev/properties文件呢?我想原因是这样的:第一次open的句柄,最终是给属性服务自己用的,所以需要有读写权限;而第二次open的句柄,会被记入pa_workspace.fd,并在合适时机添加进环境变量,供其他进程使用,因此只能具有读取权限。
第二次open动作发生在接下来的init_workspace()函数里。此时会再一次打开properties文件,这次却是以只读模式打开的:
int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);
打开的句柄记录在pa_workspace.fd处,以后每当init进程执行socket命令,并调用service_start()时,会执行类似下面的句子:
get_property_workspace(&fd, &sz); // 读取pa_workspace.fd
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
说白了就是把 pa_workspace.fd 的句柄记入一个名叫“ ANDROID_PROPERTY_WORKSPACE ”的环境变量去
初始化属性服务
property_service_init_action()函数只是在简单调用start_property_service()而已
加载若干属性文件,然后创建并监听一个socket接口。
加载的文件有:
/system/build.prop
/system/default.prop(该文件不一定存在)
/data/local.prop
/data/property目录里的若干脚本
初始化属性后的触发动作
现在就可以遍历刚生成的action_list,看看哪个刚加载好的属性可以进一步触发连锁动作。
当获取的属性名和属性值,与当初init.rc里记录的某action的激发条件匹配时,就把该action插入执行队列的尾部(action_add_queue_tail(act))
init进程循环监听socket
后创建了一个socket,
其名为ANDROID_SOCKET_DIR"/PROP_SERVICE_NAME",也即 "/dev/socket/property_service“
当从socket收到“设置属性”的命令后,会调用上面的handle_property_set_fd()函数
对于普通属性而言,主要是调用property_set()来设置属性值,但是有一类特殊属性是以“ctl.”开头的,它们本质上是一些控制命令,比如启动某个系统服务。这种控制命令需调用handle_control_message()来处理。
不是谁都可以成功设置以“ctl.”开头的特殊属性。handle_property_set_fd()会先调用check_control_perms()来检查发起方是否具有相应的权限。
如果设置方的uid是AID_SYSTEM或者AID_ROOT,那么一般都是具有权限的。而如果uid是其他值,那么就得查control_perms表了,这个表的定义如下
core/init/Property_service.c
/* White list of permissions for setting property services. */
struct {
const char *prefix;
unsigned int uid;
unsigned int gid;
} property_perms[] = {
{ "net.rmnet0.", AID_RADIO, 0 },
{ "net.gprs.", AID_RADIO, 0 },
{ "net.ppp", AID_RADIO, 0 },
{ "net.qmi", AID_RADIO, 0 },
{ "net.lte", AID_RADIO, 0 },
{ "net.cdma", AID_RADIO, 0 },
{ "ril.", AID_RADIO, 0 },
{ "gsm.", AID_RADIO, 0 },
{ "persist.radio", AID_RADIO, 0 },
{ "net.dns", AID_RADIO, 0 },
{ "sys.usb.config", AID_RADIO, 0 },
{ "net.", AID_SYSTEM, 0 },
{ "dev.", AID_SYSTEM, 0 },
{ "runtime.", AID_SYSTEM, 0 },
{ "hw.", AID_SYSTEM, 0 },
{ "sys.", AID_SYSTEM, 0 },
{ "sys.powerctl", AID_SHELL, 0 },
{ "service.", AID_SYSTEM, 0 },
{ "wlan.", AID_SYSTEM, 0 },
{ "bluetooth.", AID_BLUETOOTH, 0 },
{ "dhcp.", AID_SYSTEM, 0 },
{ "dhcp.", AID_DHCP, 0 },
{ "debug.", AID_SYSTEM, 0 },
{ "debug.", AID_SHELL, 0 },
{ "log.", AID_SHELL, 0 },
{ "service.adb.root", AID_SHELL, 0 },
{ "service.adb.tcp.port", AID_SHELL, 0 },
{ "persist.sys.", AID_SYSTEM, 0 },
{ "persist.service.", AID_SYSTEM, 0 },
{ "persist.security.", AID_SYSTEM, 0 },
{ "persist.service.bdroid.", AID_BLUETOOTH, 0 },
{ "selinux." , AID_SYSTEM, 0 },
/* psw0523 add for samsung_slsi slsiap hwc */
{ "hwc.scenario", AID_SYSTEM, 0 },
{ "hwc.scale", AID_SYSTEM, 0 },
{ "hwc.resolution", AID_SYSTEM, 0 },
{ "hwc.hdmimode", AID_SYSTEM, 0 },
{ "hwc.screendownsizing", AID_SYSTEM, 0 },
/* end psw0523 */
{ NULL, 0, 0 }
};
property_set
property_set(
一开始当然要先找到“希望设置的目标属性”在共享内存里对应的prop_info节点啦
如果可以找到prop_info节点,就尽量将这个属性的值更新一下,除非是遇到“ro.”属性,这种属性是只读的,当然不能set。
。如果找不到prop_info节点,此时会为这个新属性创建若干字典树节点,包括最终的prop_info叶子。
当某个属性修改之后, Property Service 会遍历一遍 action_list 列表,找到其中匹配的 action 节点,并将之添加进 action_queue 队列。
客户进程访问属性的机制
int __system_properties_init()
它调用的map_prop_area()会把属性共享内存,以只读模式映射到用户进程空间:
static int map_prop_area()
{
fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
. . . . . .
if ((fd < 0) && (errno == ENOENT)) {
fd = get_fd_from_env();
fromFile = false;
}
. . . . . .
pa_size = fd_stat.st_size;
pa_data_size = pa_size - sizeof(prop_area);
prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);
. . . . . .
result = 0;
__system_property_area__ = pa;
. . . . . .
return result;
}
bionic/libc/bionic/Libc_init_common.cpp
void __libc_init_common(KernelArgumentBlock& args) {
. . . . . .
. . . . . .
_pthread_internal_add(main_thread);
__system_properties_init(); // Requires 'environ'.
}
当一个用户进程被调用起来时,内核会先调用到C运行期库(crtbegin)层次来初始化运行期环境,在这个阶段就会调用到__libc_init(),而后才会间接调用到C程序员熟悉的main()函数。可见属性共享内存在执行main()函数之前就已经映射好了。
读取属性值
属性共享内存中的内容,其实被组织成一棵字典树。内存块的第一个节点是个特殊的总述节点,类型为prop_area。紧随其后的就是字典树的“树枝”和“树叶”了,树枝以prop_bt表达,树叶以prop_info表达。我们读取或设置属性值时,最终都只是在操作“叶子”节点而已。
网友评论