Android Property模块

一、导言
在Android系统中,属性模块具备记录和控制能力。它不仅记录着系统本身的硬件/软件信息、控制系统功能、在进程间传递信息,还可以调用系统预置功能。
本文将对Android Property机制进行深入解析。
二、属性分类
属性一共有两种大类型,一种是普通属性,一种是控制属性。
普通属性是我们最常见的属性,它们仅记录Key-Value信息,软件设计上可以通过这样的Key-Value值,实现进程间通信、功能控制、暴露内部信息等功能。
而控制属性则较为少见,它的功能预置在系统中,用于控制程序的运行状态,控制设备开关机,控制Selinux。
接下来,我们将对普通属性和控制属性分别进行介绍。
2.1 普通属性
2.1.1 常规属性
2.1.1.1 常规属性特点
- 常规属性可以设置多次,重启后丢失。
2.1.1.2 常规属性来源
-
系统运行时设置。
-
构建时添加。
2.1.2 只读属性(ro.*)
2.1.2.1 只读属性特点
像是ro.boot.boot_devices这样带有**ro.**前缀开头的属性是只读属性,
-
如果存在没有定义过的只读属性,那么可以设置一次;
-
只读属性一旦被设置,就无法被修改;
-
后期设置(非构建时添加)的只读属性会在重启后丢失。
2.1.2.2 只读属性的来源
-
系统运行时设置。
-
Kernel DT、Kernel Cmdline、Boot Config。
-
构建时添加,属性可以指定存放的分区,system/vendor分区在构建后会将属性生成在{partition}/build.prop文件中。其他分区会放在{partition}/etc/build.prop文件中。
例如:
# 下面的属性,将会在构建后生成在system/build.prop中
PRODUCT_SYSTEM_PROPERTIES += ro.zygote.rescue=true
# 下面的属性,将会在构建后生成在vendor/build.prop中
PRODUCT_VENDOR_PROPERTIES += ro.vendor.extend_memory=true
2.1.3 持久存储属性(persist.*)
2.1.3.1 持久存储属性特点
像是persist.sys.stability.evo_enable这样的属性,是持久化属性,
-
该属性可以被设置多次(可被修改);
-
该属性被存放一份到/data/property/persistent_properties文件中,因此重启之后,该属性也依旧存在。
-
持久化属性的加载分为两个阶段,第一个阶段从构建产生的属性文件中获取并加载(时间点在解析rc文件前),第二个阶段从data分区的文件中加载其他持久存储的属性(时间点在post-fs-data阶段,执行vdc后),并且覆盖第一阶段同名的持久存储属性。这就意味着:
-
后期(非构建时添加)设置的持久存储属性,在post-fs-data阶段之前都是没有加载的!
-
后期(非构建时添加)设置的持久存储属性,在post-fs-data阶段之后会覆盖构建时添加的同名属性!
-
2.1.3.2 持久化存储属性来源
-
系统运行时设置。
-
构建时添加。
2.2 控制属性
2.2.1 CTL控制属性
设置ctl控制属性,可以调用init内置的功能,这些属性对应的方法如下。
需要注意的是,虽然ctl控制属性走了HandlePropertySet方法,但是并不会真的在内存中设置该属性,它的作用主要是调用INIT预置的一些控制方法。
属性 | 对应方法 | 功能 | 示例 |
---|---|---|---|
ctl.sigstop_on | set_sigstop(true) | 在启动服务时,将立刻对该服务发送STOP信号 | setprop ctl.sigstop_on logd |
ctl.sigstop_off | set_sigstop(false) | 在启动服务时,正常启动服务而不发送STOP信号 | setprop ctl.sigstop_off logd |
ctl.oneshot_on | set_oneshot(true) | 设置服务为单次运行,退出后不再拉起 | setprop ctl.oneshot_on logd |
ctl.oneshot_off | set_oneshot(false) | 关闭服务单次运行选项,服务退出后将会被自动拉起 | setprop ctl.oneshot_off logd |
ctl.start | DoControlStart | 启动服务 | setprop ctl.start logd |
ctl.stop | DoControlStop | 停止服务 | setprop ctl.stop logd |
ctl.restart | DoControlRestart | 重启服务 | setprop ctl.restart netd |
2.2.2 SYS控制属性
sys.powerctl属性可以控制系统的运行状态,常见的值有
-
reboot,[reason] - 控制系统重启,如果reason是userspace,将会触发软重启。
-
shutdown,[reason] - 控制系统关机
该属性被设置后(保留在内存中,重启丢失),属性服务会通过PropertyChanged通知Init,触发ShutdownState进行重启或关机操作。
2.2.3 SELINUX控制属性
用于对文件或文件夹打上selinux label的特殊控制属性——selinux.restorecon_recursive。
-
它的作用是异步对selinux.restorecon_recursive所设置的路径进行selinux label添加操作。
-
因为进行selinux label是一个长耗时的操作,所以需要额外启用一个线程来做(打标签的时候会调用selinux_android_restorecon方法)
该属性会被保留在内存中,重启后丢失。
三、属性初始化流程
Init在SecondStage早期(时间点在解析.rc脚本之前),会执行PropertyInit对属性进行初始化,其操作包含:
-
创建存放属性的文件夹/dev/__properties__
-
加载各分区(/{partition}/etc/selinux/{partition}_property_contexts)针对属性的selinux上下文,通过BuildTrie格式化后,写入到/dev/__properties__/property_info中。
-
加载/dev/__properties__/property_info到内存中,并存储PropertyInfoArea数据结构(在第2步中,BuildTrie的时候预留了PropertyInfoArea数据结构存放的空间)。到这里开始,property_info就被映射到内存中了,后续会使用这块共享内存区域存储属性。
-
初始化bionic C库的系统属性区域,通过__system_property_area_init,使用此前Init创建好的共享内存。
-
后续将会通过下面三个方法来拿到kernel及引导配置属性
-
ProcessKernelDt - 来源于/proc/bootconfig或/proc/cmdline的androidboot.android_dt_dir属性。如果都不存在,则使用默认的路径:/proc/device-tree/firmware/android/,然后将compatible、name以外的文件内的属性读取并设置 ro.boot.{filename} = {file content}。
- 找到的dt目录下的compatible文件内容为android,firmware,才会进行后续的处理流程。
-
ProcessKernelCmdline - 来源于/proc/cmdline中所有androidboot.前缀的属性,找到后会替换前缀为ro.boot.并设置。
-
ProcessBootconfig - 来源于/proc/bootconfig中所有androidboot.前缀的属性,找到后会替换前缀为ro.boot.并设置。
-
-
通过ExportKernelBootProps将部分ro.boot.前缀属性设置为ro.前缀属性。
// 源属性 将设置的目标属性 无源属性时的默认值
{ "ro.boot.serialno", "ro.serialno", UNSET, },
{ "ro.boot.mode", "ro.bootmode", "unknown", },
{ "ro.boot.baseband", "ro.baseband", "unknown", },
{ "ro.boot.bootloader", "ro.bootloader", "unknown", },
{ "ro.boot.hardware", "ro.hardware", "unknown", },
{ "ro.boot.revision", "ro.revision", "0", },
-
接着会读取个分区下的属性并设置。
-
读取各分区的build.prop或default.prop文件获取里面的属性,并通过PropertySetNoSocket进行设置,以下是执行顺序
-
/second_stage_resources/system/etc/ramdisk/build.prop (可能不存在)
-
/system/build.prop
-
system_ext分区
-
/system_dlkm/etc/build.prop
-
/vendor/default.prop
-
/vendor/build.prop
-
/vendor_dlkm/etc/build.prop
-
/odm_dlkm/etc/build.prop
-
odm分区
-
product分区
-
/debug_ramdisk/adb_debug.prop (可能不存在)
-
-
从init第二阶段的资源中获取属性
-
从ramdisk中获取debug属性
-
从厂商自定的属性文件中获取属性
-
根据各分区属性值,设置产品基本只读属性。该属性来源的优先级可以由ro.product.property_source_order属性来确定,否则使用默认的优先级列表——product,odm,vendor,system_ext,system。
-
ro.product.brand <- ro.product.{partition}.brand
-
ro.product.device <- ro.product.{partition}.device
-
ro.product.manufacturer <- ro.product.{partition}.manufacturer
-
ro.product.model <- ro.product.{partition}.model
-
ro.product.name <- ro.product.{partition}.name
-
-
初始化build id属性
-
设置ro.build.fingerprint,通过一系列只读属性动态拼接而来,具体细节可以查看ConstructBuildFingerprint函数。
-
设置ro.build.legacy.fingerprint(可能不存在,通过ro.build.legacy.id判断)
-
如果不存在,设置cpu api标识ro.product.cpu.abilist。(通过ro.{partition}.product.cpu.abilist64获取,来源优先级分区顺序是product,odm,vendor,system)
-
设置vendor abi级别 ro.vendor.api_level。
-
最后更新usb属性配置。如果是userdebug构建(即ro.debuggable为1),默认开启adbd,否则将usb配置成none。
-
四、属性服务
位于Init中的PropertyService,在属性初始化不久后启动(在加载rc脚本之前)。
它默认会启动一个PropertyServiceThread线程,用于属性管理。如果设置了ro.property_service.async_persist_writes属性,会启用第二个线程PersistWriteThread用于将持久化属性异步写入到文件中。属性可以被多线程读取,但是仅能被单线程设置。
它会创建2个socket进行通信:
-
property_service这个unix socket和设置属性的lib之间进行通信。当收到信息时通过handle_property_set_fd进行处理,用于处理属性设置的请求。
-
一对匿名socket和Init进行通信。当收到信息时通过HandleInitSocket进行处理,用于处理Init发起的后续请求,目前只有 从/data/property/persistent_properties加载持久化属性这一个需求需要完成。因为挂载加密data分区的时间点很晚(在post-fs-data阶段),因此无法在前面属性初始化的时候从data分区完成持久化属性的设置,等到解析rc脚本,并执行load_persist_props函数的时候,才会发送消息给属性服务完成从data分区读取其他持久化属性的操作。
PropertyService的职责是处理设置属性的事件,它是由Init通过StartPropertyService来启动的。每当PropertyService进行属性修改的时候,会通过PropertyChanged通知Init,当前有属性发生了变化。
PropertyChanged做的事情非常简单,只做三个事情
-
当接收到传递了sys.powerctl属性时,会调用ShutdownState来执行相关的事件,一般是处理重启、关机等操作
-
当Init开始启用属性变更检查时(时机是在late-init之后,由property_triggers_enabled变量进行控制),会将变化的属性键值添加到事件队列中,唤醒init进行处理
-
如果此前设置了需要等待某个属性,当前改变的属性正好匹配,就会唤醒init进行处理
五、属性设置接口
在讲述属性接口之前,先提一下,Android中所有程序都能够访问属性,说明他们都把/dev/__properties__/property_info映射到自己的虚拟内存空间中了,那么在什么时候进行映射的呢?
在通过Linker(linker_main)启动程序的时候,通过__system_properties_init函数进行的映射。
之所以程序在运行时会调用Linker来进行加载程序,是因为ELF文件的interp段指定了linker程序的路径。当运行一个ELF程序时,内核会将Linker程序和实际要运行的程序一起加载到内存中。
5.1 bionic C库的属性接口
5.1.1 __system_property_add
添加属性。
5.1.2 __system_property_update
更新属性。
5.1.3 __system_property_read
读取短key短value的属性(key小于32字节)
5.1.4 __system_property_read_callback
读取属性,并通过回调函数回传。支持长key、长value。
5.1.4 int __system_property_get
获取属性,并存储到value,返回值为属性值长度。
5.1.5 const prop_info* __system_property_find(const char* name)
查找属性,不存在返回null,存在则返回prop_info指针。
5.1.6 int __system_property_set(const char* key, const char* value)
通过ro.property_service.version属性,来判断当前设置属性的协议版本(目前有1和2两个协议)。
旧协议的限制如下:
-
属性名必须小于32字节(PROP_NAME_MAX)
-
属性值必须小于92字节(PROP_VALUE_MAX)
新协议限制如下:
- 非只读属性,属性值必须小于92字节(PROP_VALUE_MAX)
Bionic C库在这的作用,是封装属性设置的交互细节,对外暴露和__system_property_get类似的属性设置接口。
5.1.7 __system_property_wait
等待属性被设置,可以设置超时时间。
5.1.8 __system_property_wait_any
等待任意属性被设置。
5.2 base库的属性设置接口
核心接口
-
SetProperty,设置属性。调用bionic C库的__system_property_set方法(同时也支持非Android的属性接口,libbase内部也实现了__system_property_set方法)。
-
GetProperty,获取属性。调用了__system_property_find/__system_property_read_callback接口。
-
WaitForProperty,等待某个属性以及其属性值被设置,base库独有实现。
-
第一阶段判断属性Key是否被设置。如果没有被设置,通过__system_property_wait等待任何属性被设置,然后再次检查。超时返回。
-
第二阶段判断属性Key的Value是否和预期匹配。若不匹配,通过__system_property_wait等待匹配的情况发生。超时自动返回。
-
其他接口
-
GetBoolProperty,获取属性的bool值。
-
true值:“1”, “y”, “yes”, “on”, “true”
-
false值:“0”, “n”, “no”, “off”, “false”
-
-
GetIntProperty,获取属性并转换为int值。支持int8,int16,int32,int64
-
GetUintProperty,获取属性并转化为uint值。支持uint8,uint16,uint32,uint64
-
WaitForPropertyCreation,等待属性(key)被创建,base库独有实现。
5.3 cutils库接口
核心接口
-
property_set,设置属性。调用bionic C库的__system_property_set方法。
-
property_get,获取属性。调用bionic C库的__system_property_get方法。
其他接口
-
property_get_int,获取属性并转换成int值。
-
property_get_int64
-
property_get_int32
-
-
property_get_bool,获取属性并转换为bool值。
-
true值:“1”, “y”, “yes”, “on”, “true”
-
false值:“0”, “n”, “no”, “off”, “false”
-
5.4 FrameWork接口 - SystemProperties
核心接口
-
get,获取属性。经过native_get、SystemProperties_getSS、ReadProperty,最终调用了bionic C库的__system_property_find方法。
-
set,设置属性。经过native_set、SystemProperties_set,最终调用了bionic C库的__system_property_set方法。
-
find,查找属性。经过native_find、SystemProperties_find,最终调用了bionic C库的__system_property_find方法。
-
addChangeCallback,监听系统属性变化。经过native_add_change_callback调用了SystemProperties_add_change_callback自己实现的逻辑。Framework独有。
-
reportSyspropChanged,通知监听器,系统属性发生变化。Framework独有。
其他接口
-
getInt,获取属性并转换为int。
-
getLong,获取属性并转换为long。
-
getBoolean,获取属性并转换为boolean。
-
true值:“1”, “y”, “yes”, “on”, “true”
-
false值:“0”, “n”, “no”, “off”, “false”
-
六、属性存储结构
属性被存储到前缀树结构中,其结构存储在/dev/__properties__/property_info共享内存中。
每个属性都通过".“进行分割,分割产生的字符串将会被放入到前缀树的节点里。每个节点有left/right/children/prop四个指针,left/right指向相同前缀的前缀树的其他节点,children指向当前存放的孩子节点,prop指针指向prop_info结构,其存储key、value值。