目录
Android 9.0 Configstore HAL
Android 8.0 将整个 Android 操作系统拆分为通用分区 (system.img
) 和特定于硬件的分区(vendor.img
和 odm.img
)。受这种变更的影响,您必须从安装到系统分区的模块中移除条件式编译,而且此类模块必须在运行时确定系统配置(并根据相应配置采取不同的行为)。
ConfigStore HAL概述
ConfigStore HAL 提供了一组 API,可供访问用于配置 Android 框架的只读配置项。
本页面介绍了 ConfigStore HAL 的设计(以及不使用系统属性来访问只读配置项的原因);
本部分的其他页面详细介绍了 HAL 接口、服务实现和客户端使用情况,所有这些均以 surfaceflinger
为例。
如需获得 ConfigStore 接口类的相关帮助,请参阅添加接口类和项。
为什么不使用系统属性?
考虑过使用系统属性,但发现了以下几个重大问题,例如:
- 值的长度受限。 系统属性对其值的长度具有严格限制(92 个字节)。此外,由于这些限制已作为 C 宏直接提供给 Android 应用,增加长度会导致出现向后兼容性问题。
- 无类型支持。 所有值本质上都是字符串,而 API 仅仅是将字符串解析为
int
或bool
。其他复合数据类型(如数组和结构体)应由客户端进行编码/解码(例如,"aaa,bbb,ccc"
可以解码为由三个字符串组成的数组)。 - 覆盖。 由于只读系统属性是以一次写入属性的形式实现的,因此如果供应商/原始设计制造商 (ODM) 想要覆盖 AOSP 定义的只读值,则必须先导入自己的只读值,然后再导入 AOSP 定义的只读值。这反过来会导致供应商定义的可重写值被 AOSP 定义的值覆盖。
- 地址空间要求。 系统属性在每个进程中都会占用较大的地址空间。系统属性在
prop_area
单元中以 128KB 的固定大小进行分组,即使目前只访问该单元中的一个系统属性,其中的所有属性也将会分配到进程地址空间。这可能会导致对地址空间需求较高的 32 位设备出现问题。
我们曾尝试在不牺牲兼容性的情况下克服这些限制,但依然会担心系统属性的设计不支持访问只读配置项。最终,我们判定系统属性更适合在所有 Android 中实时共享一些动态更新内容,因此需要采用一个专用于访问只读配置项的新系统。
ConfigStore HAL 设计
基本设计很简单:
图 1. ConfigStore HAL 设计
- 以 HIDL 描述编译标记(目前用于对框架进行条件式编译)。
- 供应商和原始设备制造商 (OEM) 通过实现 HAL 服务为编译标记提供 SoC 和设备特定值。
- 修改框架,以使用 HAL 服务在运行时查找配置项的值。
当前由框架引用的配置项会包含在具有版本号的 HIDL 软件包 (android.hardware.configstore@1.0
) 中。供应商/原始设备制造商 (OEM) 通过实现此软件包中的接口为配置项提供值,而框架会在需要获取配置项的值时使用这些接口。
安全注意事项
在同一接口中定义的编译标记会受到相同 SELinux 政策的影响。
如果一个或多个编译标记应具有不同的 SELinux 政策,则必须将这些标记分隔到其他接口。这可能需要对 android.hardware.configstore package
进行重大修订,因为被分隔的接口不再向后兼容。
创建 HAL 接口
必须使用 HIDL 来描述用于对框架进行条件式编译的所有编译标记。相关编译标记必须分组并包含在单个 .hal
文件中。使用 HIDL 指定配置项具有以下优势:
- 可实施版本控制(为了添加新配置项,供应商/OEM 必须明确扩展 HAL)
- 记录详尽
- 可使用 SELinux 实现访问控制
- 可通过供应商测试套件对配置项进行全面检查(范围检查、各项内容之间的相互依赖性检查等)
- 在 C++ 和 Java 中自动生成 API
确定框架使用的编译标记
首先,请确定用于对框架进行条件式编译的编译标记,然后舍弃过时的配置以缩小编译标记集的范围。例如,下列编译标记集已确定用于 surfaceflinger
:
TARGET_USES_HWC2
TARGET_BOARD_PLATFORM
TARGET_DISABLE_TRIPLE_BUFFERING
TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS
NUM_FRAMEBUFFER_SURFACE_BUFFERS
TARGET_RUNNING_WITHOUT_SYNC_FRAMEWORK
VSYNC_EVENT_PHASE_OFFSET_NS
SF_VSYNC_EVENT_PHASE_OFFSET_NS
PRESENT_TIME_OFFSET_FROM_VSYNC_NS
MAX_VIRTUAL_DISPLAY_DIMENSION
创建 HAL 接口
子系统的编译配置是通过 HAL 接口访问的,而用于提供配置值的接口会在 HAL 软件包 android.hardware.configstore
(目前为 1.0 版)中进行分组。例如,要为 surfaceflinger
创建 HAL 接口,请在 hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal
中运行以下命令:
package android.hardware.configstore@1.0;
interface ISurfaceFlingerConfigs {
// TO-BE-FILLED-BELOW
};
创建 .hal
文件后,请运行 hardware/interfaces/update-makefiles.sh
以将新的 .hal
文件添加到 Android.bp
和 Android.mk
文件中。
为编译标记添加函数
对于每个编译标记,请向相应接口各添加一个新函数。例如,在 hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal
中运行以下命令:
interface ISurfaceFlingerConfigs {
disableTripleBuffering() generates(OptionalBool ret);
forceHwcForVirtualDisplays() generates(OptionalBool ret);
enum NumBuffers: uint8_t {
USE_DEFAULT = 0,
TWO = 2,
THREE = 3,
};
numFramebufferSurfaceBuffers() generates(NumBuffers ret);
runWithoutSyncFramework() generates(OptionalBool ret);
vsyncEventPhaseOffsetNs generates (OptionalUInt64 ret);
presentTimeOffsetFromSyncNs generates (OptionalUInt64 ret);
maxVirtualDisplayDimension() generates(OptionalInt32 ret);
};
添加函数时,请注意以下事项:
- 采用简洁的名称。 请避免将 makefile 变量名称转换为函数名称,并切记
TARGET_
和BOARD_
前缀不再是必需的。 - 添加注释。 帮助开发者了解配置项的用途,配置项如何改变框架行为、有效值以及其他相关信息。
函数返回类型可以是 Optional[Bool|String|Int32|UInt32|Int64|UInt64]
。类型会在同一目录中的 types.hal
中进行定义,并使用字段(可表明原始值是否是由 HAL 指定)来封装原始值;如果原始值不是由 HAL 指定,则使用默认值。
struct OptionalString {
bool specified;
string value;
};
在适当的情况下,请定义最能代表配置项类型的枚举,并将该枚举用作返回类型。在上述示例中,NumBuffers
枚举会被定义为限制有效值的数量。在定义此类自定义数据类型时,请添加字段或枚举值(例如,USE_DEFAULT
)来表示该值是否由 HAL 指定。
在 HIDL 中,单个编译标记并不一定要变成单个函数。模块所有者也可以将密切相关的编译标记汇总为一个结构体,并通过某个函数返回该结构体(这样做可以减少函数调用的次数)。
例如,用于在 hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal
中将两个编译标记汇总到单个结构体的选项如下:
interface ISurfaceFlingerConfigs {
// other functions here
struct SyncConfigs {
OptionalInt64 vsyncEventPhaseoffsetNs;
OptionalInt64 presentTimeoffsetFromSyncNs;
};
getSyncConfigs() generates (SyncConfigs ret);
// other functions here
};
单个 HAL 函数的替代函数
作为针对所有编译标记使用单个 HAL 函数的替代函数,HAL 接口还提供了 getBoolean(string key)
和 getInteger(string key)
等简单函数。实际的 key=value
对会存储在单独的文件中,而 HAL 服务会通过读取/解析这些文件来提供值。
虽然这种方法很容易定义,但它不具备 HIDL 提供的优势(强制实施版本控制、便于记录、实现访问控制),因此不推荐使用。
注意:在使用简单函数时,几乎不可能实现访问控制,因为 HAL 自身无法识别客户端。
单个接口与多个接口
面向配置项设计的 HAL 接口提供了以下两种选择:
- 单个接口;涵盖所有配置项
- 多个接口;每个接口分别涵盖一组相关配置项
单个接口更易于使用,但随着更多的配置项添加到单个文件中,单个接口可能会越来越难以维护。此外,由于访问控制不够精细,获得接口访问权限的进程可能会读取所有配置项(无法授予对部分配置项的访问权限)。此外,如果未授予访问权限,则无法读取任何配置项。
由于存在这些问题,Android 会针对一组相关配置项将多个接口与单个 HAL 接口搭配使用。例如,对 surfaceflinger
相关配置项使用 ISurfaceflingerConfigs
,对蓝牙相关配置项使用 IBluetoothConfigs
等等。
实现服务
为了准备 HAL 实现,您可以先生成基本的 ConfigStore 接口代码,然后再对其进行修改以满足自己的需求。
生成接口代码
要为接口生成样板代码,请运行 hidl-gen
。 例如,要为 surfaceflinger
生成代码,请运行以下命令:
hidl-gen -o hardware/interfaces/configstore/1.0/default \
-Lc++-impl \
-randroid.hardware:hardware/interfaces \
-randroid.hidl:system/libhidl/transport \
android.hardware.config@1.0::ISurfaceFlingerConfigs
注意:请勿使用 -Landroidbp-impl
运行 hidl-gen
,因为这么做会生成 Android.bp
。该模块必须通过 Android.mk
进行编译才能访问编译标记。
修改 Android.mk
接下来,请修改 Android.mk
文件,以便将实现文件 (<modulename>Configs.cpp
) 添加到 LOCAL_SRC_FILES
,并将编译标记映射到宏定义中。例如,您可以在 hardware/interface/configstore/1.0/default/Android.mk
中修改 surfaceflinger
:
LOCAL_SRC_FILES += SurfaceFlingerConfigs.cpp
ifneq ($(NUM_FRAMEBUFFER_SURFACE_BUFFERS),)
LOCAL_CFLAGS += -DNUM_FRAMEBUFFER_SURFACE_BUFFERS=$(NUM_FRAMEBUFFER_SURFACE_BUFFERS)
endif
ifeq ($(TARGET_RUNNING_WITHOUT_SYNC_FRAMEWORK),true)
LOCAL_CFLAGS += -DRUNNING_WITHOUT_SYNC_FRAMEWORK
endif
如果 Android.mk
包含几个 ifeq-endif
块,请考虑将代码移动到新文件(即 surfaceflinger.mk
)中,然后从 Android.mk
中引用该文件。
实现函数
要填充函数以实现 HAL,请以不同的值回调 _hidl_cb
函数(以编译标记为条件)。例如,您可以在 hardware/interfaces/configstore/1.0/default/SurfaceFlingerConfigs.cpp
中填充 surfaceflinger
的函数:
Return<void> SurfaceFlingerConfigs::numFramebufferSurfaceBuffers(
numFramebufferSurfaceBuffers_cb _hidl_cb) {
#if NUM_FRAMEBUFFER_SURFACE_BUFFERS 2
_hidl_cb(NumBuffers.TWO);
#else if NUM_FRAMEBUFFER_SURFACE_BUFFERS 3
_hidl_cb(NumBuffers.THREE);
#else
_hidl_cb(NumBuffers.USE_DEFAULT);
#endif
}
Return<void> SurfaceFlingerConfigs::runWithoutSyncFramework(
runWithoutSyncFramework_cb _hidl_cb) {
#ifdef RUNNING_WITHOUT_SYNC_FRAMEWORK
_hidl_cb({true /* specified */, true /* value */});
#else
// when macro not defined, we can give any value to the second argument.
// It will simply be ignored in the framework side.
_hidl_cb({false /* specified */, false /* value */});
#endif
}
请确保该实现不包含名为 HIDL_FETCH_interface-name
的函数(例如 HIDL_FETCH_ISurfaceFlingerConfigs
)。这是 HIDL 直通模式所需的函数,configstore
不使用(且被禁止使用)该函数。ConfigStore 必须始终在绑定模式下运行。
注册为服务
最后,将所有接口实现注册为 configstore
服务。例如,您可以在 hardware/interfaces/configstore/1.0/default/service.cpp
中注册 surfaceflinger
实现:
configureRpcThreadpool(maxThreads, true);
sp<ISurfaceFlingerConfigs> surfaceFlingerConfigs = new SurfaceFlingerConfigs;
status_t status = surfaceFlingerConfigs->registerAsService();
sp<IBluetoothConfigs> bluetoothConfigs = new BluetoothConfigs;
status = bluetoothConfigs->registerAsService();
// register more interfaces here
joinRpcThreadpool();
确保可尽早访问
为了确保框架模块可以尽早访问 HAL 服务,config HAL 服务应该在 hwservicemanager
准备就绪之后尽早启动。由于 config HAL 服务不会读取外部文件,因此在启动之后预计很快就能准备就绪。
客户端使用情况
可以重构经过条件式编译的代码,以便从 HAL 接口动态读取值。例如:
#ifdef TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS
//some code fragment
#endif
随后,框架代码便可以调用一个在 <configstore/Utils.h>
中定义的适当效用函数(根据其类型)。
ConfigStore 示例
以下示例显示了读取 TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS
(在 ConfigStore HAL 中定义为 forceHwcForVirtualDisplays()
,返回类型为 OptionalBool
)的情形:
#include <configstore/Utils.h>
using namespace android::hardware::configstore;
using namespace android::hardware::configstore::V1_0;
static bool vsyncPhaseOffsetNs = getBool<ISurfaceFlingerConfigs,
ISurfaceFlingerConfigs::forceHwcForVirtualDisplays>(false);
效用函数(上例中的 getBool
)会与 configstore
服务进行通信以获取接口函数代理的句柄,然后通过 HIDL/hwbinder 来调用句柄,从而检索该值。
效用函数
<configstore/Utils.h>
(configstore/1.0/include/configstore/Utils.h
) 会为每个原始返回类型(包括 Optional[Bool|String|Int32|UInt32|Int64|UInt64]
)提供效用函数,如下所示:
类型 | 函数(已省略模板参数) |
---|---|
OptionalBool | bool getBool(const bool defValue) |
OptionalInt32 | int32_t getInt32(const int32_t defValue) |
OptionalUInt32 | uint32_t getUInt32(const uint32_t defValue) |
OptionalInt64 | int64_t getInt64(const int64_t defValue) |
OptionalUInt64 | uint64_t getUInt64(const uint64_t defValue) |
OptionalString | std::string getString(const std::string &defValue) |
defValue
是在 HAL 实现没有为配置项指定值时返回的默认值。每个函数都需要使用两个模板参数:
I
是接口类名称。Func
是用于获取配置项的成员函数指针。
由于配置值是只读属性且不会发生更改,因此效用函数会在内部缓存配置值。使用同一链接单元中的缓存值可以更有效地执行后续调用。
使用 configstore-utils
ConfigStore HAL 旨在向前兼容次要版本升级,这意味着当 HAL 进行修订并且某些框架代码使用新引入的项时,您仍然可以使用 /vendor
中旧的次要版本的 ConfigStore 服务。
为了实现向前兼容性,请确保在实现过程中遵循以下准则:
- 当只有旧版服务可用时,新项使用默认值。例如:
service = V1_1::IConfig::getService(); // null if V1_0 is installed value = DEFAULT_VALUE; if(service) { value = service->v1_1API(DEFAULT_VALUE); }
- 客户端使用包含 ConfigStore 项的第一个接口。例如:
V1_1::IConfig::getService()->v1_0API(); // NOT ALLOWED V1_0::IConfig::getService()->v1_0API(); // OK
- 可以为旧版接口检索新版服务。在以下示例中,如果已安装版本为 v1_1,则必须为
getService()
返回 v1_1 服务:V1_0::IConfig::getService()->v1_0API();
注意:当前 AOSP 实现符合此要求。
当 configstore-utils
库中的访问函数用于访问 ConfigStore 项时,#1 由实现保证,#2 由编译器错误保证。基于这些原因,我们强烈建议尽量使用 configstore-utils
。
添加 ConfigStore 类和项
可以为现有接口类添加新的 ConfigStore 项(即接口方法)。如果您未定义接口类,则必须先添加一个新类,然后才能为该接口类添加 ConfigStore 项。本部分使用 disableInitBlank
配置项示例来演示将 healthd
添加到 IChargerConfigs
接口类的过程。
注意:请务必先熟悉常规 HIDL 概念、HIDL C++ 开发工作流程、HIDL 代码样式和 ConfigStore 设计,然后再继续操作。
添加接口类
如果您没有为要添加的接口方法定义接口类,则必须先添加接口类,然后才能添加相关联的 ConfigStore 项。
- 创建 HAL 接口文件。ConfigStore 版本为 1.0,因此请在
hardware/interfaces/configstore/1.0
中定义 ConfigStore 接口。例如,在hardware/interfaces/configstore/1.0/IChargerConfigs.hal
中运行以下命令:package android.hardware.configstore@1.0; interface IChargerConfigs { // TO-BE-FILLED-BELOW };
- 为 ConfigStore 共享库和头文件更新
Android.bp
和Android.mk
,以包含新的接口 HAL。例如:
这些命令可在hidl-gen -o hardware/interfaces/configstore/1.0/default -Lmakefile -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.configstore@1.0::IChargerConfigs
hidl-gen -o hardware/interfaces/configstore/1.0/default -Landroidbp -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.configstore@1.0::IChargerConfigs
hardware/interfaces/configstore/1.0
中更新Android.bp
和Android.mk
。 - 生成用于实现服务器代码的 C++ 存根。例如:
此命令可在hidl-gen -o hardware/interfaces/configstore/1.0/default -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.configstore@1.0::IChargerConfigs
hardware/interfaces/configstore/1.0/default
中创建两个文件:ChargerConfigs.h
和ChargerConfigs.cpp
。 - 打开
.h
和.cpp
实现文件,并移除与函数HIDL_FETCH_name
(例如,HIDL_FETCH_IChargerConfigs
)相关的代码。这是 HIDL 直通模式所需的函数,ConfigStore 不使用该模式。 - 将实现注册为 ConfigStore 服务。例如,在
hardware/interfaces/configstore/1.0/default/service.cpp
中运行以下命令:#include <android/hardware/configstore/1.0/IChargerConfigs.h> #include "ChargerConfigs.h" using android::hardware::configstore::V1_0::IChargerConfigs; using android::hardware::configstore::V1_0::implementation::ChargerConfigs; int main() { ... // other code sp<IChargerConfigs> chargerConfigs = new ChargerConfigs; status = chargerConfigs->registerAsService(); LOG_ALWAYS_FATAL_IF(status != OK, "Could not register IChargerConfigs"); ... // other code }
- 修改
Android.mk
文件,以便将实现文件 (modulenameConfigs.cpp
) 添加到LOCAL_SRC_FILES
并将编译标记映射到宏定义中。例如,在hardware/interfaces/configstore/1.0/default/Android.mk
中运行以下命令:LOCAL_SRC_FILES += ChargerConfigs.cpp ifeq ($(strip $(BOARD_CHARGER_DISABLE_INIT_BLANK)),true) LOCAL_CFLAGS += -DCHARGER_DISABLE_INIT_BLANK endif
- (可选)添加清单条目。如果清单条目不存在,则默认添加 ConfigStore 的“default”实例名称。例如,在
device/google/marlin/manifest.xml
中运行以下命令:<hal format="hidl"> <name>android.hardware.configstore</name> ... <interface> <name>IChargerConfigs</name> <instance>default</instance> </interface> </hal>
- 视需要(即如果客户端没有向
hal_configstore
进行 hwbinder 调用的权限)添加 sepolicy 规则。例如,在system/sepolicy/private/healthd.te
中运行以下命令:... // other rules binder_call(healthd, hal_configstore)
添加新的 ConfigStore 项
要添加新的 ConfigStore 项,请执行以下操作:
- 打开 HAL 文件,并为该项添加所需的接口方法(ConfigStore 的
.hal
文件位于hardware/interfaces/configstore/1.0
中)。例如,在hardware/interfaces/configstore/1.0/IChargerConfigs.hal
中运行以下命令:package android.hardware.configstore@1.0; interface IChargerConfigs { ... // Other interfaces disableInitBlank() generates(OptionalBool value); };
- 在相应的接口 HAL 实现文件(
.h
和.cpp
)中实现该方法。将默认实现放置在hardware/interfaces/configstore/1.0/default
中。注意:使用
例如,在-Lc++-impl
运行hidl-gen
将为新添加的接口方法生成框架代码。不过,由于该方法也会覆盖所有现有接口方法的实现,因此请酌情使用-o
选项。hardware/interfaces/configstore/1.0/default/ChargerConfigs.h
中运行以下命令:
在struct ChargerConfigs : public IChargerConfigs { ... // Other interfaces Return<void> disableInitBlank(disableInitBlank_cb _hidl_cb) override; };
hardware/interfaces/configstore/1.0/default/ChargerConfigs.cpp
中运行以下命令:Return<void> ChargerConfigs::disableInitBlank(disableInitBlank_cb _hidl_cb) { bool value = false; #ifdef CHARGER_DISABLE_INIT_BLANK value = true; #endif _hidl_cb({true, value}); return Void(); }
使用 ConfigStore 项
要使用 ConfigStore 项,请执行以下操作:
- 添加所需的头文件。例如,在
system/core/healthd/healthd.cpp
中运行以下命令:#include <android/hardware/configstore/1.0/IChargerConfigs.h> #include <configstore/Utils.h>
- 使用
android.hardware.configstore-utils
中相应的模板函数访问 ConfigStore 项。例如,在system/core/healthd/healthd.cpp
中运行以下命令:
在本例中,系统检索了 ConfigStore 项using namespace android::hardware::configstore; using namespace android::hardware::configstore::V1_0; static int64_t disableInitBlank = getBool< IChargerConfigs, &IChargerConfigs::disableInitBlank>(false);
disableInitBlank
并将其存储到某个变量中(在需要多次访问该变量时,这样做非常有帮助)。从 ConfigStore 检索的值会缓存到实例化的模板函数内,这样系统就可以快速从缓存值中检索到该值,而无需与 ConfigStore 服务通信以便稍后调用实例化的模板函数。 - 在
Android.mk
或Android.bp
中添加对 ConfigStore 和configstore-utils
库的依赖关系。例如,在system/core/healthd/Android.mk
中运行以下命令:LOCAL_SHARED_LIBRARIES := \ android.hardware.configstore@1.0 \ android.hardware.configstore-utils \ ... (other libraries) \