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) \

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM