Android Property模块

注意
本文最后更新于 2024-03-18,文中内容可能已过时。

原文链接:https://ovea-y.cn/android_property_module/

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

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

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

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

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

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

  • 常规属性可以设置多次,重启后丢失
  1. 系统运行时设置。

  2. 构建时添加。

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

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

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

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

  1. 系统运行时设置。

  2. Kernel DT、Kernel Cmdline、Boot Config。

  3. 构建时添加,属性可以指定存放的分区,system/vendor分区在构建后会将属性生成在{partition}/build.prop文件中。其他分区会放在{partition}/etc/build.prop文件中。

例如:

Plaintext

# 下面的属性,将会在构建后生成在system/build.prop中
PRODUCT_SYSTEM_PROPERTIES += ro.zygote.rescue=true
# 下面的属性,将会在构建后生成在vendor/build.prop中
PRODUCT_VENDOR_PROPERTIES += ro.vendor.extend_memory=true

像是persist.sys.stability.evo_enable这样的属性,是持久化属性,

  • 该属性可以被设置多次(可被修改);

  • 该属性被存放一份到/data/property/persistent_properties文件中,因此重启之后,该属性也依旧存在。

  • 持久化属性的加载分为两个阶段,第一个阶段从构建产生的属性文件中获取并加载(时间点在解析rc文件前),第二个阶段从data分区的文件中加载其他持久存储的属性(时间点在post-fs-data阶段,执行vdc后),并且覆盖第一阶段同名的持久存储属性。这就意味着:

    • 后期(非构建时添加)设置的持久存储属性,在post-fs-data阶段之前都是没有加载的

    • 后期(非构建时添加)设置的持久存储属性,在post-fs-data阶段之后会覆盖构建时添加的同名属性!

  1. 系统运行时设置。

  2. 构建时添加。

设置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

sys.powerctl属性可以控制系统的运行状态,常见的值有

  • reboot,[reason] - 控制系统重启,如果reason是userspace,将会触发软重启

  • shutdown,[reason] - 控制系统关机

该属性被设置后(保留在内存中,重启丢失),属性服务会通过PropertyChanged通知Init,触发ShutdownState进行重启或关机操作。

用于对文件或文件夹打上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及引导配置属性

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

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

  6. 通过ExportKernelBootProps将部分ro.boot.前缀属性设置为ro.前缀属性。

Plaintext

//  源属性               将设置的目标属性    无源属性时的默认值
{ "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. 接着会读取个分区下的属性并设置。

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

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

    3. 从ramdisk中获取debug属性

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

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

    6. 初始化build id属性

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

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

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

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

    11. 最后更新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

添加属性。

更新属性。

读取短key短value的属性(key小于32字节)

读取属性,并通过回调函数回传。支持长key、长value。

获取属性,并存储到value,返回值为属性值长度。

查找属性,不存在返回null,存在则返回prop_info指针。

通过ro.property_service.version属性,来判断当前设置属性的协议版本(目前有1和2两个协议)。

旧协议的限制如下:

  • 属性名必须小于32字节(PROP_NAME_MAX)

  • 属性值必须小于92字节(PROP_VALUE_MAX)

新协议限制如下:

  • 非只读属性,属性值必须小于92字节(PROP_VALUE_MAX)

Bionic C库在这的作用,是封装属性设置的交互细节,对外暴露和__system_property_get类似的属性设置接口。

等待属性被设置,可以设置超时时间。

等待任意属性被设置。

核心接口

  • 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库独有实现

核心接口

  • 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”

核心接口

  • 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属性存储

原文链接:https://ovea-y.cn/android_property_module/

相关内容