Google 致力于为黑人社区推动种族平等。查看具体举措

添加系统属性

本页将介绍用于在 Android 中添加或定义系统属性的常规方法,以及用于重构现有系统属性的指南。在进行重构时,除非存在严重的兼容性问题,否则请一律遵循这些指南。

背景

由于系统属性易于使用,因此可用于多种用途。尽管系统属性与编程语言中的全局变量类似,但在创建这些属性时,不存在一系列规范化的流程要求或命名惯例。因此,系统属性理解和维护起来可能会比较困难。本页将介绍定义、添加和维护系统属性的步骤。

准备工作:考虑采用替代方案

确定系统属性是否真正满足您的需求,以及是否为唯一选项。系统属性是具备特定优势的系统级资源。它们简单易用,且它们的非动态属性使其具有相对较低的性能开销。使用系统属性时,即使系统属性在多个进程之间共享,您也无需使用进程间通信 (IPC)。然而,系统属性与全局变量类似,若被滥用则会造成损害。滥用系统属性会导致用户无法访问应用,且会引入安全漏洞等问题。

请考虑下文中的各类系统属性替代方案,评估其中是否有能满足您需求的方案。

共享首选项 (Shared preferences)

对于应用本地的持久性配置,请使用 Shared Preferences 接口,而不是系统属性。

HAL

当配置的可信来源来自设备上的硬件组件时,HAL 必须提供该组件的信息。请勿配置 HAL 使其写入系统属性,以允许其他进程读取(也不要让其他进程写入系统属性以允许 HAL 读取)。请定义新的 HAL 方法以访问配置。换言之,请勿将系统属性用作 HAL 的旁路通信机制。

请注意,该用例无需全新的 HAL,因为使用全新的 HAL 成本较高。请仅在您具有现有 HAL 来抽象硬件组件时,才采纳此建议。

配置文件

如果配置数据是静态数据但非常复杂(即结构化数据),请考虑为配置数据使用 XML 或其他此类格式。确保文件架构保持稳定。对于 XML 文件,您可以使用 xsd_config 使架构保持稳定,并利用自动生成的 XML 解析器。

Binder 服务

系统属性通常用于与多个进程共享子系统的动态状态。此类状态可以在 binder 服务(例如对象字段)中实现,其他进程可以使用对服务的 binder 调用读取(甚至修改)状态。这是首选解决方案,因为将状态设置为系统属性并授予对属性的直接访问权限需要类似以下的流程:

  1. 对状态执行健全性检查。
  2. 对状态执行后处理,使其可供使用。
  3. 执行精细的访问权限控制功能。
  4. 保持状态的结构化格式。

将状态实现为系统属性时,成功完成此过程基本不可能或非常困难。因此,在状态的读取器已有权访问 binder 服务时,请使用 binder 服务选项。

第 1 步:定义系统属性

在添加系统属性时,确定属性的名称,并将其与 SELinux 属性上下文相关联。如果没有合适的现有上下文,就创建新的上下文。该名称在访问属性时使用;属性上下文用于根据 SELinux 控制访问权限。名称可为任何字符串,但 AOSP 建议您采用结构化格式,以使它们更清晰。

属性名称

将此格式与 snake_case 大小写格式配合使用:

[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]

对元素 prefix 使用“”(省略)、ro(用于仅设置一次的属性)或 persist(用于在重新启动过程中保持不变的属性)。

注意事项

请仅在您确定以后不再需要将 prefix 设置为可写时,才使用 ro。**请勿指定 ro 前缀。**请依靠 sepolicy 将 prefix 设为只读(换言之,仅可通过 init 写入)。

请仅在您确定重新启动后仍必须保留值,且使用系统属性是唯一选择时,才使用 persist。(如需了解详情,请参阅准备工作部分。)

Google 会严格审核具有 ropersist 属性的系统属性。

group 一词用于聚合相关属性。它应该是用法与 audiotelephony 类似的子系统名称。请勿使用模糊或重载字词,例如 syssystemdevdefaultconfig

通常的做法是使用对系统属性具有专属读写访问权限的进程的域类型的名称。例如,对于 vold 进程对其具有写入权限的系统属性,通常使用 vold(进程的域类型的名称)作为群组名称。

如有必要,请添加 subgroup 以进一步对属性进行分类,但应避免使用模糊或重载字词来描述此元素。(您还可以有多个 subgroup。)

许多群组名称已经定义。请查看 system/sepolicy/private/property_contexts 文件并尽可能使用现有的群组名称,而不是创建新的群组名称。下表提供了常用群组名称的示例。

群组(和子群组)
蓝牙相关 bluetooth
来自内核 cmdline 的 sysprop boot
用于标识 build 的 sysprop build
电话相关 telephony
音频相关 audio
图形相关 graphics
vold 相关 vold

下面定义了在前一正则表达式示例nametype 的用法。

[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]

  • name 用于标识群组中的系统属性。

  • type 是一个可选元素,用于阐明系统属性的类型或 intent。例如,不要将 sysprop 命名为 audio.awesome_feature_enabled 或仅命名为 audio.awesome_feature,而要将其重命名为 audio.awesome_feature.enabled,以反映系统属性类型和 intent。

对于类型并未提出具体的规则;以下是使用建议:

  • enabled:如果类型是用于启用或停用功能的布尔值系统属性,请使用此类型。
  • config:如果 intent 是为了阐明系统属性代表系统的动态状态,请使用此类型;它表示一个预配置的值(例如,只读对象)。
  • List:如果系统属性的值为列表,请使用此类型。
  • Timeoutmillis:如果它是超时值(以毫秒为单位)的系统属性,请使用此类型。

示例:

  • persist.radio.multisim.config
  • drm.service.enabled

属性上下文

新的 SELinux 属性上下文方案可提供更精细的粒度和更具描述性的名称。与属性名称类似,AOSP 建议您采用以下格式:

{group}[_{subgroup}]*_prop

字词定义如下:

groupsubgroup 的含义与前一正则表达式示例中定义的相同。例如,vold_config_prop 表示由供应商配置且由 vendor_init 设置的属性,而 vold_status_propvold_prop 表示将公开 vold 的当前状态的属性。

在命名属性上下文时,请选择能够体现属性一般用法的名称。尤其要避免使用以下类型的字词:

  • 看起来过于宽泛和模糊的字词,例如 syssystemdefault
  • 直接对可访问性进行编码的字词,如 exportedapponlyropublicprivate

优先使用 vold_config_prop 之类的名称,而不是 exported_vold_propvold_vendor_writable_prop 等。

类型

属性类型可以是表格中列出的下列类型之一。

类型 定义
布尔值 true1 表示 true,false0 表示 false
整数 64 位有符号整数
双精度 双精度浮点数
字符串 任何有效的 UTF-8 字符串
枚举 值可以是任何不含空格的有效 UTF-8 字符串
上述类型值的列表 使用英文逗号 (,) 作为分隔符
整数列表 [1, 2, 3] 被存储为 1,2,3

在内部,所有属性都存储为字符串。您可以通过将其指定为 property_contexts 文件来强制执行该类型。如需了解详情,请参阅第 3 步中的 property_contexts

第 2 步:确定所需的可访问性级别

以下是定义属性的 4 个辅助宏。

可访问性类型 含义
system_internal_prop 仅在 /system 中使用的属性
system_restricted_prop /system 外部读取而不写入的属性
system_vendor_config_prop /system 外部读取且仅由 vendor_init 写入的属性
system_public_prop /system 外部读取和写入的属性

请尽可能缩小对系统属性的访问范围。在过去,广泛访问会导致应用中断和安全漏洞。确定范围时,请考虑以下问题:

  • 是否需要保留此系统属性?(如果是,原因是什么?)
  • 哪些进程应具有该属性的读取权限?
  • 哪些进程应具有该属性的写入权限?

以上述问题和如下决策树作为用于确定适当访问范围的工具。

用于确定访问范围的决策树

图 1. 用于确定系统属性访问范围的决策树

第 3 步:将其添加到 system/sepolicy

访问 sysprop 时,SELinux 会控制进程的可访问性。确定所需可访问性的级别后,请在 system/sepolicy 下定义属性上下文,以及其他关于进程允许(或不允许)读写的 allow 和 neverallow 规则。

首先,在 system/sepolicy/public/property.te 文件中定义属性上下文。如果属性是系统内部属性,请在 system/sepolicy/private/property.te 文件中进行定义。使用其中一个提供系统属性所需的可访问性的 system_[accessibility]_prop([context]) 宏。以下是 system/sepolicy/public/property.te 文件的示例:

system_public_prop(audio_foo_prop)
system_vendor_config_prop(audio_bar_prop)

system/sepolicy/private/property.te 文件中进行添加的示例:

system_internal_prop(audio_baz_prop)

其次,授予对属性上下文的读取和(或)写入权限。在 system/sepolicy/public/{domain}.tesystem/sepolicy/private/{domain}.te 文件中使用 set_propget_prop 宏来授予访问权限。尽可能使用 private;仅当 set_propget_prop 宏影响核心网域以外的任何网域时,才适合使用 public

例如,在 system/sepolicy/private/audio.te 文件中:

set_prop(audio, audio_foo_prop)
set_prop(audio, audio_bar_prop)

例如,在 system/sepolicy/public/domain.te 文件中:

get_prop(domain, audio_bar_prop)

第三,添加一些 neverallow 规则,以进一步降低宏作用域的可访问性。例如,假设您使用了 system_restricted_prop,因为您的系统属性必须由供应商进程读取。如果不是所有供应商进程都需要读取访问权限,并且只有一组特定进程(例如 vendor_init)需要该访问权限,就应禁止不需要读取访问权限的供应商进程。

使用以下语法限制写入和读取访问权限:

如需限制写入访问权限,请执行以下操作:

neverallow [domain] [context]:property_service set;

如需限制读取访问权限,请执行以下操作:

neverallow [domain] [context]:file no_rw_file_perms;

如果 neverallow 规则被绑定到特定网域,请在 system/sepolicy/private/{domain}.te 文件中添加 neverallow 规则。如需获得更广泛的 neverallow 规则,请使用通用域(如下所示):

  • system/sepolicy/private/property.te
  • system/sepolicy/private/coredomain.te
  • system/sepolicy/private/domain.te

system/sepolicy/private/audio.te 文件中放置以下内容:

neverallow {
    domain -init -audio
} {audio_foo_prop audio_bar_prop}:property_service set;

system/sepolicy/private/property.te 文件中放置以下内容:

neverallow {
    domain -coredomain -vendor_init
} audio_prop:file no_rw_file_perms;

请注意,{domain -coredomain} 会捕获所有供应商进程。因此,{domain -coredomain -vendor_init} 表示“除 vendor_init 之外的所有供应商进程”。

最后,将系统属性与属性上下文相关联。这可确保授予的访问权限以及应用于属性上下文的 neverallow 规则会应用于实际属性。为此,请向 property_contexts 文件添加一个条目,该文件说明了系统属性与属性上下文之间的映射。在该文件中,您可以指定单个属性,也可以为要映射到上下文的属性指定前缀。

以下是映射单个属性的语法:

[property_name] u:object_r:[context_name]:s0 exact [type]

以下是映射前缀的语法:

[property_name_prefix] u:object_r:[context_name]:s0 prefix [type]

您可以选择指定属性的类型,它可以是以下类型之一:

  • bool
  • int
  • double
  • enum [list of possible values...]
  • string(对于列表属性,请使用 string。)

尽可能确保每个条目都具有指定的类型,因为在设置 property 时会强制执行 type。以下示例展示了如何编写映射:

# binds a boolean property "ro.audio.status.enabled"
# to the context "audio_foo_prop"
ro.audio.status.enabled u:object_r:audio_foo_prop:s0 exact bool

# binds a boolean property "vold.decrypt.status"
# to the context "vold_foo_prop"
# The property can only be set to one of these: on, off, unknown
vold.decrypt.status u:object_r:vold_foo_prop:s0 exact enum on off unknown

# binds any properties starting with "ro.audio.status."
# to the context "audio_bar_prop", such as
# "ro.audio.status.foo", or "ro.audio.status.bar.baz", and so on.
ro.audio.status. u:object_r:audio_bar_prop:s0 prefix

如果确切条目与前缀条目冲突,则优先考虑确切条目。更多示例请参阅 system/sepolicy/private/property_contexts

第 4 步:确定稳定性要求

稳定性是系统属性的另一个方面,与可访问性不同。稳定性是指将来是否可以更改系统属性(例如重命名,甚至移除)。由于 Android 操作系统变得模块化,这一点尤为重要。使用 Treble,系统、供应商和产品分区可以相互独立更新。使用 Mainline,操作系统的某些部分被模块化为可更新的模块(以 APEX 或 APK 形式)。

如果系统属性被用于可更新的软件(例如,系统分区和供应商分区之间),就必须保持稳定。然而,如果它仅用于特定的 Mainline 模块等,您可以更改它的名称、类型或属性上下文,甚至将其移除。

通过回答以下问题,确定系统属性的稳定性:

  • 此系统属性是否由合作伙伴配置(或按设备进行不同配置)?如果是,就必须保持稳定。
  • 由 AOSP 定义的此系统属性是否旨在写入位于 vendor.imgproduct.img 等非系统分区中的代码(而非进程)或从中读取数据?如果是,就必须保持稳定。
  • 此系统属性是否跨多个或单个 Mainline 模块以及平台中不可更新的部分访问?如果是,就必须保持稳定。

对于稳定的系统属性,请正式将每个属性定义为一个 API,并使用该 API 访问系统属性,如第 6 步中所述。

第 5 步:在构建时设置属性

在构建时使用 makefile 变量设置属性。从技术上讲,这些值将被内置到 {partition}/build.prop。然后,init 会读取 {partition}/build.prop 以设置属性。有两组此类变量:PRODUCT_{PARTITION}_PROPERTIESTARGET_{PARTITION}_PROP

PRODUCT_{PARTITION}_PROPERTIES 包含属性值的列表。语法为 {prop}={value}{prop}?={value}

{prop}={value} 是常规赋值,可确保将 {prop} 设置为 {value};每种属性只能有一个此类赋值。

{prop}?={value} 是可选赋值;仅在没有任何 {prop}={value} 赋值时,{prop} 才会设为 {value}。如果存在多个可选赋值,则第一赋值为优先选项。

# sets persist.traced.enable to 1 with system/build.prop
PRODUCT_SYSTEM_PROPERTIES += persist.traced.enable=1

# sets ro.zygote to zygote32 with system/build.prop
# but only when there are no other assignments to ro.zygote
# optional are useful when giving a default value to a property
PRODUCT_SYSTEM_PROPERTIES += ro.zygote?=zygote32

# sets ro.config.low_ram to true with vendor/build.prop
PRODUCT_VENDOR_PROPERTIES += ro.config.low_ram=true

TARGET_{PARTITION}_PROP 包含一系列文件,这些文件会直接发送到 {partition}/build.prop。每个文件均包含一个 {prop}={value} 配对列表。

# example.prop

ro.cp_system_other_odex=0
ro.adb.secure=0
ro.control_privapp_permissions=disable

# emits example.prop to system/build.prop
TARGET_SYSTEM_PROP += example.prop

如需了解详情,请参阅 build/make/core/sysprop.mk

第 6 步:在运行时访问属性

当然,您可以在运行时读取和写入属性。

Init 脚本

Init 脚本文件(通常是 *.rc 文件)可以通过 ${prop}${prop:-default} 读取属性,可以设置在属性成为特定值时运行的操作,并可以使用 setprop 命令写入属性。

# when persist.device_config.global_settings.sys_traced becomes 1,
# set persist.traced.enable to 1
on property:persist.device_config.global_settings.sys_traced=1
    setprop persist.traced.enable 1

# when security.perf_harden becomes 0,
# write /proc/sys/kernel/sample_rate to the value of
# debug.sample_rate. If it's empty, write -100000 instead
on property:security.perf_harden=0
    write /proc/sys/kernel/sample_rate ${debug.sample_rate:-100000}

getprop 和 setprop shell 命令

您可以分别使用 getpropsetprop shell 命令读取或写入属性。如需了解详情,请调用 getprop --helpsetprop --help

$ adb shell getprop ro.vndk.version
$
$ adb shell setprop security.perf_harden 0

Sysprop 作为 C++/Java 的 API

使用 sysprop 作为 API,您可以定义系统属性并使用具体的和类型化的自动生成的 API。使用 Public 设置 scope 还能使生成的 API 可用于跨边界的模块,并确保 API 的稳定性。下面是 .sysprop 文件、Android.bp 模块以及同时使用它们的 C++ 和 Java 代码的示例。

# AudioProps.sysprop
# module becomes static class (Java) / namespace (C++) for serving API
module: "android.sysprop.AudioProps"
# owner can be Platform or Vendor or Odm
owner: Platform
# one prop defines one property
prop {
    prop_name: "ro.audio.volume.level"
    type: Integer
    scope: Public
    access: ReadWrite
    api_name: "volume_level"
}
…

// Android.bp
sysprop_library {
    name: "AudioProps",
    srcs: ["android/sysprop/AudioProps.sysprop"],
    property_owner: "Platform",
}

// Both java and cc module can link against sysprop_library
java_library {
    static_libs: ["AudioProps"],
    …
}

cc_binary {
    static_libs: ["AudioProps"],
    …
}

// Java codes accessing generated API
// get volume. use 50 as the default value.
int vol = android.sysprop.AudioProps.volume_level().orElse(50);
// add 10 to the volume level.
android.sysprop.AudioProps.volume_level(vol + 10);

// C++ codes accessing generated API
// get volume. use 50 as the default value.
int vol = android::sysprop::AudioProps::volume_level().value_or(50);
// add 10 to the volume level.
android::sysprop::AudioProps::volume_level(vol + 10);

如需了解详情,请参阅将系统属性作为 API 实现

C/C++ 和 Java 低层属性函数和方法

请使用 Sysprop 作为 API,即使您可以使用低层 C/C++ 函数或低层 Java 方法。请尽可能优先使用 Sysprop。

libclibbaselibcutils 提供 C++ 系统属性函数。libc 具有底层 API,而 libbaselibcutils 函数是封装容器。如果可行,请使用 libbase sysprop 函数;它们最为便利,并且主机二进制文件可以使用 libbase 函数。如需了解详情,请参阅 sys/system_properties.h (libc)、android-base/properties.h (libbase) 和 cutils/properties.h (libcutils)。

android.os.SystemProperties 类提供 Java 系统属性方法。

附录:添加特定于供应商的属性

合作伙伴(包括在 Pixel 开发环境中工作的 Google 员工)希望定义特定于硬件(或特定于设备)的系统属性。特定于供应商的属性是合作伙伴所拥有的属性,对于自己的硬件或设备而言是唯一的,而对于平台而言则并非如此。由于它们依赖于硬件或设备,因此它们应在 /vendor/odm 分区中使用。

从 Treble 计划开始,平台属性和供应商属性已被完全分开,以避免出现冲突。下文将介绍如何定义供应商属性,以及必须始终使用哪些供应商属性。

属性和上下文名称的命名空间

所有供应商属性都必须以如下某个前缀开头,以防止它们与其他分区的属性发生冲突。

  • ctl.odm.
  • ctl.vendor.
  • ctl.start$odm.
  • ctl.start$vendor.
  • ctl.stop$odm.
  • ctl.stop$vendor.
  • init.svc.odm.
  • init.svc.vendor.
  • ro.odm.
  • ro.vendor.
  • odm.
  • persist.odm.
  • persist.vendor.
  • vendor.

请注意,允许将 ro.hardware. 用作前缀,但仅是为了保证兼容性。请不要将其用于常规属性。

以下示例均使用此前列出的前缀之一:

  • vendor.display.primary_red
  • persist.vendor.faceauth.use_disk_cache
  • ro.odm.hardware.platform

所有供应商属性上下文都必须以 vendor_ 开头。这也是为了保证兼容性。示例如下:

  • vendor_radio_prop
  • vendor_faceauth_prop
  • vendor_usb_prop

供应商负责命名和维护属性,因此除了满足供应商命名空间的要求之外,还应遵循第 2 步中建议的格式。

特定于供应商的 SEPolicy 规则和 property_contexts

与平台属性类似,供应商属性可以通过以下宏之一定义。

可访问性类型 含义
vendor_internal_prop 仅在 /vendor 中使用的属性
vendor_restricted_prop /vendor 外部读取而不写入的属性
vendor_public_prop /vendor 外部读取和写入的属性

将您定义的特定于供应商的规则放入 BOARD_VENDOR_SEPOLICY_DIRS 目录中。例如,假设您在 coral 中定义一个供应商的 faceauth 属性。

BoardConfig.mk 文件(或任何 BoardConfig.mk 的目录)中添加以下内容:

BOARD_VENDOR_SEPOLICY_DIRS := device/google/coral-sepolicy

device/google/coral-sepolicy/private/property.te 文件中添加以下内容:

vendor_internal_prop(vendor_faceauth_prop)

device/google/coral-sepolicy/private/property_contexts 文件中添加以下内容:

vendor.faceauth.trace u:object_r:vendor_faceauth_prop:s0 exact bool

供应商属性的限制

由于系统分区和产品分区无法依赖供应商,因此不允许从 systemsystem-extproduct 分区访问供应商属性。

附录:重命名现有属性

当必须弃用某个属性并使用新属性时,请使用 Sysprop 作为 API 来重命名现有属性。通过同时指定旧属性名称和新属性名称,可以保持向后兼容性。具体而言,您可以通过 .sysprop 文件中的 legacy_prop_name 字段设置旧属性名称。生成的 API 会尝试读取 prop_name;如果 prop_name 不存在,则使用 legacy_prop_name

例如,以下步骤会将 awesome_feature_foo_enabled 重命名为 foo.awesome_feature.enabled

foo.sysprop 文件中(采用 Java 语言)

module: "android.sysprop.foo"
owner: Platform
prop {
    api_name: "is_awesome_feature_enabled"
    type: Boolean
    scope: Public
    access: Readonly
    prop_name: "foo.awesome_feature.enabled"
    legacy_prop_name: "awesome_feature_foo_enabled"
}

在 C++ 代码中

// is_awesome_feature_enabled() reads "foo.awesome_feature.enabled".
// If it doesn't exist, reads "awesome_feature_foo_enabled" instead
using android::sysprop::foo;

bool enabled = foo::is_awesome_feature_enabled().value_or(false);

请注意以下事项:

  • 首先,您无法更改 sysprop 的类型。例如,您无法将 int prop 改为 string prop。您只能更改名称。

  • 其次,只有读取 API 才能回退到旧属性名称。写入 API 不会回退。如果 sysprop 是可写的,您无法对其进行重命名。