Android Property模块

Android Property模块

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

一、导言

在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_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对属性进行初始化,其操作包含:

  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.前缀属性。

//  源属性               将设置的目标属性    无源属性时的默认值
{ "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

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

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

Read more

香港银行开户指南

香港银行开户指南

注意:本文所有内容,都是需要前往香港的情况下才能使用! 本文主要介绍2家实体银行和3家虚拟银行! 实体银行包含: * 中国银行(香港) * 汇丰银行(香港),该银行也被称为“香港上海汇丰银行” 虚拟银行包含: * 众安银行 * 天星银行 * 蚂蚁银行 1、各银行所需资料和办理方式 银行名称 办理方式 所需材料 & 要求 备注 汇丰银行(香港) 提前预约,线下到营业点办理 (如果没预约,只能很早排队取线下号,不一定能取到) 必备证件: 1. 港澳通行证 2. 入境海关小票 3. 身份证 投资证明: 1. 证券App近三月股票交割单 2. 中国结算近三月交割单 3. 银行近三月流水单 4. 支付宝投资流水 资产证明: 1. 银行资产证明 2.

By 夕元
C++协程(Coroutines)

C++协程(Coroutines)

原文链接:https://ovea-y.cn/cpp_coroutine_20/ C++协程(Coroutines) 1. 简单介绍协程 协程可以简单的理解为,它是一个可以随时“中断”,并再次恢复执行的函数。 C++协程框架的特点: * 无栈协程 * 非对称设计(开发者可以自行设计协程调度器,做成对称设计) 2. 协程和函数的区别 函数:函数调用是线性、同步、一次性的执行模式,调用者必须等待被调用方法执行完成后返回。 协程:协程可以异步执行,调用者不需要等待协程完成,可以在协程挂起时继续做其他事情。在执行过程中通过特定的语法(co__yield_和_co__await)暂停执行,并在之后的某个时刻恢复执行。 2.1 普通函数的执行过程 一个普通函数在执行的时候,主要包含两个操作,分别是调用(call)和返回(return)

By 夕元
GitHub Workflows

GitHub Workflows

原文链接:https://ovea-y.cn/github_workflows/ 本文介绍GitHub工作流的创建和作用。 工作流创建方式 在git项目下,创建.github/workflows文件夹,里面编写的任何yml文件都是工作流的一部分。 secrets的创建方式 secrets.GITHUB_TOKEN是GitHub自动创建的,不需要自己创建。 secrets.SERVER_HOST这个的来源,需要在GitHub对应的项目的设置中设置。 readme的构建标签 配置好Branch、Event类型后,把下面这段内容复制到markdown文档里,就会自动显示自动化流程状态了。其实就是一张图片,由GitHub自动生成提供。 一. 自动构建网站并推送到远程服务器 自动构建hugo,并更新远程服务器的静态网站内容。 自动化流程文件 name: Deploy Hugo Site # 当推送到main分支时触发部署流程 on: push: branches: - main

By 夕元
代码版本管理工具(git/gerrit/repo)

代码版本管理工具(git/gerrit/repo)

原文链接:https://ovea-y.cn/code_version_control_tools__git_gerrit_repo/ 一、版本控制工具的历史 1.1 版本控制雏形 在版本控制软件出现之前,就具备diff与patch工具来对源码进行比较和打补丁了,在CVS出来的一段时间里,Linus一直在使用diff与patch工具管理着Linux的代码。diff与patch也是源码版本控制中最基本的概念。 1.1.1 diff —— 用于比较两个文件或目录之间的差异 -u 表示使用 unified 格式 -r 表示比较目录 -N 表示将不存在的文件当作空文件处理,这样新添加的文件也会出现在patch文件中 diff -urN a.c b.c > c.patch 1.1.2 patch —— 用于应用差异修改 通过patch可以将原始文件变成目标文件,

By 夕元