紅茶一杯話Bindercss
(傳輸機制篇_上)html
侯 亮node
咱們先問一個問題,binder機制究竟是如何從代理對象找到其對應的binder實體呢?難道它有某種制導裝置嗎?要回答這個問題,咱們只能靜下心來研究binder驅動的代碼。在本系列文檔的初始篇中,咱們曾經介紹過ProcessState,這個結構是屬於應用層次的東西,僅靠它固然沒法完成精確打擊。其實,在binder驅動層,還有個與之相對的結構,叫作binder_proc。爲了說明問題,我修改了初始篇中的示意圖,獲得下圖:linux
當構造ProcessState並打開binder驅動之時,會調用到驅動層的binder_open()函數,而binder_proc就是在binder_open()函數中建立的。新建立的binder_proc會做爲一個節點,插入一個總鏈表(binder_procs)中。具體代碼可參考kernel/drivers/staging/android/Binder.c。android
驅動層的binder_open()的代碼以下:cookie
static int binder_open(struct inode *nodp, struct file *filp) { struct binder_proc *proc; . . . . . . proc = kzalloc(sizeof(*proc), GFP_KERNEL); get_task_struct(current); proc->tsk = current; . . . . . . hlist_add_head(&proc->proc_node, &binder_procs); proc->pid = current->group_leader->pid; . . . . . . filp->private_data = proc; . . . . . . }
注意,新建立的binder_proc會被記錄在參數filp的private_data域中,之後每次執行binder_ioctl(),都會從filp->private_data域從新讀取binder_proc的。less
binder_procs總表的定義以下:async
static HLIST_HEAD(binder_procs);
咱們能夠在List.h中看到HLIST_HEAD的定義:函數
【kernel/include/linux/List.h】ui
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
因而binder_procs的定義至關於:
struct hlist_head binder_procs = { .first = NULL };
隨着後續不斷向binder_procs表中添加節點,這個表會不斷加長,示意圖以下:
binder_proc裏含有不少重要內容,不過目前咱們只需關心其中的幾個域:
struct binder_proc { struct hlist_node proc_node; struct rb_root threads; struct rb_root nodes; struct rb_root refs_by_desc; struct rb_root refs_by_node; int pid; . . . . . . . . . . . . };
注意其中的那4個rb_root域,「rb」的意思是「red black」,可見binder_proc裏搞出了4個紅黑樹。
其中,nodes樹用於記錄binder實體,refs_by_desc樹和refs_by_node樹則用於記錄binder代理。之因此會有兩個代理樹,是爲了便於快速查找,咱們暫時只關心其中之一就能夠了。threads樹用於記錄執行傳輸動做的線程信息。
在一個進程中,有多少「被其餘進程進行跨進程調用的」binder實體,就會在該進程對應的nodes樹中生成多少個紅黑樹節點。另外一方面,一個進程要訪問多少其餘進程的binder實體,則必須在其refs_by_desc樹中擁有對應的引用節點。
這4棵樹的節點類型是不一樣的,threads樹的節點類型爲binder_thread,nodes樹的節點類型爲binder_node,refs_by_desc樹和refs_by_node樹的節點類型相同,爲binder_ref。這些節點內部都會包含rb_node子結構,該結構專門負責鏈接節點的工做,和前文的hlist_node有點兒殊途同歸,這也是linux上一個經常使用的小技巧。咱們以nodes樹爲例,其示意圖以下:
rb_node和rb_root的定義以下:
struct rb_node { unsigned long rb_parent_color; #define RB_RED 0 #define RB_BLACK 1 struct rb_node *rb_right; struct rb_node *rb_left; } __attribute__((aligned(sizeof(long)))); /* The alignment might seem pointless, but allegedly CRIS needs it */ struct rb_root { struct rb_node *rb_node; };
binder_node的定義以下:
struct binder_node { int debug_id; struct binder_work work; union { struct rb_node rb_node; struct hlist_node dead_node; }; struct binder_proc *proc; struct hlist_head refs; int internal_strong_refs; int local_weak_refs; int local_strong_refs; void __user *ptr; // 注意這個域! void __user *cookie; // 注意這個域! unsigned has_strong_ref:1; unsigned pending_strong_ref:1; unsigned has_weak_ref:1; unsigned pending_weak_ref:1; unsigned has_async_transaction:1; unsigned accept_fds:1; unsigned min_priority:8; struct list_head async_todo; };
咱們前文已經說過,nodes樹是用於記錄binder實體的,因此nodes樹中的每一個binder_node節點,必須可以記錄下相應binder實體的信息。所以請你們注意binder_node的ptr域和cookie域。
另外一方面,refs_by_desc樹和refs_by_node樹的每一個binder_ref節點則和上層的一個BpBinder對應,並且更重要的是,它必須具備和「目標binder實體的binder_node」進行關聯的信息。binder_ref的定義以下:
struct binder_ref { int debug_id; struct rb_node rb_node_desc; struct rb_node rb_node_node; struct hlist_node node_entry; struct binder_proc *proc; struct binder_node *node; // 注意這個node域 uint32_t desc; int strong; int weak; struct binder_ref_death *death; };
請注意那個node域,它負責和binder_node關聯。另外,binder_ref中有兩個類型爲rb_node的域:rb_node_desc域和rb_node_node域,它們分別用於鏈接refs_by_desc樹和refs_by_node。也就是說雖然binder_proc中有兩棵引用樹,但這兩棵樹用到的具體binder_ref節點實際上是複用的。
你們應該還記得,在《初始篇》中我是這樣表達BpBinder和BBinder關係的:
如今,咱們有了binder_ref和binder_node知識,能夠再畫一張圖,來解釋BpBinder究竟是如何和BBinder聯繫上的:
上圖只表示了從進程1向進程2發起跨進程傳輸的意思,其實反過來也是能夠的,即進程2也能夠經過本身的「引用樹」節點找到進程1的「實體樹」節點,並進行跨進程傳輸。你們能夠本身補充上圖。
OK,如今咱們能夠更深刻地說明binder句柄的做用了,好比進程1的BpBinder在發起跨進程調用時,向binder驅動傳入了本身記錄的句柄值,binder驅動就會在「進程1對應的binder_proc結構」的引用樹中查找和句柄值相符的binder_ref節點,一旦找到binder_ref節點,就能夠經過該節點的node域找到對應的binder_node節點,這個目標binder_node固然是從屬於進程2的binder_proc啦,不過沒關係,由於binder_ref和binder_node都處於binder驅動的地址空間中,因此是能夠用指針直接指向的。目標binder_node節點的cookie域,記錄的實際上是進程2中BBinder的地址,binder驅動只需把這個值反映給應用層,應用層就能夠直接拿到BBinder了。這就是Binder完成精確打擊的大致過程。
接下來咱們來談談Binder傳輸機制。
在《初始篇》中,咱們已經提到了BpBinder和ProcessState。當時只是說BpBinder是代理端的核心,主要負責跨進程傳輸,而且不關心所傳輸的內容。而ProcessState則是進程狀態的記錄器,它裏面記錄着打開binder驅動後獲得的句柄值。由於咱們並無進一步展開來討論BpBinder和ProcessState,因此也就沒有進一步打通BpBinder和ProcessState之間的關係。如今,咱們試着補充一些內容。
做爲代理端的核心,BpBinder總要經過某種方式和binder驅動打交道,纔可能完成跨進程傳遞語義的工做。既然binder驅動對應的句柄在ProcessState中記着,那麼如今就要看BpBinder如何和ProcessState聯繫了。此時,咱們須要提到IPCThreadState。
從名字上看,IPCThreadState是「和跨進程通訊(IPC)相關的線程狀態」。那麼很顯然,一個具備多個線程的進程裏應該會有多個IPCThreadState對象了,只不過每一個線程只需一個IPCThreadState對象而已。這有點兒「局部單例」的意思。因此,在實際的代碼中,IPCThreadState對象是存放在線程的局部存儲區(TLS)裏的。
每當咱們利用BpBinder的transact()函數發起一次跨進程事務時,其內部實際上是調用IPCThreadState對象的transact()。BpBinder的transact()代碼以下:
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { // Once a binder has died, it will never come back to life. if (mAlive) { status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags); if (status == DEAD_OBJECT) mAlive = 0; return status; } return DEAD_OBJECT; }
固然,進程中的一個BpBinder有可能被多個線程使用,因此發起傳輸的IPCThreadState對象可能並非同一個對象,但這沒有關係,由於這些IPCThreadState對象最終使用的是同一個ProcessState對象。
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { . . . . . . // 把data數據整理進內部的mOut包中 err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL); . . . . . . if ((flags & TF_ONE_WAY) == 0) { . . . . . . if (reply) { err = waitForResponse(reply); } else { Parcel fakeReply; err = waitForResponse(&fakeReply); } . . . . . . } else { err = waitForResponse(NULL, NULL); } return err; }
IPCThreadState::transact()會先調用writeTransactionData()函數將data數據整理進內部的mOut包中,這個函數的代碼以下:
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; . . . . . . 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(); . . . . . . mOut.writeInt32(cmd); mOut.write(&tr, sizeof(tr)); return NO_ERROR; }
接着IPCThreadState::transact()會考慮本次發起的事務是否須要回覆。「不須要等待回覆的」事務,在其flag標誌中會含有TF_ONE_WAY,表示一去不回頭。而「須要等待回覆的」,則須要在傳遞時提供記錄回覆信息的Parcel對象,通常發起transact()的用戶會提供這個Parcel對象,若是不提供,transact()函數內部會臨時構造一個假的Parcel對象。
上面代碼中,實際完成跨進程事務的是waitForResponse()函數,這個函數的命名不太好,但咱們也沒必要太在乎,反正Android中寫得很差的代碼多了去了,又不僅多這一處。waitForResponse()的代碼截選以下:
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) { int32_t cmd; int32_t err; while (1) { // talkWithDriver()內部會完成跨進程事務 if ((err = talkWithDriver()) < NO_ERROR) break; // 事務的回覆信息被記錄在mIn中,因此須要進一步分析這個回覆 . . . . . . cmd = mIn.readInt32(); . . . . . . switch (cmd) { case BR_TRANSACTION_COMPLETE: if (!reply && !acquireResult) goto finish; break; case BR_DEAD_REPLY: err = DEAD_OBJECT; goto finish; case BR_FAILED_REPLY: err = FAILED_TRANSACTION; goto finish; . . . . . . . . . . . . default: // 注意這個executeCommand()噢,它會處理BR_TRANSACTION的。 err = executeCommand(cmd); if (err != NO_ERROR) goto finish; break; } } finish: . . . . . . return err; }
waitForResponse()中是經過調用talkWithDriver()來和binder驅動打交道的,說到底會調用ioctl()函數。由於ioctl()函數在傳遞BINDER_WRITE_READ語義時,既會使用「輸入buffer」,也會使用「輸出buffer」,因此IPCThreadState專門搞了兩個Parcel類型的成員變量:mIn和mOut。總之就是,mOut中的內容發出去,發送後的回覆寫進mIn。
talkWithDriver()的代碼截選以下:
status_t IPCThreadState::talkWithDriver(bool doReceive) { . . . . . . binder_write_read bwr; . . . . . . bwr.write_size = outAvail; bwr.write_buffer = (long unsigned int)mOut.data(); . . . . . . bwr.read_size = mIn.dataCapacity(); bwr.read_buffer = (long unsigned int)mIn.data(); . . . . . . . . . . . . do { . . . . . . if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) err = NO_ERROR; . . . . . . } while (err == -EINTR); . . . . . . . . . . . . return err; }
看到了嗎?mIn和mOut的data會先整理進一個binder_write_read結構,而後再傳給ioctl()函數。而最關鍵的一句,固然就是那句ioctl()了。此時使用的文件描述符就是前文咱們說的ProcessState中記錄的mDriverFD,說明是向binder驅動傳遞語義。BINDER_WRITE_READ表示咱們但願讀寫一些數據。
至此,應用程序經過BpBinder向遠端發起傳輸的過程就交代完了,數據傳到了binder驅動,一切就看binder驅動怎麼作了。至於驅動層又作了哪些動做,咱們留在下一篇文章再介紹。
如需轉載本文內容,請註明出處。
http://my.oschina.net/youranhongcha/blog/152233
謝謝。