Android Configstore HAL

Android O 将整个 Android 操作系统拆分为通用分区 (system.img) 和特定于硬件的分区(vendor.img 和 odm.img)。受这种变更的影响,您必须从安装到系统分区的模块中移除条件式编译,而且此类模块现在必须在运行时确定系统配置(并根据相应配置采取不同的行为)。

1    综述

1.1  那为什么不使用系统属性?

Android考虑过使用系统属性,但发现了以下几个重大问题,例如:

值的长度受限系统属性对其值的长度具有严格限制(92 个字节)。此外,由于这些限制已作为C宏直接提供给 Android 应用,增加长度会导致出现向后兼容性问题。

无类型支持所有值本质上都是字符串,而API仅仅是将字符串解析为int或bool。其他复合数据类型(数组、结构体等)应由客户端进行编码/解码(例如,“aaa,bbb,ccc”可以解码为由三个字符串组成的数组)。

覆盖由于只读系统属性是以一次写入属性的形式实现的,因此如果供应商/原始设计制造商ODM)想要覆盖AOSP定义的只读值,则必须先于AOSP定义的只读值导入自己的只读值,而这反过来又会导致供应商定义的可重写值被 AOSP 定义的值所覆盖。

地址空间要求系统属性在每个进程中都会占用较大的地址空间。系统属性在prop_area单元中以128KB的固定大小进行分组,即使目前只访问该单元中的一个系统属性,其中的所有属性也将会分配到进程地址空间。这可能会导致对地址空间需求较高的32位设备出现问题。

Google曾尝试在不牺牲兼容性的情况下克服这些限制,但依然会担心系统属性的设计不支持访问只读配置项。最终,Google判定系统属性更适合在所有Android中实时共享一些动态更新内容,因此需要采用一个专用于访问只读配置项的新系统

1.2  ConfigStore HAL设计

基本设计很简单:

 图 1. ConfigStore HAL 设计

l  以 HIDL 描述编译标记(目前用于对框架进行条件式编译)。

l  供应商和原始设备制造商(OEM)通过实现HAL服务为编译标记提供SoC和设备特定值。

l  修改框架,以使用 HAL 服务在运行时查找配置项的值。

当前由框架引用的配置项会包含在具有版本号的HIDL软件包 (android.hardware.configstore@1.0)中。供应商和/或原始设备制造商(OEM)通过实现此软件包中的接口为配置项提供值,而框架会在需要获取配置项的值时使用接口。

1.3  安全注意事项

当前由框架引用的配置项会包含在具有版本号的HIDL软件包 (android.hardware.configstore@1.0)中。供应商和/或原始设备制造商(OEM)通过实现此软件包中的接口为配置项提供值,而框架会在需要获取配置项的值时使用接口。

2    创建HAL接口

您必须使用HIDL来描述用于对框架进行条件式编译的所有编译标记。相关编译标记必须分组并包含在单个.hal文件中。使用HIDL指定配置项具有以下优势:

l  可实施版本控制(为了添加新配置项,供应商/OEM 必须明确扩展 HAL)

l  记录详尽

l  可使用 SELinux 实现访问控制

l  可通过供应商测试套件对配置项进行全面检查(范围检查、各项内容之间的相互依赖性检查等)

l  在 C++ 和 Java 中自动生成 API

2.1  确定框架使用的编译标记

首先,请确定用于对框架进行条件式编译的编译标记,然后舍弃过时的配置以缩小编译标记集的范围。例如,下列编译标记集已确定用于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

2.2  创建 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.bpAndroid.mk文件中。 

2.3  为编译标记添加函数

对于每个编译标记,请向相应接口各添加一个新函数。例如,在 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);
};

添加函数时,请注意以下事项:

l  采用简洁的名称。请避免将 makefile 变量名称转换为函数名称,并切记 TARGET_ 和 BOARD_ 前缀不再是必需的。

l  添加注释。帮助开发者了解配置项的用途,配置项如何改变框架行为、有效值等。

函数返回类型可以是 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
}; 

2.4  单个 HAL 函数的替代函数

作为针对所有编译标记使用单个 HAL 函数的替代函数,HAL 接口还提供了 getBoolean(string key) 和 getInteger(string key) 等简单函数。实际的 key=value 对会存储在单独的文件中,而 HAL 服务会通过读取/解析这些文件来提供值。

虽然这种方法很容易定义,但它不具备 HIDL 提供的优势(强制实施版本控制、便于记录、实现访问控制),因此不推荐使用。

注意:在使用简单函数时,几乎不可能实现访问控制,因为 HAL 自身无法识别客户端。

2.5  单个接口与多个接口

面向配置项设计的 HAL 接口提供了以下两种选择:

l  单个接口;涵盖所有配置项

l  多个接口;每个接口分别涵盖一组相关配置项

单个接口更易于使用,但随着更多的配置项添加到单个文件中,单个接口可能会越来越难以维护。此外,由于访问控制不够精细,获得接口访问权限的进程可能会读取所有配置项(无法授予对部分配置项的访问权限)。此外,如果未授予访问权限,则无法读取任何配置项。

由于存在这些问题,Android 会针对一组相关配置项将多个接口与单个 HAL 接口搭配使用。例如,对surfaceflinger相关配置项使用 ISurfaceflingerConfigs,对蓝牙相关配置项使用 IBluetoothConfigs 等等。

3    实现服务

为了准备 HAL 实现,您可以先生成基本的 configstore 接口代码,然后再对其进行修改以满足自己的需求。

3.1  生成接口代码

要为接口生成样板代码,请运行 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 进行编译才能访问编译标记。

3.2  修改 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 中引用该文件。

3.3  实现函数

要填充函数以实现 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必须始终在绑定模式下运行。

3.4  注册为服务

最后,将所有接口实现注册为 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();

3.5  确保可尽早访问

为了确保框架模块可以尽早访问HAL 服务,config HAL服务应该在 hwservicemanager 准备就绪之后尽早启动。由于配置 HAL 服务不会读取外部文件,因此在启动之后预计很快就能准备就绪。

4    客户端使用情况

您可以重构经过条件式编译的代码,以便从 HAL 接口动态读取值。例如:

#ifdef TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS
//some code fragment
#endif

随后,框架代码便可以调用一个在 <configstore/Utils.h> 中定义的适当效用函数(根据其类型)。

4.1  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 来调用句柄,从而检索该值。

4.2  效用函数

<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 实现没有为配置项指定值时返回的默认值。每个函数都需要使用两个模板参数:

l  接口类名称。

l  Func. 用于获取配置项的成员函数指针。

由于配置值是只读属性且不会发生更改,因此效用函数会在内部缓存配置值。使用同一链接单元中的缓存值可以更有效地执行后续调用。

4.3  使用 configstore-utils

ConfigStore HAL 旨在向前兼容次要版本升级,这意味着当 HAL 进行升级并且某些框架代码使用新引入的项时,您仍然可以使用 /vendor 中旧的次要版本的 ConfigStore 服务。

为了实现向前兼容性,请确保在实现过程中遵循以下准则:

1)   当只有旧版服务可用时,新项使用默认值。例如:

service = V1_1::IConfig::getService(); // null if V1_0 is installed
value = DEFAULT_VALUE;
  if(service) {
    value = service->v1_1API(DEFAULT_VALUE);
  }

2)   客户端使用引入 ConfigStore 项的最早的接口。例如:

V1_1::IConfig::getService()->v1_0API(); // NOT ALLOWED

V1_0::IConfig::getService()->v1_0API(); // OK

3)   可以为旧版接口检索新版服务。在以下示例中,如果已安装版本为 v1_1,则必须为 getService() 返回 v1_1 服务:

V1_0::IConfig::getService()->v1_0API();
当configstore-utils库中的访问函数用于访问ConfigStore项时,#1由实现保证,#2由编译器错误保证。基于这些原因,我们强烈建议尽量使用configstore-utils。

5    添加 ConfigStore 类和项

您可以为现有接口类添加新的 ConfigStore 项(即接口方法)。如果您未定义接口类,则必须先添加一个新类,然后才能为该接口类添加 ConfigStore 项。本部分使用 disableInitBlank 配置项示例来演示将 healthd 添加到 IChargerConfigs 接口类的过程。

5.1  添加接口类

如果您没有为要添加的接口方法定义接口类,则必须先添加接口类,然后才能添加相关联的 ConfigStore 项。

1)   创建 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
};

2)   为 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。

3)   生成用于实现服务器代码的 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。

4)   打开 .h 和 .cpp 实现文件,并移除与函数 HIDL_FETCH_name(例如 HIDL_FETCH_IChargerConfigs)相关的代码。这是 HIDL 直通模式所需的函数,ConfigStore 不使用该模式。

5)   将实现注册为 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
}

6)   修改 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

7)   (可选)添加清单项。如果清单项不存在,则默认添加 ConfigStore 的“default”实例名称。例如,在 device/google/marlin/manifest.xml 中运行以下命令:

    <hal format="hidl">
        <name>android.hardware.configstore</name>
        ...
        <interface>
            <name>IChargerConfigs</name>
            <instance>default</instance>
        </interface>
    </hal>

8)   视需要(即,如果客户端没有向 hal_configstore 进行 hwbinder 调用的权限)添加 sepolicy 规则。例如,在 system/sepolicy/private/healthd.te 中运行以下命令:

... // other rules
binder_call(healthd, hal_configstore)

5.2  添加新的 ConfigStore 项

要添加新的 ConfigStore 项,请执行以下操作:

1)   打开 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);
};

2)   在相应的接口 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();
}

5.3  使用ConfigStore项

要使用 ConfigStore 项,请执行以下操作:

1)   添加所需的标头文件。例如,在 system/core/healthd/healthd.cpp 中运行以下命令:

#include <android/hardware/configstore/1.0/IChargerConfigs.h>
#include <configstore/Utils.h>

2)   使用 android.hardware.configstore-utils 中相应的模板函数访问 ConfigStore 项。例如,在 system/core/healthd/healthd.cpp 中运行以下命令:

using namespace android::hardware::configstore;
using namespace android::hardware::configstore::V1_0;

static int64_t disableInitBlank = getBool<
        IChargerConfigs,
        &IChargerConfigs::disableInitBlank>(false);

在本例中,系统检索了 ConfigStore 项 disableInitBlank 并将其存储到某个变量中(在需要多次访问该变量时,这样做非常有帮助)。从 ConfigStore 检索的值会缓存到实例化的模板函数内,这样系统就可以快速从缓存值中检索到该值,而无需与 ConfigStore 服务通信以便稍后调用实例化的模板函数。

3)   在 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) 
原文地址:https://www.cnblogs.com/tsts/p/9241496.html