Android Property机制分析

1,125 阅读12分钟

原文链接:ovea-y.cn/android_pro…

一、导言

在Android系统中,属性模块具备记录和控制能力。它不仅记录着系统本身的硬件/软件信息、控制系统功能、在进程间传递信息,还可以调用系统预置功能。

本文将对Android Property机制进行深入解析。

二、属性分类

属性一共有两种大类型,一种是普通属性,一种是控制属性。

普通属性是我们最常见的属性,它们仅记录Key-Value信息,软件设计上可以通过这样的Key-Value值,实现进程间通信、功能控制、暴露内部信息等功能。

而控制属性则较为少见,它的功能预置在系统中,用于控制程序的运行状态,控制设备开关机,控制Selinux。

接下来,我们将对普通属性和控制属性分别进行介绍。

2.1 普通属性

2.1.1 常规属性

2.1.1.1 常规属性特点
  • 常规属性可以设置多次,重启后丢失

2.1.1.2 常规属性来源
  1. 系统运行时设置。

  2. 构建时添加。

2.1.2 只读属性(ro.*)

2.1.2.1 只读属性特点

像是ro.boot.boot_devices这样带有**ro.**前缀开头的属性是只读属性,

  • 如果存在没有定义过的只读属性,那么可以设置一次;

  • 只读属性一旦被设置,就无法被修改;

  • 后期设置(非构建时添加)的只读属性会在重启后丢失

2.1.2.2 只读属性的来源
  1. 系统运行时设置。

  2. Kernel DT、Kernel Cmdline、Boot Config。

  3. 构建时添加,属性可以指定存放的分区,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 持久化存储属性来源
  1. 系统运行时设置。

  2. 构建时添加。

2.2 控制属性

2.2.1 CTL控制属性

设置ctl控制属性,可以调用init内置的功能,这些属性对应的方法如下。

需要注意的是,虽然ctl控制属性走了HandlePropertySet方法,但是并不会真的在内存中设置该属性,它的作用主要是调用INIT预置的一些控制方法

属性对应方法功能示例
ctl.sigstop_onset_sigstop(true)在启动服务时,将立刻对该服务发送STOP信号setprop ctl.sigstop_on logd
ctl.sigstop_offset_sigstop(false)在启动服务时,正常启动服务而不发送STOP信号setprop ctl.sigstop_off logd
ctl.oneshot_onset_oneshot(true)设置服务为单次运行,退出后不再拉起setprop ctl.oneshot_on logd
ctl.oneshot_offset_oneshot(false)关闭服务单次运行选项,服务退出后将会被自动拉起setprop ctl.oneshot_off logd
ctl.startDoControlStart启动服务setprop ctl.start logd
ctl.stopDoControlStop停止服务setprop ctl.stop logd
ctl.restartDoControlRestart重启服务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对属性进行初始化,其操作包含:

  1. 创建存放属性的文件夹/dev/__properties__

  2. 加载各分区(/{partition}/etc/selinux/{partition}_property_contexts)针对属性的selinux上下文,通过BuildTrie格式化后,写入到/dev/__properties__/property_info中。

  3. 加载/dev/__properties__/property_info到内存中,并存储PropertyInfoArea数据结构(在第2步中,BuildTrie的时候预留了PropertyInfoArea数据结构存放的空间)。到这里开始,property_info就被映射到内存中了,后续会使用这块共享内存区域存储属性。

  4. 初始化bionic C库的系统属性区域,通过__system_property_area_init,使用此前Init创建好的共享内存。

  5. 后续将会通过下面三个方法来拿到kernel及引导配置属性

  6. 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,才会进行后续的处理流程。
  1. ProcessKernelCmdline - 来源于/proc/cmdline中所有androidboot.前缀的属性,找到后会替换前缀为ro.boot.并设置。

  2. ProcessBootconfig - 来源于/proc/bootconfig中所有androidboot.前缀的属性,找到后会替换前缀为ro.boot.并设置。

  3. 通过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", },

  1. 接着会读取个分区下的属性并设置。

  2. 读取各分区的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 (可能不存在)

  1. 从init第二阶段的资源中获取属性

  2. 从ramdisk中获取debug属性

  3. 从厂商自定的属性文件中获取属性

  4. 根据各分区属性值,设置产品基本只读属性。该属性来源的优先级可以由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

  1. 初始化build id属性

  2. 设置ro.build.fingerprint,通过一系列只读属性动态拼接而来,具体细节可以查看ConstructBuildFingerprint函数。

  3. 设置ro.build.legacy.fingerprint(可能不存在,通过ro.build.legacy.id判断)

  4. 如果不存在,设置cpu api标识ro.product.cpu.abilist。(通过ro.{partition}.product.cpu.abilist64获取,来源优先级分区顺序是product,odm,vendor,system)

  5. 设置vendor abi级别 ro.vendor.api_level。

  6. 最后更新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程序和实际要运行的程序一起加载到内存中。

Pasted image 20240318213011

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值。

Android属性存储

原文链接:ovea-y.cn/android_pro…