我們知道,傳統(tǒng)的IPC方式傳遞大塊內(nèi)存時,一般使用共享內(nèi)存的方式。在Android Binder IPC中,有著自己獨特的跨進(jìn)程傳遞方式。它也同樣,避免了內(nèi)存拷貝的方式,可以讓內(nèi)存基址和偏移在進(jìn)程間不斷而且方便的傳遞。Android傳遞大內(nèi)存塊的方式稍有不同,這些大內(nèi)存塊往往位于特定的設(shè)備文件中,如pmem和ashmem(匿名共享內(nèi)存:Anonymous Shared Memory),當(dāng)然,也支持共享使用其它特定設(shè)備的緩沖區(qū)。pmem和ashmem就是Android在Linux內(nèi)核中虛擬出的兩個設(shè)備,前者是物理連續(xù)的大塊內(nèi)存區(qū)域,通常大小在8MB~16MB之間,不同的硬件平臺或產(chǎn)品對其定義不同,它也位于系統(tǒng)的RAM中,不由內(nèi)核的mm管理,而是劃歸給虛擬的設(shè)備pmem來管理,用戶空間通過設(shè)備文件與其交互,一般用于Camera;后者原理與前者相同,也是通過設(shè)備文件使用,但它可能物理上不連續(xù),大小也通常小些,作為普通的共享之用。在使用之前,必須調(diào)用mmap將設(shè)備緩沖區(qū)映射到系統(tǒng)的進(jìn)程內(nèi)存空間。
具體實現(xiàn)則是通過libbinder.so庫中的IMemory、IMemoryHeap、MemoryHeapBase、MemoryHeapPmem等類實現(xiàn)的。將某個設(shè)備(如pmem或ashmem)管理的內(nèi)存映射(mmap)到系統(tǒng)的進(jìn)程內(nèi)存空間,這塊內(nèi)存稱為內(nèi)存堆(MemeoryHeap) IMemoryHeap定義了獲取內(nèi)存堆信息的接口,這個內(nèi)存堆的信息有:HeapID(亦即設(shè)備文件描述符)、經(jīng)過mmap映射到進(jìn)程空間的基址、內(nèi)存堆大小以及訪問標(biāo)志??梢允褂孟旅嫠膫€API來獲得它們的值: virtual int getHeapID() const = 0; virtual void* getBase() const = 0; virtual size_t getSize() const = 0; virtual uint32_t getFlags() const = 0; Client側(cè)調(diào)用接口類實際上調(diào)用到子類BpMemoryHeap對象。經(jīng)Binder跨進(jìn)程后,請求由server側(cè)的子類MemoryHeapBase或MemoryHeapPmem完成。類的繼承關(guān)系圖如下:
IMemoryHeap接口中定義的四個純虛函數(shù)由子類BpMemoryHeap中重載實現(xiàn),它們均是直接返回對應(yīng)的成員的值(見圖中的BpMemeoryHeap,上面有四個私有成員變量,下面有四個共有成員函數(shù))。不過在返回之前,都調(diào)用了映射函數(shù)assertMapped確保已經(jīng)映射:
void BpMemoryHeap::assertMapped() const { if (mHeapId == -1) {//在還沒有映射的情況下才執(zhí)行下面代碼 sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder());//asBinder實際調(diào)用的是remote(),也就是得到BpBinder對象 sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get()));//通過binder在cache緩存中找到對應(yīng)的BpMemoryHeap對象,然后調(diào)用下面一行進(jìn)行真正的映射 heap->assertReallyMapped();//進(jìn)行真正的映射 if (heap->mBase != MAP_FAILED) {//沒有出錯則繼續(xù) Mutex::Autolock _l(mLock); if (mHeapId == -1) {//還沒有更新base,size和heapID等信息,則更新它們 mBase = heap->mBase; mSize = heap->mSize; android_atomic_write( dup( heap->mHeapId ), &mHeapId ); } } else { // something went wrong free_heap(binder); } } } 見代碼注釋,只有在還沒有映射的情況下才進(jìn)行映射,判斷的依據(jù)是文件描述符是否為-1。首先獲取對應(yīng)的BpBinder對象,然后在全局的cache中查詢BpMemoryHeap對象,接著調(diào)用BpMemoryHeap對象的assertReallyMapped進(jìn)行真正的映射。 全局緩存cache(即類HeapCache)中維護(hù)著一個鍵值表,它是從Bpinder對象的弱指針到heap_info_t結(jié)構(gòu)體的映射: struct heap_info_t { sp<IMemoryHeap> heap; int32_t count; //使用計數(shù) };
KeyedVector< wp<IBinder>, heap_info_t > mHeapCache;
HeapCache的find_heap函數(shù)首先查詢其鍵值表,若找到則直接返回BpMemoryHeap對象強(qiáng)指針,并將使用計數(shù)加1;若沒找到,則創(chuàng)建一個BpMemoryHeap對象,并將其添加到表項中: heap_info_t info; info.heap = interface_cast<IMemoryHeap>(binder); //以binder創(chuàng)建一個BpMemoryHeap對象 info.count = 1; //LOGD(“adding binder=%p, heap=%p, count=%d”, // binder.get(), info.heap.get(), info.count); mHeapCache.add(binder, info); return info.heap;
也就是說,在使用IMemoryHeap也就是創(chuàng)建其子類對象時,不是直接使用interface_cast<IMemoryHeap>(binder)進(jìn)行創(chuàng)建,而是首先在cache中查詢;若沒有查到,才進(jìn)行創(chuàng)建。也就是確保內(nèi)存堆代理BpMemoryHeap對象在一個進(jìn)程中的唯一性。當(dāng)多次使用到某個內(nèi)存堆代理時,有使用計數(shù)維護(hù)引用次數(shù)。這樣做的目的是確保只對設(shè)備文件進(jìn)行一次mmap映射。從assertMapped調(diào)用assertReallyMapped就可以看出來: void BpMemoryHeap::assertReallyMapped() const { if (mHeapId == -1) { Parcel data, reply; data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor()); status_t err = remote()->transact(HEAP_ID, data, &reply); int parcel_fd = reply.readFileDescriptor();//從對方獲取設(shè)備文件描述符,通常文件描述符不能跨進(jìn)程,但Android的Binder IPC在Linux內(nèi)核中做了特殊處理,使它們共享內(nèi)核中的file節(jié)點,因此可以跨進(jìn)程。 ssize_t size = reply.readInt32();//得到設(shè)備內(nèi)存大小 uint32_t flags = reply.readInt32();//訪問標(biāo)志
LOGE_IF(err, “binder=%p transaction failed fd=%d, size=%ld, err=%d (%s)”, asBinder().get(), parcel_fd, size, err, strerror(-err));
int fd = dup( parcel_fd ); //復(fù)制一個文件描述符 LOGE_IF(fd==-1, “cannot dup fd=%d, size=%ld, err=%d (%s)”, parcel_fd, size, err, strerror(errno));
int access = PROT_READ; if (!(flags & READ_ONLY)) { access |= PROT_WRITE; }//若沒有只寫明確規(guī)定是只讀,則加上寫標(biāo)志
Mutex::Autolock _l(mLock); if (mHeapId == -1) {//再次確保還沒有映射 mRealHeap = true; mBase = mmap(0, size, access, MAP_SHARED, fd, 0);//映射到內(nèi)存空間 if (mBase == MAP_FAILED) {//出錯,則給出log信息 LOGE(“cannot map BpMemoryHeap (binder=%p), size=%ld, fd=%d (%s)”, asBinder().get(), size, fd, strerror(errno)); close(fd); } else {//成功,更新其余三個成員信息 mSize = size; mFlags = flags; android_atomic_write(fd, &mHeapId); } } } } 可見,它從對方得到相關(guān)信息后,在本進(jìn)程空間重新進(jìn)行mmap映射。
在server側(cè),類MemoryHeapBase負(fù)責(zé)處理來自Client側(cè)的請求,它有多個構(gòu)造函數(shù),當(dāng)沒有指定設(shè)備文件名稱或設(shè)備文件描述符時,它就默認(rèn)使用ashmem上的內(nèi)存,否則使用指定的設(shè)備文件的內(nèi)存。在這些構(gòu)造函數(shù)中,首先檢查并確保size是頁對齊的,然后調(diào)用打開設(shè)備文件(若還沒有文件描述符fd的話),再調(diào)用mapfd成員函數(shù)對設(shè)備文件進(jìn)行mmap映射。 若使用的是pmem,則由MemoryHeapBase的子類MemoryHeapPmem來處理。當(dāng)然,平臺開發(fā)商也可以編寫其它子類,來實現(xiàn)對其它內(nèi)存設(shè)備的支持。 在libbinder庫中,還有類MemoryDealer這個類用來進(jìn)行內(nèi)存分配管理,它包含一個內(nèi)存分配器SimpleBestFitAllocator,所分配的內(nèi)存塊為Allocation(繼承自MemoryBase),但是當(dāng)前還沒有使用它們。
一個內(nèi)存堆上,往往可以分為多個區(qū)域以供調(diào)用者使用,如Camera預(yù)覽中的圖像的一楨。IMemory就代表中內(nèi)存堆中的這塊內(nèi)存區(qū)域??梢杂扇齻€變量來確定它:在哪個MemoryHeap、在堆中的偏移量offset和大小size。我們可以通過getMemory函數(shù)獲得該內(nèi)存區(qū)域的這三樣信息: sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const 偏移量offset和大小size通過修改實參傳回,而MemoryHeap則通過函數(shù)返回。為方便起見,IMemory還提供了三個方便使用的輔助函數(shù):通過fastPointer或pointer直接得到指向內(nèi)存塊的指針,通過size函數(shù)可以得到內(nèi)存塊大小。
不管是內(nèi)存堆MemoryHeapBase還是其子類,以及其里面的內(nèi)存區(qū)域,均由使用者創(chuàng)建。如如CameraService依賴于Camera硬件來實現(xiàn)各項功能,這就是平臺廠商實現(xiàn)的CameraHardeInterface的子類(如有的平臺使用的是V4L2,對應(yīng)的子類是CameraHardwareV4L2)。在Camera硬件實現(xiàn)中,一般都需要創(chuàng)建MemoryHeapBase和MemoryBase對象,即server側(cè)對象。Camera的使用者(如應(yīng)用程序)在Client側(cè)通過調(diào)用,將得到接口對象強(qiáng)指針,實際指向前述BpXXX(即IMemoryHeap和IMemory的子類:BpMemoryHeap和BpMemory)對象。Client側(cè)在在自己的進(jìn)程里進(jìn)行內(nèi)存映射后,通過對應(yīng)接口API就可以得到heapID,base基址和偏移量offset,于是就可以對數(shù)據(jù)(如預(yù)覽、拍照和錄像的數(shù)據(jù))進(jìn)行自己的處理了。因此,在server和client側(cè),它們基址多半不同,但都映射到同一設(shè)備的內(nèi)存上,如pmem中的一塊內(nèi)存。這樣,就實現(xiàn)了大塊內(nèi)存的共享。也就是說,跨進(jìn)程傳遞的過程,實際是傳遞設(shè)備文件描述符、基址和偏移量等信息的過程。
本文鏈接地址: http://www./?p=902 |
|