標籤:工作
問題:binder到底是如何從代理對象找到其對應的binder實體的呢?要回答這個就必須要看Binder驅動如何工作的。
1.Binder裝置檔案的開啟
一個進程在使用Binder機制進行通訊之前,必須開啟裝置檔案/dev/binder來擷取一個檔案描述符,然後才能通過這個檔案描述符和Binder驅動進行互動,進程調用open函數開啟/dev/binder的時候,binder_open會被調用,實現如下:
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結構體,接下來就進行proc的初始化工作,並將proc加入全域雙向鏈表binder_procs當中,Binder驅動程式將所有開啟裝置檔案/dev/binder的進程都加入到binder_procs當中,因此,通過遍曆這個鏈表就可以知道系統當前有多少進程在使用Binder處理序間通訊機制。filp->private_data=proc;將新建立的proc儲存在當前檔案的private_data變數中,參數filep指向一個開啟的檔案結構體。進程以後調用binder_ioctl與驅動程式互動式,會將struct file結構體傳過去,從file結構體的private_data欄位就可以得到proc。
1.1 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; . . . . . . . . . . . .};
binder_proc用來描述正在使用binder機制的進程。上面也提到,每當進程開啟/dev/binder時,binder驅動就會為其建立binder_proc
結構體。每一個使用Binder進程通訊機制的進程都有一個binder線程池,用來出來通訊請求,這個Binder線程池是由Binder驅動程式來維護的。成員變數threads是紅/黑樹狀結構節點,它以線程ID作為關鍵字來組織一個進程中的binder線程池,進程可以調用ioctl函數將線程註冊到binder驅動程式當中。
一個進程內部包含了一系列的Binder實體物件和Binder引用對象,進程使用三個紅/黑樹狀結構描述。nodes組織binder實體物件,以binder實體物件的ptr作為關鍵字;refs_by_desc和refs_by_node用來組織Binder引用對象,前者以Binder引用對象的desc作為關鍵字,後者以node作為關鍵字。
有了binder_ref和binder_node知識,我們就可以解釋BpBinder到底如何和BBinder聯絡上的。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完成精確打擊的大體過程。如:
2. BpBinder和IPCThreadState
從名字上看,IPCThreadState是“和跨進程通訊(IPC)相關的線程狀態”。那麼很顯然,一個具有多個線程的進程裡應該會有多個IPCThreadState對象了,只不過每個線程只需一個IPCThreadState對象而已。這有點兒“局部單例”的意思。所以,在實際的代碼中,IPCThreadState對象是存放線上程的局部儲存區(TLS)裡的。
IPCThreadState類中含有一個成員變數mProcess,它指向一個ProcessState對象,從名字上看“進程狀態”就知道它是進程相關,是進程範圍內唯一的。ProcessState負責初始化Binder裝置,即開啟裝置檔案/dev/binder,以及將裝置檔案對應到進程的地址空間。Binder線程中的每個線程都可以通過它來和Binder驅動建立串連。
2.1 BpBinder的transact函數
每當我們利用BpBinder的transact函數發起一次跨進程事物時,其內部都是調用IPCThreadState對象的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對象可能並不是同一個對象,不過沒關係,因為這些ICPThreadState對象可能最終使用同一個ProcessState對象。
2.1.1
調用IPCThreadState的transact
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;}
真正完成跨進程事務的是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; }
2.1.2 talkWithDriver
waitForResponse是通過talkWithDriver來和Binder驅動打交道的,最終會調用ioctl函數,因為ioctl函數在傳遞BINDER_WRITE_READ意義時,使用輸入緩衝區和輸出緩衝區,所以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會封裝成binder_write_read結構,然後在傳給ioctl函數。最關鍵的當然是ioctl了,此時使用的檔案描述符就是ProcessState中記錄的mDriverFD,說明是想binder驅動傳遞語義,至此應用程式通過BpBinder向遠端傳輸的過程就清楚了,資料傳送到Binder驅動,就看binder驅動做哪些動作,留到中篇解析。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
binder機制(上篇)