property

1) 系统一启动就会从若干属性脚本文件中加载属性内容;

2) 系统中的所有属性(key/value)会存入同一块共享内存中;

3) 系统中的各个进程会将这块共享内存映射到自己的内存空间,这样就可以直接读取属性内容了;

4) 系统中只有一个实体可以设置、修改属性值,它就是属性服务(Property Service);

5) 不同进程只可以通过socket方式,向属性服务Service发出修改属性值的请求,而不能直接修改属性值;

6) 共享内存中的键值内容会以一种字典树的形式进行组织。

2018082118102975.png

init进程里的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表达。我们读取或设置属性值时,最终都只是在操作“叶子”节点而已。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容