Android 匿名共享內存C++接口分析


在上一篇Android 匿名共享內存C接口分析中介紹了Android系統的匿名共享內存C語言訪問接口,本文在前文的基礎上繼續介紹Android系統的匿名共享內存提供的C++訪問接口。在C++層通過引入Binder進程間通信機制可以實現跨進程訪問匿名共享內存。我們知道Android匿名共享內存的設計本身就是為了實現進程間共享大量數據,當源進程開辟一塊匿名共享內存並把這塊匿名共享內存映射到當前進程的虛擬地址空間從而使當前進程可以直接訪問這塊匿名共享內存后,如何讓目標進程共享訪問這塊匿名共享內存呢?這就需要利用Binder進程間通信方式將匿名共享內存的文件描述符傳輸給目標進程,目標進程才能將得到的匿名共享內存文件描述符映射到本進程地址空間中,只有這樣目標進程才能訪問源進程創建的這塊匿名共享內存。將Binder進程間通信方式與匿名共享內存相結合實現小數據量傳輸換來大數據量共享。


由於這里用到了Binder進程間通信機制,這里再次貼上Android系統的Binder通信設計框架,關於Binder通信的具體分析,請查看Binder通信模塊中的一系列文章。


MemoryHeapBase

Android使用MemoryHeapBase接口來實現進程間共享一個完整的匿名共享內存塊,通過MemoryBase接口來實現進程間共享一個匿名共享內存塊中的其中一部分,MemoryBase接口是建立在MemoryHeapBase接口的基礎上面的,都可以作為一個Binder對象來在進程間傳輸。首先介紹MemoryHeapBase如何實現一個完整匿名共享內存塊的共享,下面是MemoryHeapBase在Binder通信框架中的類關系圖:


既然Android的匿名共享內存涉及到了Binder通信機制,那就必定包括客戶端和服務端兩種實現方式。圖中黃色標識的類及MemoryHeapBase是匿名共享內存在服務進程中的實現類,而粉紅色標識的類則是匿名共享內存在Client進程中的引用類。IMemoryHeapding接口定義了匿名共享內存訪問接口;BpMemoryHeap定義了匿名共享內存在客戶進程中的業務邏輯,BpInterface,BpRefBase,BpBinder則是為業務邏輯層的BpMemoryHeap提供進程間通信支持,ProcessState和IPCThreadState是Binder進程和線程的抽象操作類,為BpBinder和Binder驅動交互提供輔助接口。黃色標識類BBinder,BnInterface是服務進程中用於支持Binder通信實現類,BnMemoryHeap用於分發關於匿名共享內存請求命令,而MemoryHeapBase實現了匿名共享內存在服務進程的所有業務邏輯。了解了整個匿名共享內存的框架設計后,接下來根據代碼具體分析Android是如何在進程間共享匿名共享內存的。在IMemoryBase類中定義了匿名共享內存的訪問接口:

 

class IMemoryHeap : public IInterface
{
public:
    DECLARE_META_INTERFACE(MemoryHeap);
	...
	//用來獲得匿名共享內存塊的打開文件描述符
    virtual int         getHeapID() const = 0;
	//用來獲得匿名共享內存塊的基地址
    virtual void*       getBase() const = 0;
	//用來獲得匿名共享內存塊的大小
    virtual size_t      getSize() const = 0;
	//用來獲得匿名共享內存塊的保護標識位
    virtual uint32_t    getFlags() const = 0;
	//用來獲得匿名共享內存塊中的偏移量
    virtual uint32_t    getOffset() const = 0;
	...
};

 

Server端

 

MemoryHeapBase類實現了IMemoryBase提供的接口函數,位於frameworks\native\libs\binder\MemoryBase.cpp,MemoryHeapBase提供了四種構造函數:

 

MemoryHeapBase::MemoryHeapBase()
    : mFD(-1), mSize(0), mBase(MAP_FAILED),
      mDevice(NULL), mNeedUnmap(false), mOffset(0)
{
}


size表示要創建的匿名共享內存的大小,flags是用來設置這塊匿名共享內存的屬性的,name是用來標識這個匿名共享內存的名字,mFD表示匿名共享內存的文件描述符,mBase標識匿名共享內存的基地址,mDevice表示匿名共享內存的設備文件。這個構造函數只是簡單初始化了各個成員變量。

 

 

MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name)
    : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
      mDevice(0), mNeedUnmap(false), mOffset(0)
{
	//獲得系統中一頁大小的內存
    const size_t pagesize = getpagesize();
	//內存頁對齊
    size = ((size + pagesize-1) & ~(pagesize-1));
	//創建一塊匿名共享內存
    int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);
    ALOGE_IF(fd<0, "error creating ashmem region: %s", strerror(errno));
    if (fd >= 0) {
		//將創建的匿名共享內存映射到當前進程地址空間中
        if (mapfd(fd, size) == NO_ERROR) {
            if (flags & READ_ONLY) {//如果地址映射成功,修改匿名共享內存的訪問屬性
                ashmem_set_prot_region(fd, PROT_READ);
            }
        }
    }
}

在以上構造函數中根據參數,利用匿名共享內存提供的C語言接口創建一塊匿名共享內存,並映射到當前進程的虛擬地址空間,參數size是指定匿名共享內存的大小,flags指定匿名共享內存的訪問屬性,name指定匿名共享內存的名稱,如果沒有指定名稱,默認命名為MemoryHeapBase。

 

 

MemoryHeapBase::MemoryHeapBase(const char* device, size_t size, uint32_t flags)
    : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
      mDevice(0), mNeedUnmap(false), mOffset(0)
{
    int open_flags = O_RDWR;
    if (flags & NO_CACHING)
        open_flags |= O_SYNC;
	//打開匿名共享內存設備文件
    int fd = open(device, open_flags);
    ALOGE_IF(fd<0, "error opening %s: %s", device, strerror(errno));
    if (fd >= 0) {
		//將指定的匿名共享內存大小按頁對齊
        const size_t pagesize = getpagesize();
        size = ((size + pagesize-1) & ~(pagesize-1));
		//將匿名共享內存映射到當前進程地址空間
        if (mapfd(fd, size) == NO_ERROR) {
            mDevice = device;
        }
    }
}

該構造函數通過指定已創建的匿名共享內存的設備文件來打開該共享內存,並映射到當前進程地址空間。device指定已經創建的匿名共享內存的設備文件路徑,size指定匿名共享內存的大小。

 

 

MemoryHeapBase::MemoryHeapBase(int fd, size_t size, uint32_t flags, uint32_t offset)
    : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
      mDevice(0), mNeedUnmap(false), mOffset(0)
{
	//指定匿名共享內存大小按頁對齊
    const size_t pagesize = getpagesize();
    size = ((size + pagesize-1) & ~(pagesize-1));
    mapfd(dup(fd), size, offset);
}


以上四種構造函數都通過mapfd函數來將匿名共享內存映射到當前進程的虛擬地址空間,以便進程可以直接訪問匿名共享內存中的內容。

 

 

status_t MemoryHeapBase::mapfd(int fd, size_t size, uint32_t offset)
{
    if (size == 0) {
        // try to figure out the size automatically
#ifdef HAVE_ANDROID_OS
        // first try the PMEM ioctl
        pmem_region reg;
        int err = ioctl(fd, PMEM_GET_TOTAL_SIZE, ®);
        if (err == 0)
            size = reg.len;
#endif
        if (size == 0) { // try fstat
            struct stat sb;
            if (fstat(fd, &sb) == 0)
                size = sb.st_size;
        }
        // if it didn't work, let mmap() fail.
    }

    if ((mFlags & DONT_MAP_LOCALLY) == 0) {
		//通過mmap系統調用進入內核空間的匿名共享內存驅動,並調用ashmem_map函數將匿名共享內存映射到當前進程
        void* base = (uint8_t*)mmap(0, size,PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);
        if (base == MAP_FAILED) {
            ALOGE("mmap(fd=%d, size=%u) failed (%s)",fd, uint32_t(size), strerror(errno));
            close(fd);
            return -errno;
        }
        mBase = base;
        mNeedUnmap = true;
    } else  {
        mBase = 0; // not MAP_FAILED
        mNeedUnmap = false;
    }
    mFD = fd;
    mSize = size;
    mOffset = offset;
    return NO_ERROR;
}

mmap函數的第一個參數0表示由內核來決定這個匿名共享內存文件在進程地址空間的起始位置,第二個參數size表示要映射的匿名共享內文件的大小,第三個參數PROT_READ|PROT_WRITE表示這個匿名共享內存是可讀寫的,第四個參數fd指定要映射的匿名共享內存的文件描述符,第五個參數offset表示要從這個文件的哪個偏移位置開始映射。調用了這個函數之后,最后會進入到內核空間的ashmem驅動程序模塊中去執行ashmem_map函數,調用mmap函數返回之后,就得這塊匿名共享內存在本進程地址空間中的起始訪問地址了,將這個地址保存在成員變量mBase中,最后,還將這個匿名共享內存的文件描述符和以及大小分別保存在成員變量mFD和mSize,並提供了相應接口函數來訪問這些變量值。通過構造MemoryHeapBase對象就可以創建一塊匿名共享內存,或者映射一塊已經創建的匿名共享內存到當前進程的地址空間。

 

 

int MemoryHeapBase::getHeapID() const {
    return mFD;
}

void* MemoryHeapBase::getBase() const {
    return mBase;
}

size_t MemoryHeapBase::getSize() const {
    return mSize;
}

uint32_t MemoryHeapBase::getFlags() const {
    return mFlags;
}

const char* MemoryHeapBase::getDevice() const {
    return mDevice;
}

uint32_t MemoryHeapBase::getOffset() const {
    return mOffset;
}

Client端

前面介紹了匿名共享內存服務端的實現,那客戶端如果通過Binder通信中的RPC方式訪問匿名共享內存的服務端,從而得到相應的服務呢?Android提供了BpMemoryHeap類來請求服務,位於frameworks\native\libs\binder\IMemory.cpp
BpMemoryHeap::BpMemoryHeap(const sp<IBinder>& impl)
    : BpInterface<IMemoryHeap>(impl),
        mHeapId(-1), mBase(MAP_FAILED), mSize(0), mFlags(0), mOffset(0), mRealHeap(false)
{
}
在BpMemoryHeap的構造函數里只是初始化一些成員變量,BpMemoryHeap提供了和服務端MemoryHeapBase相對應的服務函數:
int BpMemoryHeap::getHeapID() const {
    assertMapped();
    return mHeapId;
}

void* BpMemoryHeap::getBase() const {
    assertMapped();
    return mBase;
}

size_t BpMemoryHeap::getSize() const {
    assertMapped();
    return mSize;
}

uint32_t BpMemoryHeap::getFlags() const {
    assertMapped();
    return mFlags;
}

uint32_t BpMemoryHeap::getOffset() const {
    assertMapped();
    return mOffset;
}
通過BpMemoryHeap代理對象訪問匿名共享內存前都會調用函數assertMapped()來判斷是否已經向服務獲取到匿名共享內存的信息,如果沒有就向服務端發起請求:
void BpMemoryHeap::assertMapped() const
{
    if (mHeapId == -1) {//如果還沒有請求服務創建匿名共享內存
		//將當前BpMemoryHeap對象轉換為IBinder對象
        sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder());
		//從成員變量gHeapCache中查找對應的BpMemoryHeap對象
        sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get()));
		//向服務端請求獲取匿名共享內存信息
        heap->assertReallyMapped();
		//判斷該匿名共享內存是否映射成功
        if (heap->mBase != MAP_FAILED) {
            Mutex::Autolock _l(mLock);
			//保存服務端返回回來的匿名共享內存信息
            if (mHeapId == -1) {
                mBase   = heap->mBase;
                mSize   = heap->mSize;
                mOffset = heap->mOffset;
                android_atomic_write( dup( heap->mHeapId ), &mHeapId );
            }
        } else {
            // something went wrong
            free_heap(binder);
        }
    }
}
mHeapId等於-1,表示匿名共享內存還為准備就緒,因此請求服務端MemoryHeapBase創建匿名共享內存,否則該函數不作任何處理。只有第一次使用匿名共享時才會請求服務端創建匿名共享內存。由於在客戶端進程中使用同一個BpBinder代理對象可以創建多個與匿名共享內存業務相關的BpMemoryHeap對象,因此定義了類型為HeapCache的全局變量gHeapCache用來保存創建的所有BpMemoryHeap對象,assertMapped函數首先將當前BpMemoryHeap對象強制轉換為IBinder類型對象,然后調用find_heap()函數從全局變量gHeapCache中查找出對應的BpMemoryHeap對象,並調用assertReallyMapped()函數向服務進程的BnemoryHeap請求創建匿名共享內存。
void BpMemoryHeap::assertReallyMapped() const
{
    if (mHeapId == -1) {//再次判斷是否已經請求創建過匿名共享內存
        Parcel data, reply;
        data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor());
		//向服務端BnMemoryHeap發起請求
        status_t err = remote()->transact(HEAP_ID, data, &reply);
        int parcel_fd = reply.readFileDescriptor();
        ssize_t size = reply.readInt32();
        uint32_t flags = reply.readInt32();
        uint32_t offset = reply.readInt32();
		//保存服務進程創建的匿名共享內存的文件描述符
        int fd = dup( parcel_fd );
        int access = PROT_READ;
        if (!(flags & READ_ONLY)) {
            access |= PROT_WRITE;
        }
        Mutex::Autolock _l(mLock);
        if (mHeapId == -1) {
			//將服務進程創建的匿名共享內存映射到當前客戶進程的地址空間中
            mRealHeap = true;
            mBase = mmap(0, size, access, MAP_SHARED, fd, offset);
            if (mBase == MAP_FAILED) {
                ALOGE("cannot map BpMemoryHeap (binder=%p), size=%ld, fd=%d (%s)",asBinder().get(), size, fd, strerror(errno));
                close(fd);
            } else {//映射成功后,將匿名共享內存信息保存到BpMemoryHeap的成員變量中,供其他接口函數訪問
                mSize = size;
                mFlags = flags;
                mOffset = offset;
                android_atomic_write(fd, &mHeapId);
            }
        }
    }
}
該函數首先通過Binder通信方式向服務進程請求創建匿名共享內存,當服務端BnMemoryHeap對象創建完匿名共享內存后,並將共享內存信息返回到客戶進程后,客戶進程通過系統調用mmap函數將匿名共享內存映射到當前進程的地址空間,這樣客戶進程就可以訪問服務進程創建的匿名共享內存了。當了解Binder通信機制,就知道BpMemoryHeap對象通過transact函數向服務端發起請求后,服務端的BnMemoryHeap的onTransact函數會被調用。
status_t BnMemoryHeap::onTransact(
        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
       case HEAP_ID: {
            CHECK_INTERFACE(IMemoryHeap, data, reply);
            reply->writeFileDescriptor(getHeapID());
            reply->writeInt32(getSize());
            reply->writeInt32(getFlags());
            reply->writeInt32(getOffset());
            return NO_ERROR;
        } break;
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}
服務端的BnMemoryHeap對象將調用服務端的匿名共享內存訪問接口得到創建的匿名共享內存信息,並返回給客戶端進程,MemoryHeapBase是BnMemoryHeap的子類,實現了服務端的匿名共享內存訪問接口。全局變量gHeapCache用來保存客戶端創建的所有BpMemoryHeap對象,它的類型為HeapCache

BpMemoryHeap通過find_heap()函數從全局變量gHeapCache中查找當前的BpMemoryHeap對象,查找過程其實是調用HeapCache類的find_heap()函數從向量mHeapCache中查找,該向量以鍵值對的形式保存了客戶端創建的所有BpMemoryHeap對象。為什么要保存所有創建的BpMemoryHeap對象呢?雖然客戶端可以創建多個不同的BpMemoryHeap對象,但他們請求的匿名共享內存在服務端確實同一塊。當第一個BpMemoryHeap對象從服務端獲取匿名共享內存的信息並在本進程中映射好這塊匿名共享內存之后,其他的BpMemoryHeap對象就可以直接使用了,不需要再映射了。下圖顯示了匿名共享內存在客戶端和服務端分別提供的訪問接口:


通過對Android匿名共享內存C++層的數據結構分析及Binder通信的服務端和客戶端分析,總結一下匿名共享內存的C++訪問方式:
1)服務端構造MemoryHeapBase對象時,創建匿名共享內存,並映射到服務進程的地址空間中,同時提供獲取該匿名共享內存的接口函數;
2)客戶端通過BpMemoryHeap對象請求服務端返回創建的匿名共享內存信息,並且將服務端創建的匿名共享內存映射到客戶進程的地址空間中,在客戶端也提供對應的接口函數來獲取匿名共享內存的信息;

MemoryBase

MemoryBase接口是建立在MemoryHeapBase接口的基礎上的,它們都可以作為一個Binder對象來在進程間進行數據共享

和MemoryHeapBase類型,首先定義了IMemory,同時定義了客戶端的BpMemory代理類,服務端的BnMemory及其子類MemoryBase,熟悉Binder進程間通信框架就應該很請求各個類之間的關系,這里不在介紹Binder通信層的相關類,而是直接介紹在通信層上面的業務邏輯層的相關類。IMemory類定義了MemoryBase類所需要實現的接口,這個類定義在frameworks/base/include/binder/IMemory.h
class IMemory : public IInterface
{
public:
    DECLARE_META_INTERFACE(Memory);
    virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const = 0;
    // helpers
    void* fastPointer(const sp<IBinder>& heap, ssize_t offset) const;
    void* pointer() const;
    size_t size() const;
    ssize_t offset() const;
};
在IMemory類中實現了大部分接口函數,只有getMemory函數是在IMemory的子類MemoryBase中實現的。成員函數getMemory用來獲取內部的MemoryHeapBase對象的IMemoryHeap接口。
void* IMemory::pointer() const {
    ssize_t offset;
    sp<IMemoryHeap> heap = getMemory(&offset);
    void* const base = heap!=0 ? heap->base() : MAP_FAILED;
    if (base == MAP_FAILED)
        return 0;
    return static_cast<char*>(base) + offset;
}
函數pointer()用來獲取內部所維護的匿名共享內存的基地址
size_t IMemory::size() const {
    size_t size;
    getMemory(NULL, &size);
    return size;
}
函數size()用來獲取內部所維護的匿名共享內存的大小
ssize_t IMemory::offset() const {
    ssize_t offset;
    getMemory(&offset);
    return offset;
}
函數offset()用來獲取內部所維護的這部分匿名共享內存在整個匿名共享內存中的偏移量
由於這三個接口函數都在IMemory類中實現了,因此無論是在Binder進程間通信的客戶端的BpMemory還是服務端的MemoryBase類中都不在實現這三個接口函數,從以上三個接口函數的實現可以看出,它們都是調用getMemory函數來獲取匿名共享內存的基地址,大小及偏移量,那getMemory函數在客戶端和服務端的實現有何區別呢,下面分別介紹客戶端BpMemory和服務端MemoryBase中getMemory函數的實現過程。

客戶端

在客戶端的BpMemory類中,通過Binder進程間通信機制向服務端MemoryBase發起請求:
frameworks\native\libs\binder\IMemory.cpp
sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const
{
    if (mHeap == 0) {//指向的匿名共享內存MemoryHeapBase為空
        Parcel data, reply;
        data.writeInterfaceToken(IMemory::getInterfaceDescriptor());
		//向服務端MemoryBase發起RPC請求
        if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) {
			//讀取服務端返回來的結果
            sp<IBinder> heap = reply.readStrongBinder();//讀取匿名共享內存MemoryHeapBase的IBinder對象
            ssize_t o = reply.readInt32();//讀取匿名共享內存中的偏移量
            size_t s = reply.readInt32();//讀取匿名共享內存的大小
			//如果服務端返回來的用於描述整塊匿名共享內存的MemoryHeapBase不為空
            if (heap != 0) {
                mHeap = interface_cast<IMemoryHeap>(heap);
                if (mHeap != 0) {//將匿名共享內存的偏移和大小保存到成員變量中
                    mOffset = o;
                    mSize = s;
                }
            }
        }
    }
	//將成員變量賦值給傳進來的參數,從而修改參數值
    if (offset) *offset = mOffset;
    if (size) *size = mSize;
    return mHeap;
}

服務端

當客戶端的BpMemory向服務端MemoryBase發起RPC請求后,服務端的BnMemory對象的onTransact函數被調用
status_t BnMemory::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
        case GET_MEMORY: {
	    //根據客戶端發送過來的接口描述進行檢查確認
            CHECK_INTERFACE(IMemory, data, reply);
            ssize_t offset;
            size_t size;
	    //調用服務端的getMemory函數獲取匿名共享內存對象MemoryHeapBase及匿名共享內存大小,偏移,並返回給客戶端
            reply->writeStrongBinder(getMemory(&offset, &size)->asBinder() );
	    //將偏移量返回給客戶端
            reply->writeInt32(offset);
	    //將匿名共享內存大小返回給客戶端
            reply->writeInt32(size);
            return NO_ERROR;
        } break;
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}
服務端的getMemory函數由BnMemory的子類MemoryBase實現
sp<IMemoryHeap> MemoryBase::getMemory(ssize_t* offset, size_t* size) const
{
    if (offset) *offset = mOffset;
    if (size)   *size = mSize;
    return mHeap;
}
這里只是將成員變量的值賦給傳進來的參數,從而修改參數值,由於參數類型為指針類型,因此形參的改變必然改變實參。那服務端MemoryBase的成員變量mSize,mOffset,mHeap是在什么時候賦值的呢?MemoryBase的構造函數如下:
MemoryBase::MemoryBase(const sp<IMemoryHeap>& heap,
        ssize_t offset, size_t size)
    : mSize(size), mOffset(offset), mHeap(heap)
{
}
從MemoryBase的構造函數可以看出,它的成員變量值都是在構造MemoryBase對象是被初始化的。參數heap指向的是一個MemoryHeapBase對象,真正的匿名共享內存就是由它來維護的,參數offset表示這個MemoryBase對象所要維護的這部分匿名共享內存在整個匿名共享內存塊中的起始位置,參數size表示這個MemoryBase對象所要維護的這部分匿名共享內存的大小。MemoryBase用於共享匿名共享內存中的部分內存,在構造MemoryBase對象是指定共享的整塊匿名共享內存為mHeap,大小為mSize,被共享的內存在整塊匿名共享內存中的偏移量為mOffset。客戶端通過Binder進程間通信方式獲取用於描述部分共享內存的MemoryBase對象信息,MemoryBase只是用來維護部分匿名共享內存,而匿名共享內存的創建依然是通過MemoryHeapBase來完成的。

 


免責聲明!

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



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