HIDL MemoryBlock

HIDL MemoryBlock は、hidl_memoryHIDL @1.0::IAllocatorHIDL @1.0::IMapper 上に構築された抽象レイヤです。複数のメモリブロックが 1 つのメモリヒープを共有する HIDL サービス用に設計されています。

パフォーマンスの改善

アプリで MemoryBlock を使用すると、mmap / munmap とユーザー空間セグメンテーション違反の数を大幅に削減して、パフォーマンスを改善できます。次に例を示します。

  • バッファ割り当てごとに hidl_memory を使用すると、平均して 238 us/1 割り当てになります。
  • MemoryBlock を使用して 1 つの hidl_memory を共有すると、平均して 2.82 us/1 割り当てになります。

アーキテクチャ

HIDL MemoryBlock アーキテクチャには、複数のメモリブロックが 1 つのメモリヒープを共有する HIDL サービスが含まれています。

HIDL MemoryBlock

図 1: HIDL MemoryBlock アーキテクチャ

通常の使用方法

このセクションでは、最初に HAL を宣言し、次に HAL を実装して、MemoryBlock を使用する例を示します。

HAL の宣言

IFoo HAL の例を次に示します。

import android.hidl.memory.block@1.0::MemoryBlock;

interface IFoo {
    getSome() generates(MemoryBlock block);
    giveBack(MemoryBlock block);
};

Android.bp は次のようになります。

hidl_interface {
    ...
    srcs: [
        "IFoo.hal",
    ],
    interfaces: [
        "android.hidl.memory.block@1.0",
        ...
};

HAL の実装

上の例の HAL を実装するには、以下を実行します。

  1. hidl_memory を取得します(詳細については、HIDL C++ をご覧ください)。

    #include <android/hidl/allocator/1.0/IAllocator.h>
    
    using ::android::hidl::allocator::V1_0::IAllocator;
    using ::android::hardware::hidl_memory;
    ...
      sp<IAllocator> allocator = IAllocator::getService("ashmem");
      allocator->allocate(2048, [&](bool success, const hidl_memory& mem)
      {
            if (!success) { /* error */ }
            // you can now use the hidl_memory object 'mem' or pass it
      }));
    
  2. 取得した hidl_memoryHidlMemoryDealer インスタンスを作成します。

    #include <hidlmemory/HidlMemoryDealer.h>
    
    using ::android::hardware::HidlMemoryDealer
    /* The mem argument is acquired in the Step1, returned by the ashmemAllocator->allocate */
    sp<HidlMemoryDealer> memory_dealer = HidlMemoryDealer::getInstance(mem);
    
  3. MemoryBlock を割り当てます。これは HIDL で定義された構造体です。

    MemoryBlock の例:

    struct MemoryBlock {
    IMemoryToken token;
    uint64_t size;
    uint64_t offset;
    };
    

    MemoryDealer を使用して MemoryBlock を割り当てる例:

    #include <android/hidl/memory/block/1.0/types.h>
    
    using ::android::hidl::memory::block::V1_0::MemoryBlock;
    
    Return<void> Foo::getSome(getSome_cb _hidl_cb) {
        MemoryBlock block = memory_dealer->allocate(1024);
        if(HidlMemoryDealer::isOk(block)){
            _hidl_cb(block);
        ...
    
  4. MemoryBlock の割り当てを解除します。

    Return<void> Foo::giveBack(const MemoryBlock& block) {
        memory_dealer->deallocate(block.offset);
    ...
    
  5. データの操作:

    #include <hidlmemory/mapping.h>
    #include <android/hidl/memory/1.0/IMemory.h>
    
    using ::android::hidl::memory::V1_0::IMemory;
    
    sp<IMemory> memory = mapMemory(block);
    uint8_t* data =
    
    static_cast<uint8_t*>(static_cast<void*>(memory->getPointer()));
    
  6. Android.bp を構成します。

    shared_libs: [
            "android.hidl.memory@1.0",
    
            "android.hidl.memory.block@1.0"
    
            "android.hidl.memory.token@1.0",
            "libhidlbase",
            "libhidlmemory",
    
  7. フローを確認して lockMemory が必要かどうかを判断します。

    通常、MemoryBlock は参照カウントを使用して、共有 hidl_memory を管理します。これは、MemoryBlock instances is mapped and ismunmap()-ed when nothing refers to it. To keephidl_memoryalways mapped, you can uselockMemory, a RAII style object that keeps the correspondinghidl_memory` のいずれかがロックのライフサイクルを通じて初めてマッピングされたときに mmap() されます。次に例を示します。

    #include <hidlmemory/mapping.h>
    
    sp<RefBase> lockMemory(const sp<IMemoryToken> key);
    

拡張された使用方法

このセクションでは、MemoryBlock の拡張された使用方法の詳細について説明します。

参照カウントを使用した MemoryBlock の管理

ほとんどの場合、MemoryBlock を使用する最も効率的な方法は、明示的に割り当てと割り当て解除を行うことです。しかし、複雑なアプリでは、ガベージ コレクションに参照カウントを使用したほうが良い場合があります。MemoryBlock で参照カウントを使用するには、MemoryBlock をバインダ オブジェクトにバインドします。これにより、参照数がカウントされ、カウントがゼロになると MemoryBlock の割り当てが解除されます。

HAL の宣言

HAL を宣言する場合は、MemoryBlock インスタンスと IBase を含む HIDL 構造体を記述します。

import android.hidl.memory.block@1.0::MemoryBlock;

struct MemoryBlockAllocation {
    MemoryBlock block;
    IBase refcnt;
};

MemoryBlockAllocation を使用して MemoryBlock を置き換え、メソッドを削除して MemoryBlock を戻します。MemoryBlockAllocation を使用した参照カウントによって割り当てが解除されます。次に例を示します。

interface IFoo {
    allocateSome() generates(MemoryBlockAllocation allocation);
};

HAL の実装

HAL のサービス側の実装例:

class MemoryBlockRefCnt: public virtual IBase {
   MemoryBlockRefCnt(uint64_t offset, sp<MemoryDealer> dealer)
     : mOffset(offset), mDealer(dealer) {}
   ~MemoryBlockRefCnt() {
       mDealer->deallocate(mOffset);
   }
 private:
   uint64_t mOffset;
   sp<MemoryDealer> mDealer;
};

Return<void> Foo::allocateSome(allocateSome_cb _hidl_cb) {
    MemoryBlockAllocation allocation;
    allocation.block = memory_dealer->allocate(1024);
    if(HidlMemoryDealer::isOk(block)){
        allocation.refcnt= new MemoryBlockRefCnt(...);
        _hidl_cb(allocation);

HAL のクライアント側の実装例:

ifoo->allocateSome([&](const MemoryBlockAllocation& allocation){
    ...
);

メタデータの添付と取得

アプリによっては、割り当てられた MemoryBlock にバインドする追加のデータが必要です。メタデータは、次の 2 つの方法で追加または取得できます。

  • アプリがブロック自体と同じ頻度でメタデータにアクセスする場合は、メタデータを追加してすべて構造体に渡します。次に例を示します。

    import android.hidl.memory.block@1.0::MemoryBlock;
    
    struct MemoryBlockWithMetaData{
        MemoryBlock block;
        MetaDataStruct metaData;
    };
    
  • アプリがブロックほど頻繁にメタデータにアクセスしない場合は、インターフェースでメタデータを受動的に渡すほうが効率的です。次に例を示します。

    import android.hidl.memory.block@1.0::MemoryBlock;
    
    struct MemoryBlockWithMetaData{
        MemoryBlock block;
        IMetaData metaData;
    };
    

    次に、MemoryDealer を使用して MemoryBlock にメタデータをバインドします。次に例を示します。

    MemoryBlockWithMetaData memory_block;
    memory_block.block = dealer->allocate(size);
    if(HidlMemoryDealer::isOk(block)){
        memory_block.metaData = new MetaData(...);