<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                原文出處——>[Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析](http://blog.csdn.net/luoshengyang/article/details/6939890) 在Android系統中,針對移動設備內存空間有限的特點,提供了一種在進程間共享數據的機制:匿名共享內存,它能夠輔助內存管理系統來有效地管理內存,它的實現原理我們在前面已經分析過了。為了方便使用匿名共享內存機制,系統還提供了Java調用接口(MemoryFile)和C++調用接口(MemoryHeapBase、MemoryBase),Java接口在前面也已經分析過了,本文中將繼續分析它的C++接口。 在前面一篇文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析中,我們分析了匿名共享內存驅動程序Ashmem的實現,重點介紹了它是如何輔助內存管理系統來有效地管理內存的,簡單來說,它就是給使用者提供鎖機制來輔助管理內存,當我們申請了一大塊匿名共享內存時,中間過程有一部分不需要使用時,我們就可以將這一部分內存塊解鎖,這樣內存管理系統就可以把它回收回去了。接著又在前面一篇文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析中,我們分析了匿名共享內存是如何通過Binder進程間通信機制來實現在進程間共享的,簡單來說,就是每一個匿名共享內存塊都是一個文件,當我們需要在進程間共享時,就把這個文件的打開描述符通過Binder進程間通信機制傳遞給另一外進程,在傳遞的過程中,Binder驅動程序就通過這個復制這個打開文件描述符到目標進程中去,從而實現數據共享。在文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃中,我們介紹了如何在Android應用程序中使用匿名共享內存,主要是通過應用程序框架層提供的MemoryFile接口來使用的,而MemoryFile接口是通過JNI方法調用到系統運行時庫層中的匿名共享內存C接口,最終通過這些C接口來使用內核空間中的匿名共享內存驅動模塊。為了方便開發者靈活地使用匿名共享內存,Android系統在應用程序框架層中還提供了使用匿名共享內存的C++接口,例如,Android應用程序四大組件之一Content Provider,它在應用程序間共享數據時,就是通過匿名共享內存機制來實現,但是它并不是通過MemoryFile接口來使用,而是通過調用C++接口中的MemoryBase類和MemoryHeapBase類來使用。在接下來的內容中,我們就詳細分析MemoryHeapBase類和MemoryBase類的實現,以及它們是如何實現在進程間共享數據的。 如果我們想在進程間共享一個完整的匿名共享內存塊,可以通過使用MemoryHeapBase接口來實現,如果我們只想在進程間共享一個匿名共享內存塊中的其中一部分時,就可以通過MemoryBase接口來實現。MemoryBase接口是建立在MemoryHeapBase接口的基礎上面的,它們都可以作為一個Binder對象來在進程間傳輸,因此,希望讀者在繼續閱讀本文之前,對Android系統的Binder進程間通信機制有一定的了解,具體可以參考前面一篇文章Android進程間通信(IPC)機制Binder簡要介紹和學習計劃。下面我們就首先分析MemoryHeapBase接口的實現,然后再分析MemoryBase接口的實現,最后,通過一個實例來說明它們是如何使用的。 1. MemoryHeapBase 前面說到,MemoryHeapBase類的對象可以作為Binder對象在進程間傳輸,作為一個Binder對象,就有Server端對象和Client端引用的概念,其中,Server端對象必須要實現一個BnInterface接口,而Client端引用必須要實現一個BpInterface接口。下面我們就先看一下MemoryHeapBase在Server端實現的類圖: ![](http://hi.csdn.net/attachment/201111/6/0_1320553904cHii.gif) 這個類圖中的類可以劃分為兩部分,一部分是和業務相關的,即跟匿名共享內存操作相關的類,包括MemoryHeapBase、IMemoryBase和RefBase三個類,另一部分是和Binder機制相關的,包括IInterface、BnInterface、BnMemoryHeap、IBinder、BBinder、ProcessState和IPCThreadState七個類。 我們先來看跟匿名共享內存業務相關的這部分類的邏輯關系。IMemoryBase定義了匿名共享內操作的接口,而MemoryHeapBase是作為Binder機制中的Server角色的,因此,它需要實現IMemoryBase接口,此外,MemoryHeapBase還繼承了RefBase類。從前面一篇文章Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析中,我們知道,繼承了RefBase類的子類,它們的對象都可以結合Android系統的智能指針來使用,因此,我們在實例化MemoryHeapBase類時,可以通過智能指針來管理它們的生命周期。 再來看和Binder機制相關的這部分類的邏輯關系。從Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析這篇文章中,我們知道,所有的Binder對象都必須實現IInterface接口,無論是Server端實體對象,還是Client端引用對象,通過這個接口的asBinder成員函數我們可以獲得Binder對象的IBinder接口,然后通過Binder驅動程序把它傳輸給另外一個進程。當一個類的對象作為Server端的實體對象時,它還必須實現一個模板類BnInterface,這里負責實例化模板類BnInterface的類便是BnMemoryHeap類了,它里面有一個重要的成員函數onTransact,當Client端引用請求Server端對象執行命令時,Binder系統就會調用BnMemoryHeap類的onTransact成員函數來執行具體的命令。當一個類的對象作為Server端的實體對象時,它還要繼承于BBinder類,這是一個實現了IBinder接口的類,它里面有一個重要的成員函數transact,當我們從Server端線程中接收到Client端的請求時,就會調用注冊在這個線程中的BBinder對象的transact函數來處理這個請求,而這個transact函數會將這些Client端請求轉發給BnMemoryHeap類的onTransact成員函數來處理。最后,ProcessState和IPCThreadState兩個類是負責和Binder驅動程序打交道的,其中,ProcessState負責打開Binder設備文件/dev/binder,打開了這個Binder設備文件后,就會得到一個打開設備文件描述符,而IPCThreadState就是通過這個設備文件描述符來和Binder驅動程序進行交互的,例如它通過一個for循環來不斷地等待Binder驅動程序通知它有新的Client端請求到來了,一旦有新的Client端請求到來,它就會調用相應的BBinder對象的transact函數來處理。 本文我們主要是要關注和匿名共享內存業務相關的這部分類,即IMemoryBase和MemoryHeapBase類的實現,和Binder機制相關的這部分類的實現,可以參考Android進程間通信(IPC)機制Binder簡要介紹和學習計劃一文。 IMemoryBase類主要定義了幾個重要的操作匿名共享內存的方法,它定義在**frameworks/base/include/binder/IMemory.h**文件中: ~~~ class IMemoryHeap : public IInterface { public: ...... virtual int getHeapID() const = 0; virtual void* getBase() const = 0; virtual size_t getSize() const = 0; ...... }; ~~~ 成員函數getHeapID是用來獲得匿名共享內存塊的打開文件描述符的;成員函數getBase是用來獲得匿名共享內存塊的基地址的,有了這個地址之后,我們就可以在程序里面直接訪問這塊共享內存了;成員函數getSize是用來獲得匿名共享內存塊的大小的。 MemoryHeapBase類主要用來實現上面IMemoryBase類中列出來的幾個成員函數的,這個類聲明在**frameworks/base/include/binder/MemoryHeapBase.h**文件中: ~~~ class MemoryHeapBase : public virtual BnMemoryHeap { public: ...... /* * maps memory from ashmem, with the given name for debugging */ MemoryHeapBase(size_t size, uint32_t flags = 0, char const* name = NULL); ...... /* implement IMemoryHeap interface */ virtual int getHeapID() const; virtual void* getBase() const; virtual size_t getSize() const; ...... private: int mFD; size_t mSize; void* mBase; ...... } ~~~ MemoryHeapBase類的實現定義在frameworks/base/libs/binder/MemoryHeapBase.cpp文件中,我們先來看一下它的構造函數的實現: ~~~ MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name) : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), mDevice(0), mNeedUnmap(false) { const size_t pagesize = getpagesize(); size = ((size + pagesize-1) & ~(pagesize-1)); int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size); LOGE_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); } } } } ~~~ 這個構造函數有三個參數,其中size表示要創建的匿名共享內存的大小,flags是用來設置這塊匿名共享內存的屬性的,例如是可讀寫的還是只讀的,name是用來標識這個匿名共享內存的名字的,可以傳空值進來,這個參數只是作為調試信息使用的。 MemoryHeapBase類創建的匿名共享內存是以頁為單位的,頁的大小一般為4K,但是是可以設置的,這個函數首先通過getpagesize函數獲得系統中一頁內存的大小值,然后把size參數對齊到頁大小去,即如果size不是頁大小的整數倍時,就增加它的大小,使得它的值為頁大小的整數倍: ~~~ const size_t pagesize = getpagesize(); size = ((size + pagesize-1) & ~(pagesize-1)); ~~~ 調整好size的大小后,就調用系統運行時庫層的C接口ashmem_create_region來創建一塊共享內存了: ~~~ int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size); ~~~ 這個函數我們在前面一篇文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析中可以介紹過了,這里不再詳細,它只要就是通過Ashmem驅動程序來創建一個匿名共享內存文件,因此,它的返回值是一個文件描述符。 得到了這個匿名共享內存的文件描述符后,還需要調用mapfd成函數把它映射到進程地址空間去: ~~~ status_t MemoryHeapBase::mapfd(int fd, size_t size, uint32_t offset) { ...... if ((mFlags & DONT_MAP_LOCALLY) == 0) { void* base = (uint8_t*)mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset); ...... mBase = base; ...... } else { ...... } mFD = fd; mSize = size; return NO_ERROR; } ~~~ 一般我們創建MemoryHeapBase類的實例時,都是需要把匿名共享內存映射到本進程的地址空間去的,因此,這里的條件(mFlags & DONT_MAP_LOCALLY == 0)為true,于是執行系統調用mmap來執行內存映射的操作。 ~~~ void* base = (uint8_t*)mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset); ~~~ 傳進去的第一個參數0表示由內核來決定這個匿名共享內存文件在進程地址空間的起始位置,第二個參數size表示要映射的匿名共享內文件的大小,第三個參數PROT_READ|PROT_WRITE表示這個匿名共享內存是可讀寫的,第四個參數fd指定要映射的匿名共享內存的文件描述符,第五個參數offset表示要從這個文件的哪個偏移位置開始映射。調用了這個函數之后,最后會進入到內核空間的ashmem驅動程序模塊中去執行ashmem_map函數,這個函數的實現具體可以參考Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析一文,這里就不同詳細描述了。調用mmap函數返回之后,就得這塊匿名共享內存在本進程地址空間中的起始訪問地址了,將這個地址保存在成員變量mBase中,最后,還將這個匿名共享內存的文件描述符和以及大小分別保存在成員變量mFD和mSize中。 回到前面MemoryHeapBase類的構造函數中,將匿名共享內存映射到本進程的地址空間去后,還看繼續設置這塊匿名共享內存的讀寫屬性: ~~~ if (fd >= 0) { if (mapfd(fd, size) == NO_ERROR) { if (flags & READ_ONLY) { ashmem_set_prot_region(fd, PROT_READ); } } } ~~~ 上面調用mapfd函數來映射匿名共享內存時,指定這塊內存是可讀寫的,但是如果傳進來的參數flags設置了只讀屬性,那么還需要調用系統運行時庫存層的ashmem_set_prot_region函數來設置這塊匿名共享內存為只讀,這個函數定義在system/core/libcutils/ashmem-dev.c文件,有興趣的讀者可以自己去研究一下。 這樣,通過這個構造函數,一塊匿名共享內存就建立好了,其余的三個成員函數getHeapID、getBase和getSize就簡單了: ~~~ int MemoryHeapBase::getHeapID() const { return mFD; } void* MemoryHeapBase::getBase() const { return mBase; } size_t MemoryHeapBase::getSize() const { return mSize; } ~~~ 接下來我們再來看一下MemoryHeapBase在Client端實現的類圖: ![](http://hi.csdn.net/attachment/201111/6/0_1320566518k7tu.gif) 這個類圖中的類也是可以劃分為兩部分,一部分是和業務相關的,即跟匿名共享內存操作相關的類,包括BpMemoryHeap、IMemoryBase和RefBase三個類,另一部分是和Binder機制相關的,包括IInterface、BpInterface、BpRefBase、IBinder、BpBinder、ProcessState和IPCThreadState七個類。 在和匿名共享內存操作相關的類中,BpMemoryHeap類是前面分析的MemoryHeapBase類在Client端進程的遠接接口類,當Client端進程從Service Manager或者其它途徑獲得了一個MemoryHeapBase對象的引用之后,就會在本地創建一個BpMemoryHeap對象來代表這個引用。BpMemoryHeap類同樣是要實現IMemoryHeap接口,同時,它是從RefBase類繼承下來的,因此,它可以與智能指針來結合使用。 在和Binder機制相關的類中,和Server端實現不一樣的地方是,Client端不需要實現BnInterface和BBinder兩個類,但是需要實現BpInterface、BpRefBase和BpBinder三個類。BpInterface類繼承于BpRefBase類,而在BpRefBase類里面,有一個成員變量mRemote,它指向一個BpBinder對象,當BpMemoryHeap類需要向Server端對象發出請求時,它就會通過這個BpBinder對象的transact函數來發出這個請求。這里的BpBinder對象是如何知道要向哪個Server對象發出請深圳市的呢?它里面有一個成員變量mHandle,它表示的是一個Server端Binder對象的引用值,BpBinder對象就是要通過這個引用值來把請求發送到相應的Server端對象去的了,這個引用值與Server端Binder對象的對應關系是在Binder驅動程序內部維護的。這里的ProcessSate類和IPCThreadState類的作用和在Server端的作用是類似的,它們都是負責和底層的Binder驅動程序進行交互,例如,BpBinder對象的transact函數就通過線程中的IPCThreadState對象來將Client端請求發送出去的。這些實現具體可以參考Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析一文。 這里我們主要關注BpMemoryHeap類是如何實現IMemoryHeap接口的,這個類聲明和定義在**frameworks/base/libs/binder/IMemory.cpp**文件中: ~~~ class BpMemoryHeap : public BpInterface<IMemoryHeap> { public: BpMemoryHeap(const sp<IBinder>& impl); ...... virtual int getHeapID() const; virtual void* getBase() const; virtual size_t getSize() const; ...... private: mutable volatile int32_t mHeapId; mutable void* mBase; mutable size_t mSize; ...... } ~~~ 先來看構造函數BpMemoryHeap的實現: ~~~ BpMemoryHeap::BpMemoryHeap(const sp<IBinder>& impl) : BpInterface<IMemoryHeap>(impl), mHeapId(-1), mBase(MAP_FAILED), mSize(0), mFlags(0), mRealHeap(false) { } ~~~ 它的實現很簡單,只是初始化一下各個成員變量,例如,表示匿名共享內存文件描述符的mHeapId值初化為-1、表示匿名內共享內存基地址的mBase值初始化為MAP_FAILED以及表示匿名共享內存大小的mSize初始為為0,它們都表示在Client端進程中,這個匿名共享內存還未準備就緒,要等到第一次使用時才會去創建。這里還需要注意的一點,參數impl指向的是一個BpBinder對象,它里面包含了一個指向Server端Binder對象,即MemoryHeapBase對象的引用。 其余三個成員函數getHeapID、getBase和getSize的實現是類似的: ~~~ int BpMemoryHeap::getHeapID() const { assertMapped(); return mHeapId; } void* BpMemoryHeap::getBase() const { assertMapped(); return mBase; } size_t BpMemoryHeap::getSize() const { assertMapped(); return mSize; } ~~~ 即它們在使用之前,都會首先調用assertMapped函數來保證在Client端的匿名共享內存是已經準備就緒了的: ~~~ void BpMemoryHeap::assertMapped() const { if (mHeapId == -1) { sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder()); 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; android_atomic_write( dup( heap->mHeapId ), &mHeapId ); } } else { // something went wrong free_heap(binder); } } } ~~~ 在解釋這個函數之前,我們需要先了解一下BpMemoryHeap是如何知道自己內部維護的這塊匿名共享內存有沒有準備就緒的。 在frameworks/base/libs/binder/IMemory.cpp文件中,定義了一個全局變量gHeapCache: ~~~ static sp<HeapCache> gHeapCache = new HeapCache(); ~~~ 它的類型為HeapCache,這也是一個定義在frameworks/base/libs/binder/IMemory.cpp文件的類,它里面維護了本進程中所有的MemoryHeapBase對象的引用。由于在Client端進程中,可能會有多個引用,即多個BpMemoryHeap對象,對應同一個MemoryHeapBase對象(這是由于可以用同一個BpBinder對象來創建多個BpMemoryHeap對象),因此,當第一個BpMemoryHeap對象在本進程中映射好這塊匿名共享內存之后,后面的BpMemoryHeap對象就可以直接使用了,不需要再映射一次,當然重新再映射一次沒有害處,但是會是多此一舉,Google在設計這個類時,可以說是考慮得非常周到的。 我們來看一下HeapCache的實現: ~~~ class HeapCache : public IBinder::DeathRecipient { public: HeapCache(); virtual ~HeapCache(); ...... sp<IMemoryHeap> find_heap(const sp<IBinder>& binder); void free_heap(const sp<IBinder>& binder); sp<IMemoryHeap> get_heap(const sp<IBinder>& binder); ...... private: // For IMemory.cpp struct heap_info_t { sp<IMemoryHeap> heap; int32_t count; }; ...... Mutex mHeapCacheLock; KeyedVector< wp<IBinder>, heap_info_t > mHeapCache; }; ~~~ 它里面定義了一個成員變量mHeapCache,用來維護本進程中的所有BpMemoryHeap對象,同時還提供了find_heap和get_heap函數來查找內部所維護的BpMemoryHeap對象的功能。函數find_heap和get_heap的區別是,在find_heap函數中,如果在mHeapCache找不到相應的BpMemoryHeap對象,就會把這個BpMemoryHeap對象加入到mHeapCache中去,而在get_heap函數中,則不會自動把這個BpMemoryHeap對象加入到mHeapCache中去。 這里,我們主要看一下find_heap函數的實現: ~~~ sp<IMemoryHeap> HeapCache::find_heap(const sp<IBinder>& binder) { Mutex::Autolock _l(mHeapCacheLock); ssize_t i = mHeapCache.indexOfKey(binder); if (i>=0) { heap_info_t& info = mHeapCache.editValueAt(i); LOGD_IF(VERBOSE, "found binder=%p, heap=%p, size=%d, fd=%d, count=%d", binder.get(), info.heap.get(), static_cast<BpMemoryHeap*>(info.heap.get())->mSize, static_cast<BpMemoryHeap*>(info.heap.get())->mHeapId, info.count); android_atomic_inc(&info.count); return info.heap; } else { heap_info_t info; info.heap = interface_cast<IMemoryHeap>(binder); 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; } } ~~~ 這個函數很簡單,首先它以傳進來的參數binder為關鍵字,在mHeapCache中查找,看看是否有對應的heap_info對象info存在,如果有的話,就增加它的引用計數info.count值,表示這個BpBinder對象多了一個使用者;如果沒有的話,那么就需要創建一個heap_info對象info,并且將它加放到mHeapCache中去了。 回到前面BpMemoryHeap類中的assertMapped函數中,如果本BpMemoryHeap對象中的mHeapID等于-1,那么就說明這個BpMemoryHeap對象中的匿名共享內存還沒準備就緒,因此,需要執行一次映射匿名共享內存的操作。 在執行映射操作之作,先要看看在本進程中是否有其它映射到同一個MemoryHeapBase對象的BpMemoryHeap對象存在: ~~~ sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder()); sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get())); ~~~ 這里的find_heap函數是BpMemoryHeap的成員函數,最終它調用了前面提到的全局變量gHeapCache來直正執行查找的操作: ~~~ class BpMemoryHeap : public BpInterface<IMemoryHeap> { ...... private: static inline sp<IMemoryHeap> find_heap(const sp<IBinder>& binder) { return gHeapCache->find_heap(binder); } ...... } ~~~ 注意,這里通過find_heap函數得到BpMemoryHeap對象可能是和正在執行assertMapped函數中的BpMemoryHeap對象一樣,也可能不一樣,但是這沒有關系,這兩種情況的處理方式都是一樣的,都是通過調用這個通過find_heap函數得到BpMemoryHeap對象的assertReallyMapped函數來進一步確認它內部的匿名共享內存是否已經映射到進程空間了: ~~~ void BpMemoryHeap::assertReallyMapped() const { if (mHeapId == -1) { // remote call without mLock held, worse case scenario, we end up // calling transact() from multiple threads, but that's not a problem, // only mmap below must be in the critical section. Parcel data, reply; data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor()); status_t err = remote()->transact(HEAP_ID, data, &reply); int parcel_fd = reply.readFileDescriptor(); ssize_t size = reply.readInt32(); uint32_t flags = reply.readInt32(); 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 ); 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; } Mutex::Autolock _l(mLock); if (mHeapId == -1) { mRealHeap = true; mBase = mmap(0, size, access, MAP_SHARED, fd, 0); if (mBase == MAP_FAILED) { 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); } } } } ~~~ 如果成員變量mHeapId的值為-1,就說明還沒有把在Server端的MemoryHeapBase對象中的匿名共享內存映射到本進程空間來,于是,就通過一個Binder進程間調用把Server端的MemoryHeapBase對象中的匿名共享內存對象信息取回來: ~~~ Parcel data, reply; data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor()); status_t err = remote()->transact(HEAP_ID, data, &reply); int parcel_fd = reply.readFileDescriptor(); ssize_t size = reply.readInt32(); uint32_t flags = reply.readInt32(); ...... int fd = dup( parcel_fd ); ...... ~~~ 取回來的信息包括MemoryHeapBase對象中的匿名共享內存在本進程中的文件描述符fd、大小size以及訪問屬性flags。如何把MemoryHeapBase對象中的匿名共享內存作為本進程的一個打開文件描述符,請參考前面一篇文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析。有了這個文件描述符fd后,就可以對它進行內存映射操作了: ~~~ Mutex::Autolock _l(mLock); if (mHeapId == -1) { mRealHeap = true; mBase = mmap(0, size, access, MAP_SHARED, fd, 0); if (mBase == MAP_FAILED) { 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); } } ~~~ 前面已經判斷過mHeapId是否為-1了,這里為什么又要重新判斷一次呢?這里因為,在上面執行Binder進程間調用的過程中,很有可能也有其它的線程也對這個BpMemoryHeap對象執行匿名共享內存映射的操作,因此,這里還要重新判斷一下mHeapId的值是否為-1,如果是的話,就要執行匿名共享內存映射的操作了,這是通過調用mmap函數來進行的,這個函數我們前面在分析MemoryHeapBase類的實現時已經見過了。 從assertReallyMapped函數返回到assertMapped函數中: ~~~ if (heap->mBase != MAP_FAILED) { Mutex::Autolock _l(mLock); if (mHeapId == -1) { mBase = heap->mBase; mSize = heap->mSize; android_atomic_write( dup( heap->mHeapId ), &mHeapId ); } } else { // something went wrong free_heap(binder); } ~~~ 如果heap->mBase的值不為MAP_FAILED,就說明這個heap對象中的匿名共享內存已經映射好了。進入到里面的if語句,如果本BpMemoryHeap對象中的mHeap成員變量的值不等待-1,就說明前面通過find_heap函數得到的BpMemoryHeap對象和正在執行assertMapped函數的BpMemoryHeap對象是同一個對象了,因此,什么也不用做就可以返回了,否則的話,就要初始化一下本BpMemoryHeap對象的相關成員變量了: ~~~ mBase = heap->mBase; mSize = heap->mSize; android_atomic_write( dup( heap->mHeapId ), &mHeapId ); ~~~ 注意,由于這塊匿名共享內存已經在本進程中映射好了,因此,這里不需要再執行一次mmap操作,只需要把heap對象的相應成員變量的值拷貝過來就行了,不過對于文件描述符,需要通過dup函數來復制一個。 這樣,BpMemoryHeap對象中的匿名共享內存就準備就緒了,可以通過使用的它mBase成員變量來直接訪問這塊匿名共享內存。 至此,MemoryHeapBase類的實現就分析完了,下面我們繼續分析MemoryBase類的實現。 2. MemoryBase 文章開始時說過,MemoryBase接口是建立在MemoryHeapBase接口的基礎上的,它們都可以作為一個Binder對象來在進程間進行數據共享,它們的關系如下所示: ![](http://hi.csdn.net/attachment/201111/6/0_13205739214AOu.gif) MemoryBase類包含了一個成員變量mHeap,它的類型的IMemoryHeap,MemoryBase類所代表的匿名共享內存就是通過這個成員變量來實現的。 與MemoryHeapBase的分析過程一樣,我們先來看MemoryBase類在Server端的實現,然后再來看它在Client端的實現。 MemoryBase在Server端實現的類圖如下所示: ![](http://hi.csdn.net/attachment/201111/6/0_1320574208wQGD.gif) MemoryBase類在Server端的實現與MemoryHeapBase類在Server端的實現是類似的,這里只要把IMemory類換成IMemoryHeap類、把BnMemory類換成BnMemoryHeap類以及MemoryBase類換成MemoryHeapBase類就變成是MemoryHeapBase類在Server端的實現了,因此,我們這里只簡單分析IMemory類和MemoryBase類的實現。 IMemory類定義了MemoryBase類所需要實現的接口,這個類定義在**frameworks/base/include/binder/IMemory.h**文件中: ~~~ class IMemory : public IInterface { public: ...... virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const = 0; ...... void* pointer() const; size_t size() const; ssize_t offset() const; }; ~~~ 成員函數getMemory用來獲取內部的MemoryHeapBase對象的IMemoryHeap接口;成員函數pointer()用來獲取內部所維護的匿名共享內存的基地址;成員函數size()用來獲取內部所維護的匿名共享內存的大小;成員函數offset()用來獲取內部所維護的這部分匿名共享內存在整個匿名共享內存中的偏移量。 IMemory類本身實現了pointer、size和offset三個成員函數,因此,它的子類,即MemoryBase類,只需要實現getMemory成員函數就可以了。IMemory類的實現定義在**frameworks/base/libs/binder/IMemory.cpp**文件中: ~~~ 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; } size_t IMemory::size() const { size_t size; getMemory(NULL, &size); return size; } ssize_t IMemory::offset() const { ssize_t offset; getMemory(&offset); return offset; } ~~~ MemoryBase類聲明在**frameworks/base/include/binder/MemoryBase.h**文件中: ~~~ class MemoryBase : public BnMemory { public: MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size); ...... virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const; ...... private: size_t mSize; ssize_t mOffset; sp<IMemoryHeap> mHeap; }; ~~~ MemoryBase類實現在frameworks/base/libs/binder/MemoryBase.cpp文件中: ~~~ MemoryBase::MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size) : mSize(size), mOffset(offset), mHeap(heap) { } sp<IMemoryHeap> MemoryBase::getMemory(ssize_t* offset, size_t* size) const { if (offset) *offset = mOffset; if (size) *size = mSize; return mHeap; } ~~~ 在它的構造函數中,接受三個參數,參數heap指向的是一個MemoryHeapBase對象,真正的匿名共享內存就是由它來維護的,參數offset表示這個MemoryBase對象所要維護的這部分匿名共享內存在整個匿名共享內存塊中的起始位置,參數size表示這個MemoryBase對象所要維護的這部分匿名共享內存的大小。 成員函數getMemory的實現很簡單,只是簡單地返回內部的MemoryHeapBase對象的IMemoryHeap接口,如果傳進來的參數offset和size不為NULL,還會把其內部維護的這部分匿名共享內存在整個匿名共享內存塊中的偏移位置以及這部分匿名共享內存的大小返回給調用者。 這里我們可以看出,MemoryBase在Server端的實現只是簡單地封裝了MemoryHeapBase的實現。 下面我們再來看MemoryBase類在Client端的實現,同樣,先看它們的類圖關系: ![](http://hi.csdn.net/attachment/201111/6/0_132057570121ft.gif) 這個圖中我們可以看出,MemoryBase類在Client端的實現與MemoryHeapBase類在Client端的實現是類似的,這里只要把IMemory類換成IMemoryHeap類以及把BpMemory類換成BpMemoryHeap類就變成是MemoryHeapBase類在Client端的實現了,因此,我們這里只簡單分析BpMemory類的實現,前面已經分析過IMemory類的實現了。 BpMemory類實現在**frameworks/base/libs/binder/IMemory.cpp**文件中,我們先看它的聲明: ~~~ class BpMemory : public BpInterface<IMemory> { public: BpMemory(const sp<IBinder>& impl); virtual ~BpMemory(); virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const; private: mutable sp<IMemoryHeap> mHeap; mutable ssize_t mOffset; mutable size_t mSize; }; ~~~ 和MemoryBase類一樣,它實現了IMemory類的getMemory成員函數,在它的成員變量中,mHeap的類型為IMemoryHeap,它指向的是一個BpMemoryHeap對象,mOffset表示這個BpMemory對象所要維護的這部分匿名共享內存在整個匿名共享內存塊中的起始位置,mSize表示這個BpMemory對象所要維護的這部分匿名共享內存的大小。 下面我們就看一下BpMemory類的成員函數getMemory的實現: ~~~ sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const { if (mHeap == 0) { Parcel data, reply; data.writeInterfaceToken(IMemory::getInterfaceDescriptor()); if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) { sp<IBinder> heap = reply.readStrongBinder(); ssize_t o = reply.readInt32(); size_t s = reply.readInt32(); 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; } ~~~ 如果成員變量mHeap的值為NULL,就表示這個BpMemory對象尚未建立好匿名共享內存,于是,就會通過一個Binder進程間調用去Server端請求匿名共享內存信息,在這些信息中,最重要的就是這個Server端的MemoryHeapBase對象的引用heap了,通過這個引用可以在Client端進程中創建一個BpMemoryHeap遠程接口,最后將這個BpMemoryHeap遠程接口保存在成員變量mHeap中,同時,從Server端獲得的信息還包括這塊匿名共享內存在整個匿名共享內存中的偏移位置以及大小。這樣,這個BpMemory對象中的匿名共享內存就準備就緒了。 至此,MemoryBase類的實現就分析完了,下面我們將通過一個實例來說明如何使用MemoryBase類在進程間進行內存共享,因為MemoryBase內部使用了MemoryHeapBase類,所以,這個例子同時也可以說明MemoryHeapBase類的使用方法。 3. MemoryHeapBas類e和MemoryBase類的使用示例 在這個例子中,我們將在Android源代碼工程的external目錄中創建一個ashmem源代碼工程,它里面包括兩個應用程序,一個是Server端應用程序SharedBufferServer,它提供一段共享內存來給Client端程序使用,一個是Client端應用程序SharedBufferClient,它簡單地對Server端提供的共享內存進行讀和寫的操作。Server端應用程序SharedBufferServer和Client端應用程序SharedBufferClient通過Binder進程間通信機制來交互,因此,我們需要定義自己的Binder對象接口ISharedBuffer。Server端應用程序SharedBufferServer在內部實現了一個服務SharedBufferService,這個服務托管給Service Manager來管理,因此,Client端應用程序SharedBufferClient可以向Service Manager請求這個SharedBufferService服務的一個遠接接口,然后就可以通過這個服務來操作Server端提供的這段共享內存了。 這個工程由三個模塊組成,第一個模塊定義服務接口,它的相關源代碼位于external/ashmem/common目錄下,第二個模塊實現Server端應用程序SharedBufferServer,它的相關源代碼位于external/ashmem/server目錄下,第三個模塊實現Client端應用程序SharedBufferClient,它的相關源代碼碼位于external/ashmem/client目錄下。 首先來看common模塊中的服務接口的定義。在external/ashmem/common目錄下,有兩個源文件ISharedBuffer.h和ISharedBuffer.cpp。源文件ISharedBuffer.h定義了服務的接口: ~~~ #ifndef ISHAREDBUFFER_H_ #define ISHAREDBUFFER_H_ #include <utils/RefBase.h> #include <binder/IInterface.h> #include <binder/Parcel.h> #define SHARED_BUFFER_SERVICE "shy.luo.SharedBuffer" #define SHARED_BUFFER_SIZE 4 using namespace android; class ISharedBuffer: public IInterface { public: DECLARE_META_INTERFACE(SharedBuffer); virtual sp<IMemory> getBuffer() = 0; }; class BnSharedBuffer: public BnInterface<ISharedBuffer> { public: virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0); }; #endif ~~~ 這個文件定義了一個ISharedBuffer接口,里面只有一個成員函數getBuffer,通過這個成員函數,Client端可以從Server端獲得一個匿名共享內存,這塊匿名共享內存通過我們上面分析的MemoryBase類來維護。這個文件同時也定義了一個必須要在Server端實現的BnSharedBuffer接口,它里面只有一個成員函數onTransact,這個成員函數是用來處理Client端發送過來的請求的。除了定義這兩個接口之外,這個文件還定義了兩個公共信息,一個是定義常量SHARED_BUFFER_SERVICE,它是Server端提供的內存共享服務的名稱,即這個內存共享服務在Service Manager中是以SHARED_BUFFER_SERVICE來作關鍵字索引的,另外一個是定義常量SHARED_BUFFER_SIZE,它定義了Server端共享的內存塊的大小,它的大小設置為4個字節,在這個例子,將把這個共享內存當作一個整型變量來訪問。 源代文件ISharedBuffer.cpp文件定義了一個在Client端使用的BpSharedBuffer接口,它是指向運行在Server端的實現了ISharedBuffer接口的內存共享服務的遠程接口,同時,在這個文件里面,也實現了BnSharedBuffer類的onTransact成員函數: ~~~ #define LOG_TAG "ISharedBuffer" #include <utils/Log.h> #include <binder/MemoryBase.h> #include "ISharedBuffer.h" using namespace android; enum { GET_BUFFER = IBinder::FIRST_CALL_TRANSACTION }; class BpSharedBuffer: public BpInterface<ISharedBuffer> { public: BpSharedBuffer(const sp<IBinder>& impl) : BpInterface<ISharedBuffer>(impl) { } public: sp<IMemory> getBuffer() { Parcel data; data.writeInterfaceToken(ISharedBuffer::getInterfaceDescriptor()); Parcel reply; remote()->transact(GET_BUFFER, data, &reply); sp<IMemory> buffer = interface_cast<IMemory>(reply.readStrongBinder()); return buffer; } }; IMPLEMENT_META_INTERFACE(SharedBuffer, "shy.luo.ISharedBuffer"); status_t BnSharedBuffer::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { switch(code) { case GET_BUFFER: { CHECK_INTERFACE(ISharedBuffer, data, reply); sp<IMemory> buffer = getBuffer(); if(buffer != NULL) { reply->writeStrongBinder(buffer->asBinder()); } return NO_ERROR; } default: { return BBinder::onTransact(code, data, reply, flags); } } } ~~~ 在BpSharedBuffer類的成員函數transact中,向Server端發出了一個請求代碼為GET_BUFFER的Binder進程間調用請求,請求Server端返回一個匿名共享內存對象的遠程接口IMemory,它實際指向的是一個BpMemory對象,獲得了這個對象之后,就將它返回給調用者;在BnSharedBuffer類的成員函數onTransact中,當它接收到從Client端發送過來的代碼為GET_BUFFER的Binder進程間調用請求后,便調用其子類的getBuffer成員函數來獲一個匿名共享內存對象接口IMemory,它實際指向的是一個MemoryBase對象,獲得了這個對象之后,就把它返回給Client端。 接下來,我們再來看看server模塊的實現。在external/ashmem/common目錄下,只有一個源文件SharedBufferServer.cpp,它實現了內存共享服務SharedBufferService: ~~~ #define LOG_TAG "SharedBufferServer" #include <utils/Log.h> #include <binder/MemoryBase.h> #include <binder/MemoryHeapBase.h> #include <binder/IServiceManager.h> #include <binder/IPCThreadState.h> #include "../common/ISharedBuffer.h" class SharedBufferService : public BnSharedBuffer { public: SharedBufferService() { sp<MemoryHeapBase> heap = new MemoryHeapBase(SHARED_BUFFER_SIZE, 0, "SharedBuffer"); if(heap != NULL) { mMemory = new MemoryBase(heap, 0, SHARED_BUFFER_SIZE); int32_t* data = (int32_t*)mMemory->pointer(); if(data != NULL) { *data = 0; } } } virtual ~SharedBufferService() { mMemory = NULL; } public: static void instantiate() { defaultServiceManager()->addService(String16(SHARED_BUFFER_SERVICE), new SharedBufferService()); } virtual sp<IMemory> getBuffer() { return mMemory; } private: sp<MemoryBase> mMemory; }; int main(int argc, char** argv) { SharedBufferService::instantiate(); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); return 0; } ~~~ SharedBufferService服務實現了BnSharedBuffer接口。在它的構造函數里面,首先是使用MemoryHeapBase類創建了一個匿名共享內存,大小為SHARED_BUFFER_SIZE。接著,又以這個MemoryHeapBase對象為參數,創建一個MemoryBase對象,這個MemoryBase對象指定要維護的匿名共享內存的的偏移位置為0,大小為SHARED_BUFFER_SIZE,并且,將這個匿名共享內存當作一個整型變量地址,將它初始化為0。最終,這個匿名共享內存對象保存在SharedBufferService類的成員變量mMemory中,這個匿名共享內存對象可以通過成員函數getBuffer來獲得。 在Server端應用程序的入口函數main中,首先是調用SharedBufferService靜態成員函數instantiate函數來創建一個SharedBufferService實例,然后通過defaultServiceManager函數來獲得系統中的Service Manager接口,最后通過這個Service Manager接口的addService函數來把這個SharedBufferService服務添加到Service Manager中去,這樣,Client端就可以通過Service Manager來獲得這個共享內存服務了。有關Service Manager的實現,請參考前面一篇文章淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路,而用來獲取Service Manager接口的defaultServiceManager函數的實現可以參考另外一篇文章淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路。初始化好這個共享內存服務之后,程序就通過ProcessState::self()->startThreadPool()函數來創建一個線程等待Client端來請求服務了,最后,程序的主線程也通過IPCThreadState::self()->joinThreadPool()函數來進入到等待Client端來請求服務的狀態中。 我們還需要為這個Server端應用程序編譯一個編譯腳本,在external/ashmem/server目錄下,新建一個Android.mk文件,它的內容如下所示: ~~~ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := ../common/ISharedBuffer.cpp \ SharedBufferServer.cpp LOCAL_SHARED_LIBRARIES:= libcutils libutils libbinder LOCAL_MODULE := SharedBufferServer include $(BUILD_EXECUTABLE) ~~~ 最后,我們再來看看client模塊的實現。在external/ashmem/client目錄下,只有一個源文件SharedBufferClient.cpp,它的內容如下所示: ~~~ #define LOG_TAG "SharedBufferClient" #include <utils/Log.h> #include <binder/MemoryBase.h> #include <binder/IServiceManager.h> #include "../common/ISharedBuffer.h" int main() { sp<IBinder> binder = defaultServiceManager()->getService(String16(SHARED_BUFFER_SERVICE)); if(binder == NULL) { printf("Failed to get service: %s.\n", SHARED_BUFFER_SERVICE); return -1; } sp<ISharedBuffer> service = ISharedBuffer::asInterface(binder); if(service == NULL) { return -2; } sp<IMemory> buffer = service->getBuffer(); if(buffer == NULL) { return -3; } int32_t* data = (int32_t*)buffer->pointer(); if(data == NULL) { return -4; } printf("The value of the shared buffer is %d.\n", *data); *data = *data + 1; printf("Add value 1 to the shared buffer.\n"); return 0; } ~~~ 在這個文件中,主要就是定義了Client端應用程序的入口函數main,在這個main函數里面,首先通過Service Manager接口獲得前面所實現的匿名共享內存服務SharedBufferService的遠程接口service,然后通過這個遠程接口的getBuffer成員函數獲得由Server端提供的一塊匿名共享內存接口buffer,最后通過這個匿名共享內存接口獲得這個匿名共享內存的基地址data。有了這個匿名共享內存的地址data之后,我們就可以對它進行讀寫了,先是把這個匿名共享內存當作是一個整型變量地址進行訪問,并輸出它的值的大小,然后對這個整量變量進行加1的操作,并寫回到原來的共享內存空間中去。這樣,當Server端應用程序運行之后,第一次運行這個Client端應用程序時,輸出的值為0,第二次運行這個個Client端應用程序時,輸出的值為1,第三次運行這個個Client端應用程序時,輸出的值為3......依次類推,后面我們將在模擬器中對這個分析進行驗證,如果驗證成功的話,就說明這個匿名共享內存成功地在Server端和Client端實現共享了。 同樣,我們需要為這個Client端應用程序編譯一個編譯腳本,在external/ashmem/client目錄下,新建一個Android.mk文件,它的內容如下所示: ~~~ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := ../common/ISharedBuffer.cpp \ SharedBufferClient.cpp LOCAL_SHARED_LIBRARIES:= libcutils libutils libbinder LOCAL_MODULE := SharedBufferClient include $(BUILD_EXECUTABLE) ~~~ 源代碼都準備好了之后,就可以對Server端和Client端應用程序進行編譯了。關于如何單獨編譯Android源代碼工程中的模塊,以及如何打包system.img,請參考如何單獨編譯Android源代碼中的模塊一文。 執行以下命令進行編譯和打包: ~~~ USER-NAME@MACHINE-NAME:~/Android$ mmm external/ashmem/server USER-NAME@MACHINE-NAME:~/Android$ mmm external/ashmem/client USER-NAME@MACHINE-NAME:~/Android$ make snod ~~~ 這樣,打包好的Android系統鏡像文件system.img就包含我們前面創建的Server端應用程序SharedBufferServer和Client端應用程序SharedBufferClient了。 至此,我們就可以運行模擬器來驗證我們的程序了。關于如何在Android源代碼工程中運行模擬器,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文。 執行以下命令啟動模擬器: ~~~ USER-NAME@MACHINE-NAME:~/Android$ emulator ~~~ 模擬器運行起來后,就可以通過adb shell命令連上它: ~~~ USER-NAME@MACHINE-NAME:~/Android$ adb shell ~~~ 最后,進入到/system/bin/目錄下: ~~~ luo@ubuntu-11-04:~/Android$ adb shell root@android:/ # cd system/bin ~~~ 進入到/system/bin/目錄后,首先在后臺中運行Server端應用程序SharedBufferServer: ~~~ root@android:/system/bin # ./SharedBufferServer & ~~~ 然后再在前臺中重復運行Client端應用程序SharedBufferClient,以便驗證程序的正確性: ~~~ root@android:/system/bin # ./SharedBufferClient The value of the shared buffer is 0. Add value 1 to the shared buffer. root@android:/system/bin # ./SharedBufferClient The value of the shared buffer is 1. Add value 1 to the shared buffer. root@android:/system/bin # ./SharedBufferClient The value of the shared buffer is 2. Add value 1 to the shared buffer. root@android:/system/bin # ./SharedBufferClient The value of the shared buffer is 3. Add value 1 to the shared buffer. ~~~ 如果我們看到這樣的輸出,就說明我們成功地在Server端應用程序SharedBufferServer和Client端應用程序SharedBufferClietn共享內存中的數據了。 至此,Android系統匿名共享內存的C++調用接口MemoryHeapBase和MemoryBase就分析完成了。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看