HIDL Memory Block

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_memory を使用して HidlMemoryDealer を作成します。

    #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 のいずれかがマッピングされたときに mmap() でマップされ、どこからも参照されていないときに munmap() でマップ解除されます。常に hidl_memory がマッピングされるようにするには、lockMemory を使用します。これは、ロックのライフサイクル全体を通じて対応する hidl_memory がマッピングされた状態を維持する RAII スタイルのオブジェクトです。例:

    #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;
    };
    

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

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