Android Binder 分析——資料傳遞者(Parcel)
前面 binder 原理和通訊模型中在介面實現部分(Bp 和 Bn)中應該看到很多地方都有使用 parcel。這個 android 專門設計用來跨程序傳遞資料的,實現在 native,java 層有介面(基本上是 jni 馬甲)。照例先說下原始碼位置(4.4 的):
12345678910111213141516171819202122232425262728 |
# java parcel (MemoryFile 是封裝好的匿名共享記憶體的介面)frameworks/base/core/java/os/Parcel.javaframeworks/base/core/java/os/Parcelable.javaframeworks/base/core/java/os/ParcelFileDescriptor.javaframeworks/base/core/java/os/MemoryFile.java# parcel jni 相關 |
原理
在 java 層 parcel 有個介面叫 Parcelable,和 java 的 Serializable 很像,剛開始我還沒搞明白這2個有什麼區別(以前對 java 也不太熟)。這裡簡單說一下, Serializable 是 java 的介面,翻譯過來是序列化的意思,就是通過實現這個介面能夠讓 java 的物件序列化能夠永久儲存在的儲存介質上,然後反序列化就能從儲存介質上實列化出 java 物件(通俗點,就是一個 save/load 的功能)。因為儲存到了儲存介質上,所以是可以跨程序的(一個程序把資料寫入檔案,另外一個去讀)。但是為什麼 android 還要搞一個 parcel 出來,是因為 java 的 Serializable 是通過儲存介質的,所以速度慢。parcel 是基於記憶體傳遞的,比磁碟I/O要塊,而且更加輕量級(這個我是從網上看到的,我沒研究過 java 的 Serializable 程式碼)。
parcel 在記憶體中的結構是一塊連續的記憶體,會動根據需要自動擴充套件大小(這個設計比較贊,一些對效能要求不是太高、或是小資料的地方,可以不用廢腦想分配多大空間)。parcel 傳遞資料,可以分為3種,傳遞方式也不一樣:
- 小型資料: 從使用者空間(源程序)copy 到 kernel 空間(binder 驅動中)再寫回使用者空間(目標程序,binder 驅動負責尋找目標程序)。
- 大型資料: 使用 android 的匿名共享記憶體(Ashmem)傳遞
- binder 物件: kernel binder 驅動專門處理
下面逐一分析。這裡我打算從 natvie 到 kernel 再到 java 的順序進行,因為接著前面通訊原型那裡,所以從 natvie 開始會比較好,而且實現的地方也在 native。
小型資料
先來看看 Parcel.h 中幾個比較關鍵的幾個變數:
123456789 | uint8_t* mData;size_t mDataSize;size_t mDataCapacity;mutable size_t mDataPos;size_t* mObjects;size_t mObjectsSize;size_t mObjectsCapacity;mutable size_t mNextObjectHint; |
- mData: 資料指標,也是資料在本程序空間內的記憶體地址
- mDataSize: 儲存的資料大小(使用的空間大小)
- mDataCapacity: 資料空間大小,如果不夠的話,可以動態增長
- mDataPos: 資料遊標,當前資料的位置,和讀檔案的遊標類似,可以手動設定。聲明瞭 mutbale 屬性,可以學習下這個屬性應該用在宣告地方 ^_^。
- mObjects:
flat_binder_object
物件的位置資料,注意這個是個指標(其實就是個陣列),裡面儲存的不是資料,而且地址的偏移(後面再具體說)。 - mObjectsSize: 這個簡單來說其實就是上面那個 objects 陣列的大小。
- mObjectsCapacity: objects 偏移地址(再次強調一次是地址)的空間大小,同樣可以動態增長
- mNextObjectHint: 可以理解為 objects 的 dataPos 。
還記得通訊模型 IPCThreadState 中有2個 Parcel 變數: mIn、mOut,前面分析這2個東西是 binder 通訊的時候打包資料用的。我們通過結合前面的例子來分析。
首先是初始化,IPCThreadState 是直接使用變數的(棧記憶體),使用預設建構函式:
123456789101112131415161718192021222324 | Parcel::Parcel(){ initState();}void Parcel::initState(){ mError = NO_ERROR; mData = 0; mDataSize = 0; mDataCapacity = 0; mDataPos = 0; ALOGV("initState Setting data size of %p to %d\n", this, mDataSize); ALOGV("initState Setting data pos of %p to %d\n", this, mDataPos); mObjects = NULL; mObjectsSize = 0; mObjectsCapacity = 0; mNextObjectHint = 0; mHasFds = false; mFdsKnown = true; mAllowFds = true; mOwner = NULL;} |
初始話很簡單,幾乎都是初始化為 0(NULL) 的。然後看看 IPCThreadState 使用的初始化:
123456789101112 | IPCThreadState::IPCThreadState() : mProcess(ProcessState::self()), mMyThreadId(androidGetTid()), mStrictModePolicy(0), mLastTransactionBinderFlags(0){ pthread_setspecific(gTLS, this); clearCaller(); mIn.setDataCapacity(256); mOut.setDataCapacity(256);} |
首先呼叫 setDataCapacity 來初始化 parcel 的資料空間大小:
123456789101112131415161718192021222324252627282930313233343536 | status_t Parcel::setDataCapacity(size_t size){ if (size > mDataCapacity) return continueWrite(size); return NO_ERROR;}status_t Parcel::continueWrite(size_t desired){... ... if (mOwner) {... ... } else if (mData) {... ... } else { // This is the first data. Easy! uint8_t* data = (uint8_t*)malloc(desired); if (!data) { mError = NO_MEMORY; return NO_MEMORY; } if(!(mDataCapacity == 0 && mObjects == NULL && mObjectsCapacity == 0)) { ALOGE("continueWrite: %d/%p/%d/%d", mDataCapacity, mObjects, mObjectsCapacity, desired); } mData = data; mDataSize = mDataPos = 0; ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos); mDataCapacity = desired; } return NO_ERROR;} |
設定空間大小的,其實主要是呼叫到了 contiueWrite 函式。前面那個 size > mDataCapacity 判斷意思是如果設定的大小比原來的要大,則需要調整申請的記憶體的大小,如果小的話,就直接使用原來的大小。
接下來看 contiueWrite,前面有個 object 的判斷先不管。然後下面主要是分開3個分支,分別是:
- 分支一: 如果設定了 release 函式指標(mOwner是個函式指標),呼叫 release 函式進行處理。
- 分支二: 沒有設定 release 函式指標,但是 mData 中存在資料,需要在原來的資料的基礎上擴充套件儲存空間。
- 分支三: 沒有設定 release 函式指標,並且 mData 中不存在資料(就是註釋中說的第一次使用, Easy -_-||),呼叫 malloc 申請記憶體塊,儲存在 mData。設定相應的設定 capacity、size、pos、object 的值。
這裡先貼出分支三的程式碼,第一次使用,是走分支三的,其它2個後面再說。這裡注意一點,這裡只 malloc 了一個塊記憶體,就是 mData 的,前面說 parcel 儲存結構是一塊連續的記憶體,mObjects 只是儲存的只是地址的偏移,這裡可以看到一些端倪(後面就能清楚)。
初始化了之後,我們看看怎麼使用的,在通訊模型中我們說道 Bp 端發起 IPC 呼叫,通過 IPCThreadState 對 binder 驅動寫入請求資料傳送到 Bn 端,我們回想下 Bp 端寫資料的地方:
12345678910111213 | status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer){ binder_transaction_data tr;... ... mOut.writeInt32(cmd); mOut.write(&tr, sizeof(tr)); return NO_ERROR;} |
先看前面 writeInt32 這個,對 parcel 寫入一個 32bit 的 int 型資料。parcel 介面中有一類是專門針對基本型別(int、float、double、int陣列),writeInt32、writeInt64、writeFloat、writeDouble、writeIntArray 這些(對應有 read 介面)。然後他們都是另一個函式:
12345678910111213141516171819202122232425262728 | template<class T>status_t Parcel::writeAligned(T val) { COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T)); if ((mDataPos+sizeof(val)) <= mDataCapacity) {restart_write: *reinterpret_cast<T*>(mData+mDataPos) = val; return finishWrite(sizeof(val)); } status_t err = growData(sizeof(val)); if (err == NO_ERROR) goto restart_write; return err;}status_t Parcel::finishWrite(size_t len){ //printf("Finish write of %d\n", len); mDataPos += len; ALOGV("finishWrite Setting data pos of %p to %d\n", this, mDataPos); if (mDataPos > mDataSize) { mDataSize = mDataPos; ALOGV("finishWrite Setting data size of %p to %d\n", this, mDataSize); } //printf("New pos=%d, size=%d\n", mDataPos, mDataSize); return NO_ERROR;} |
writeAligned 看名字就知道要記憶體對齊,第一句好像就是驗證下是否記憶體對齊的,好像能夠根據編譯選項判斷,應該是如果開啟某個編譯選項,如果傳過來的 size 沒記憶體對齊直接報錯吧,記憶體對齊的演算法都是搞一些位運算(這裡好像是4位元組對齊吧):
#define PAD_SIZE(s) (((s)+3)&~3)
int32、int64、float、double 都是4位元組對齊的。接著往下看,有個判斷當前 pos + 要寫入的資料的所佔用的空間是否比 capacity 大,就是看空間是不是夠大。前面所了 parcel 能夠根據需求自動增長空間,這裡我們先看空間夠的情況,就是走 if 裡面:
*reinterpret_cast(mData+mDataPos) = val;
直接取當前地址強制轉化指標型別,然後賦值(c/c++語言就是舒服)。然後呼叫 finishWrite 完成寫入。finishWrite 就是把 mDataPos 和 mDataSize 值改了一下(加上剛剛寫入資料的大小),從這裡可以看得出,對於 write 來說,mDataPos = mDataSize。
然後我們看看當空間不夠的情況,就是走 if 後面,有一個 growData 的函式,這個是用來調整記憶體空間的,然後一個 goto 跳轉回 if 裡面重寫寫入(parcel 的實現很多地方有 goto,其實 goto 在本函式裡面用還好)。我們來看看 growData:
12345678 | status_t Parcel::growData(size_t len){ size_t newSize = ((mDataSize+len)*3)/2; return (newSize <= mDataSize) ? (status_t) NO_MEMORY : continueWrite(newSize); } |
這裡 parcel 的增長演算法: ((mDataSize+len)*3)/2, 帶一定預測性的增長,避免頻繁的空間調整(每次調整需要重新 malloc 記憶體的,頻繁的話會影響效率的)。然後這裡有個判斷 newSize < mDataSize 就認為 NO_MEMORY。這是所如果如果溢位了(是負數),就認為申請不到記憶體了。然後呼叫的函式是 continueWrite ,和前面 setCapacity 呼叫的是同一個。前面說這個函式有3個分支,這裡我們就可以來看第2個分支了(mData 有資料的情況):
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 | status_t Parcel::continueWrite(size_t desired){ // If shrinking, first adjust for any objects that appear // after the new data size. size_t objectsSize = mObjectsSize; if (desired < mDataSize) { if (desired == 0) { objectsSize = 0; } else { while (objectsSize > 0) { if (mObjects[objectsSize-1] < desired) break; objectsSize--; } } } if (mOwner) {... ... } else if (mData) { if (objectsSize < mObjectsSize) { // Need to release refs on any objects we are dropping. const sp<ProcessState> proc(ProcessState::self()); for (size_t i=objectsSize; i<mObjectsSize; i++) { const flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(mData+mObjects[i]); if (flat->type == BINDER_TYPE_FD) { // will need to rescan because we may have lopped off the only FDs mFdsKnown = false; } release_object(proc, *flat, this); } size_t* objects = (size_t*)realloc(mObjects, objectsSize*sizeof(size_t)); if (objects) { mObjects = objects; } mObjectsSize = objectsSize; mNextObjectHint = 0; } // We own the data, so we can just do a realloc(). if (desired > mDataCapacity) { uint8_t* data = (uint8_t*)realloc(mData, desired); if (data) { mData = data; mDataCapacity = desired; } else if (desired > mDataCapacity) { mError = NO_MEMORY; return NO_MEMORY; } } else { if (mDataSize > desired) { mDataSize = desired; ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); } if (mDataPos > desired) { mDataPos = desired; ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos); } } } else {... ... } return NO_ERROR;} |
這裡有關 object 的處理也是先放放,後面再一起說。看後面的,如果需要的空間比原來的大,那麼呼叫 realloc 把空間調整一下。realloc 可以看 man,是說保留原來的記憶體空間,然後嘗試在原來的空間後面擴充套件需要的記憶體空間。然後就是把 mDataPos 和 mDataSize 設定一下。如果是要的空間比原來的小,那就什麼都不幹,就是說用就當成小的用,記憶體還是以前那麼大。
然後回到 IPCThreadState::writeTransactionData 我們看看後面那個 mOut.write:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051 | status_t Parcel::write(const void* data, size_t len){ void* const d = writeInplace(len); if (d) { memcpy(d, data, len); return NO_ERROR; } return mError;}void* Parcel::writeInplace(size_t len){ // 4位元組對齊,看樣子位元組對齊對效率還是有影響的 const size_t padded = PAD_SIZE(len); // sanity check for integer overflow if (mDataPos+padded < mDataPos) { return NULL; } if ((mDataPos+padded) <= mDataCapacity) {restart_write: //printf("Writing %ld bytes, padded to %ld\n", len, padded); uint8_t* const data = mData+mDataPos; // Need to pad at end? if (padded != len) {#if BYTE_ORDER == BIG_ENDIAN static const uint32_t mask[4] = { 0x00000000, 0xffffff00, 0xffff0000, 0xff000000 };#endif #if BYTE_ORDER == LITTLE_ENDIAN static const uint32_t mask[4] = { 0x00000000, 0x00ffffff, 0x0000ffff, 0x000000ff };#endif //printf("Applying pad mask: %p to %p\n", (void*)mask[padded-len], // *reinterpret_cast<void**>(data+padded-4)); *reinterpret_cast<uint32_t*>(data+padded-4) &= mask[padded-len]; } finishWrite(padded); return data; } status_t err = growData(padded); if (err == NO_ERROR) goto restart_write; return NULL;} |
這個函式首先呼叫 writeInplace。來看下 writeInplace,這個函式引數是一個大小,返回是一個地址。進去裡面看下,除去位元組對齊的部分,就是把 mDataPos 和 mDataSize 的值加上了傳過去的 len 大小,然後返回 mData + len 的地址。注意這裡也有空間不夠的情況,和前面的處理一樣,呼叫 growData 去調整空間(parcel 寫的介面基本上都有這個 growData 的處理)。這個函式相當於是幫你把記憶體分配好,然後返回計算好的起始地址給你。然後回到 write 下面直接 memcpy,把傳過來的地址中的資料複製過來。
這2個介面的示例很經典,一個是寫基本型別,一個是寫物件型別的。基本型別可以說是值型別,直接把值寫入記憶體中;物件型別,是把物件的記憶體資料寫進來,這個相當於 c++ 裡面的深拷貝,複製資料。上面 IPCThreadState 寫入的是
binder_transaction_data
這個結構體,後面具體說說 binder 通訊之間的資料格式。現在再來看看 writeString8 這個介面,加深下理解:
123456789101112 | status_t Parcel::writeString8(const String8& str){ status_t err = writeInt32(str.bytes()); // only write string if its length is more than zero characters, // as readString8 will only read if the length field is non-zero. // this is slightly different from how writeString16 works. if (str.bytes() > 0 && err == NO_ERROR) { err = write(str.string(), str.bytes()+1); } return err;} |
String8 是 android 在 native 對 char*
封裝了一下,有點像 java 的 String方便字串操作的。這個也是物件型別,看到 parcel 先是把 String8 的 size 寫進去,然後 write 把
char*
資料寫在 size 後面的記憶體中。我們再來看看對應的 readString8:
1234567891011121314151617181920212223242526272829303132333435363738394041 | String8 Parcel::readString8() const{ int32_t size = readInt32(); // watch for potential int overflow adding 1 for trailing NUL if (size > 0 && size < INT32_MAX) { const char* str = (const char*)readInplace(size+1); if (str) return String8(str, size); } return String8();}status_t Parcel::readInt32(int32_t *pArg) const{ return readAligned(pArg);}template<class T>status_t Parcel::readAligned(T *pArg) const { COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T)); if ((mDataPos+sizeof(T)) <= mDataSize) { const void* data = mData+mDataPos; mDataPos += sizeof(T); *pArg = *reinterpret_cast<const T*>(data); return NO_ERROR; } else { return NOT_ENOUGH_DATA; }}const void* Parcel::readInplace(size_t len) const{ if ((mDataPos+PAD_SIZE(len)) >= mDataPos && (mDataPos+PAD_SIZE(len)) <= mDataSize) { const void* data = mData+mDataPos; mDataPos += PAD_SIZE(len); ALOGV("readInplace Setting data pos of %p to %d\n", this, mDataPos); return data; } return NULL;} |
讀是先 readInt32 把 write 寫入的 size 取出來,readInt32 是呼叫 readAligned 的。和前面 writeAligned 對應,先是判斷下位元組對齊,然後直接取 mData + mDataPos 地址的資料,轉化成模版型別(再次感嘆一次 c、c++ 爽)。注意一下這個函式最後移動了 mDataPos 的位置(對應 read 的資料的大小)。
然後是呼叫 readInplace ,有了前面的說明,你也應該知道這個是去返回對應的 writeInplace 的地址。裡面果然是,同樣注意最後移動了 mDataPos 的位置。然後去返回的地址取之前寫入的
char*
資料,基於上面的資料重新構造出新的 String8 物件。這裡你看出 pacrel 和 Serializable 很像,只不過 parcel 是在記憶體中搗騰,還有後面你會發現 parcel 還為 binder 做了一些別的事情。
還有前面說的 read 的介面回自動移動 mDataPos 的位置(parcel 所有 read 的介面都會自動移動 mDataPos),然後看前面的程式碼,你會發現,write 之後,到 read 的時候,能否取得到正確的資料,依賴於 mDataPos 的位置。這裡就要求 binder 通訊的時候,雙方在用 parcel 讀寫資料的時候順序一定要一致。例如說一個 IPC 呼叫,傳遞一個函式的引數: int、float、object,用 parcel 寫順序是: writeInt32、writeFloat、write,那麼對方接到傳過來的 parcel read 的順序也必須為: readInt32、readFloat、read。就算其中某些引數你不用,你也要 read 一下,主要是要把 mDataPos 的位置移動對。
這裡可以看得出 parcel 只是提供的是一塊連續的記憶體塊,至於往裡面寫什麼東西,格式是怎麼樣的,取決於使用的人,所以使用人要要保證自己讀得正確(要和寫對應),例如前面說的 String8,前面一個 int32 是大小,後面這個大小的是
char*
資料,這個讀的人必須按這個格式才重新創建出 String8。這個我們後面看 binder 中的使用能夠看得出來。
接下來我們看看 binder 怎麼把 parcel 打包的資料傳遞給另外一個程序的。這裡我們結合下通訊模型那分析的東西。首先 Bp 端呼叫 writeTransationData 把 IPC 請求打包傳送到 binder 驅動。前面看到打包的是一個 cmd 和一個
binder_transaction_data
結構。先來看看這個結構(在 kernel binder.h 中):
123456789101112131415161718192021222324252627282930313233 | struct binder_transaction_data { /* The first two are only used for bcTRANSACTION and brTRANSACTION, * identifying the target and contents of the transaction. */ union { size_t handle; /* target descriptor of command transaction */ void *ptr; /* target descriptor of return transaction */ } target; void *cookie; /* target object cookie */ unsigned int code; /* transaction command */ /* General information about the transaction. */ unsigned int flags; pid_t sender_pid; uid_t sender_euid; size_t data_size; /* number of bytes of data */ size_t offsets_size; /* number of bytes of offsets */ /* If this transaction is inline, the data immediately * follows here; otherwise, it ends with a pointer to * the data buffer. */ union { struct { /* transaction data */ const void *buffer; /* offsets from buffer to flat_binder_object structs */ const void *offsets; } ptr; uint8_t buf[8]; } data;}; |
然後我們看看 Bp 端對這個結構體填充了什麼:
1234567891011121314151617181920212223242526272829303132333435 | status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer){ binder_transaction_data tr; tr.target.handle = handle; tr.code = code; tr.flags = binderFlags; tr.cookie = 0; tr.sender_pid = 0; tr.sender_euid = 0; const status_t err = data.errorCheck(); if (err == NO_ERROR) { tr.data_size = data.ipcDataSize(); tr.data.ptr.buffer = data.ipcData(); tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t); tr.data.ptr.offsets = data.ipcObjects(); } else if (statusBuffer) { tr.flags |= TF_STATUS_CODE; *statusBuffer = err; tr.data_size = sizeof(status_t); tr.data.ptr.buffer = statusBuffer; tr.offsets_size = 0; tr.data.ptr.offsets = NULL; } else { return (mLastError = err); } mOut.writeInt32(cmd); mOut.write(&tr, sizeof(tr)); return NO_ERROR;} |
binder_transatcion_data
target 這個 union 先看註釋的說明, target.handle 是說是 IPC 目標的標示,這個 handle 這個東西后面再細說。code 是 IPC 介面定義的介面的標示(例如
START_ACTIVITY
, GET_TASK
之類的玩意)。然後是檢查下 parcel 的錯誤狀態,一般是沒啥錯誤的。然後後面幾個賦值,來看看從 parcel 取出的是什麼東西:
1234567891011121314151617181920 | const uint8_t* Parcel::ipcData() const{ return mData;}size_t Parcel::ipcDataSize() const{ return (mDataSize > mDataPos ? mDataSize : mDataPos);}const size_t* Parcel::ipcObjects() const{ return mObjects;}size_t Parcel::ipcObjectsCount() const{ return mObjectsSize;} |
看註釋 binder_transatcion_data
的 data 這個變數是個 union,遠端傳輸的時候用的是 ptr 這個結構,裡面儲存的是資料的地址。ptr.buffer 是 parcel 的 ipcData() ,這個函式返回的是 mData 就是資料地址。注意一下這裡 data.ptr 儲存的是 IPC Bp 傳入的那個 parcel,不是 IPCThreadState mOut 這個用來打包 binder 資料的 parcel(都是用 parcel 容易搞混)。這裡
data 這個 parcel 是將 IPC 的介面函式的引數資料打包起來的,例如 int、string 之類的引數。Bn 端返回的資料也是通過 parcel 打包的。而 IPCThreadState 的 mOut 只是寫入了 cmd 和
binder_transatcion_data
而已,而 binder_transation_data
儲存了 IPC 中傳遞的真正資料的地址(從引數 parcel 或取的),僅僅是地址而已。所以開頭為什麼 mOut 和 mIn 只把空間大小設定為 256,剛開始以為是因為 parcel 可以動態增長空間,先在看來,其實根本用不了到 256,因為資料大小隻有一個 int32 的 cmd 和
binder_transation_data
這個結構而已。算一下 int32 4位元組,binder_transation_data
第一個 target union 2個都是4位元組的地址,所以就是4位元組,除去後面 data 的 union 其餘的7個都是4位元組的地址,後面那個 data union 算最大的資料,是 4位元組x2,所以
binder_transation_data
結構佔 40位元組,加上 cmd 就是 44位元組。回去通訊模型那看看圖,是不是 Bp 端發
BC_TRANSACTION
write_size 是不是 44。mIn 從 Bn 那讀回來的資料也是差不多的,所以 256 足夠了,基本上不需要動態調整空間的。
好,回到賦值那,看看後面幾個,data_size
是取 mDataSize, mDataPos 比較大的那個(估計是為了保險吧,對於寫 mDataSize 應該等於 mDataPos),然後看看後面的把 parcel 的 mObjectsSize 和 mObjects 分別給了
offset_size
和 ptr.offsets,offset_size
還乘了個地址的大小。前面說過了 parcel 的 mObjects 儲存的是偏移地址,parcel 的名字很奇怪,kernel 裡面的資料結構用名字再次告訴了我們這個是偏移地址。這個到後面就清楚了。
好 mOut 把資料打包好了,到了 waitForResponse 迴圈呼叫 talkWithDriver 向 binder 驅動寫資料,以及等待 Bn 端返回資料(忘記了的回通訊模型看看流程圖)。我們先來看第一次寫通訊命令寫了什麼東西進去:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 | status_t IPCThreadState::talkWithDriver(bool doReceive){ if (mProcess->mDriverFD <= 0) { return -EBADF; } binder_write_read bwr; // Is the read buffer empty? const bool needRead = mIn.dataPosition() >= mIn.dataSize(); // We don't want to write anything if we are still reading // from data left in the input buffer and the caller // has requested to read the next data. const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0; bwr.write_size = outAvail; bwr.write_buffer = (long unsigned int)mOut.data(); // This is what we'll read. if (doReceive && needRead) { bwr.read_size = mIn.dataCapacity(); bwr.read_buffer = (long unsigned int)mIn.data(); } else { bwr.read_size = 0; bwr.read_buffer = 0; }... ... // Return immediately if there is nothing to do. if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR; bwr.write_consumed = 0; bwr.read_consumed = 0; status_t err; do { ... ... if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) err = NO_ERROR; else err = -errno; ... ... } while (err == -EINTR);... ... return err;} |
我們在 kernel 的 binder.h 看到 BINDER_WRITE_READ
的引數是 binder_write_read
這個結構體:
12345678910111213141516 | #define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)/* * On 64-bit platforms where user code may run in 32-bits the driver must * translate the buffer (and local binder) addresses apropriately. */struct binder_write_read { signed long write_size; /* bytes to write */ signed long write_consumed; /* bytes consumed by driver */ unsigned long write_buffer; signed long read_size; /* bytes to read */ signed long read_consumed; /* bytes consumed by driver */ unsigned long read_buffer;}; |
binder_write_read
的結構並不複雜,就是一個數據地址,一個數據大小,一個數據確認處理的大小,分為2部分,write 和 read(看註釋後面要支援 64bit binder 資料傳輸這裡要改不少東西吧)。回來看下賦值。前面那個那個判斷 mIn 中的是否有讀的資料,是通過 mDataPos 的位置來判斷的,就是說如果 mDataPos 的位置比 mDataSize 小,說明還有資料還沒讀完,前面說了 parcel 每呼叫一次 read 介面就會自動移動 mDataPos,如果正好把
read 次數(Bp 端讀)對應上 write 次數(Bn 端寫),那麼 mDataPos 是正好等於 mDataSize 的。後面根據 (!doReceive || needRead) 決定
write_size
的大小,這個後面到 kernel 裡可以知道 size 的大小是否為 0 決定了是否呼叫 binder 驅動的讀寫處理函式。如果 mIn 中還有資料還沒讀取完,needRead 為 true, doReceive 預設是 true(預設要接收 Bn 端返回的資料),所以如果還有 Bn 端發過來的資料還沒讀完,本次迴圈在 binder 驅動中是發不出資料的。這裡開始是能沒有讀資料的,所以能發得出來,write_size
大小是 mOut
parcel 的 mDataSize,write_buffer
是 mOut 的 mData 地址。讀的部分相應的取 mIn 的,這裡給接收的大小也是 256,後面可以看到 Bn 端發過來也是也是
binder_transatcion_data
結構,所以 256 也夠了。然後在 ioctl 前把 consumed 都設定成0。
然後就 ioctl 到 kernel 的 binder 驅動裡面去了,我在 binder 驅動中看看,parcel 是怎麼從 Bp 端傳遞到 Bn 端(或者從 Bn 返回到 Bp)的。首先是上面的 Bp 向 binder 傳送
BC_TRANSACTION
把 binder_transtion_data
的地址儲存到 ioctl 的引數
binder_write_read
中:
1234567891011121314151617181920212223242526272829303132333435363738394041 |