HIDL MemoryBlock

The HIDL MemoryBlock is an abstract layer built on hidl_memory, HIDL @1.0::IAllocator, and HIDL @1.0::IMapper. It is designed for HIDL services that have multiple memory blocks to share a single memory heap.

Performance improvements

Using MemoryBlock in apps can significantly reduce the number of mmap/munmap and user space segmentation faults, thus improving performance. For example:

  • Using per hidl_memory for each buffer allocation averages 238 us/1 allocation.
  • Using MemoryBlock and sharing a single hidl_memory averages 2.82 us/1 allocation.

Architecture

The HIDL MemoryBlock architecture includes HIDL services with multiple memory blocks sharing a single memory heap:

HIDL MemoryBlock

Figure 1. HIDL MemoryBlock architecture

Normal usage

This section provides an example of using MemoryBlock by first declaring the HAL then implementing the HAL.

Declare the HAL

For the following example IFoo HAL:

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

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

The Android.bp is as follows:

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

Implement the HAL

To implement the example HAL:

  1. Get the hidl_memory (for details, refer to 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. Make a HidlMemoryDealer instance with the acquired hidl_memory:

    #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. Allocate MemoryBlock, which is a struct defined with HIDL.

    Example MemoryBlock:

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

    Example using the MemoryDealer to allocate a 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. Deallocate MemoryBlock:

    Return<void> Foo::giveBack(const MemoryBlock& block) {
        memory_dealer->deallocate(block.offset);
    ...
    
  5. Manipulate the data:

    #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. Config Android.bp:

    shared_libs: [
            "android.hidl.memory@1.0",
    
            "android.hidl.memory.block@1.0"
    
            "android.hidl.memory.token@1.0",
            "libhidlbase",
            "libhidlmemory",
    
  7. Review the flow to determine if you need to lockMemory.

    Normally, MemoryBlock uses reference count to maintain the shared hidl_memory which is mmap()-ed the first time one of its 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` mapped throughout the lock lifecycle. Example:

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

Extended usage

This section provides details about the extended usage of MemoryBlock.

Use reference count to manage MemoryBlock

In most situations, the most efficient way to use MemoryBlock is to explicitly allocate/deallocate. However, in complicated apps using reference count for garbage collection might be a better idea. To have reference count on MemoryBlock, you can bind MemoryBlock with a binder object, which helps to count the references and deallocate the MemoryBlock when the count decreases to zero.

Declare the HAL

When declaring the HAL, describe a HIDL struct that contains a MemoryBlock instance and an IBase:

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

struct MemoryBlockAllocation {
    MemoryBlock block;
    IBase refcnt;
};

Use MemoryBlockAllocation to replace MemoryBlock and remove the method to give back MemoryBlock. It's deallocated by reference counting with MemoryBlockAllocation. Example:

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

Implement the HAL

Example of the service side implementation of the 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);

Example of the client side implementation of the HAL:

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

Attach and retrieve metadata

Some apps need additional data to bind with the allocated MemoryBlock. You can append and retrieve metadata using two methods:

  • If the app accesses the metadata as often as the block itself, append the metadata and pass them all in a struct. Example:

    import android.hidl.memory.block@1.0::MemoryBlock;
    
    struct MemoryBlockWithMetaData{
        MemoryBlock block;
        MetaDataStruct metaData;
    };
    
  • If the app accesses the metadata much less frequently than the block, it is more efficient to pass the metadata passively with an interface. Example:

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

    Next, bind the metadata with the MemoryBlock using MemoryDealer. Example:

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