版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
AndroidBinder設(shè)計與實現(xiàn)–設(shè)計篇摘要Binder是Android系統(tǒng)進程間通信(IPC)方式之一。Linux已經(jīng)擁有管道、systemVIPC、socket等IPC手段,卻還要倚賴Binder來實現(xiàn)進程間通信,說明Binder具有無可比擬的優(yōu)勢。深入了解Binder并將之與傳統(tǒng)IPC做對比有助于我們深入領(lǐng)會進程間通信的實現(xiàn)和性能優(yōu)化。本文將對Binder的設(shè)計細(xì)節(jié)做一個全面的闡述,首先通過介紹Binder通信模型和Binder通信協(xié)議了解Binder的設(shè)計需求;然后分別闡述Binder在系統(tǒng)不同部分的表述方式和起的作用;最后還會解釋Binder在數(shù)據(jù)接收端的設(shè)計考慮,包括線程池管理,內(nèi)存映射和等待隊列管理等。通過本文對Binder的詳細(xì)介紹以及與其它IPC通信方式的對比,讀者將對Binder的優(yōu)勢和使用Binder作為Android主要IPC方式的原因有深入了解。1.引言基于Client-Server的通信方式廣泛應(yīng)用于從互聯(lián)網(wǎng)和數(shù)據(jù)庫訪問到嵌入式手持設(shè)備內(nèi)部通信等各個領(lǐng)域。智能手機平臺特別是Android系統(tǒng)中,為了向應(yīng)用開發(fā)者提供豐富多樣的功能,這種通信方式更是無處不在,諸如媒體播放,視音頻捕獲,到各種讓手機更智能的傳感器(加速度、方位、溫度、光亮度等)都由不同的Server負(fù)責(zé)管理,應(yīng)用程序只需作為Client與這些Server建立連接便可以使用這些服務(wù),花很少的時間和精力就能開發(fā)出令人眩目的功能。Client-Server方式的廣泛采用對進程間通信(IPC)機制是一個挑戰(zhàn)。目前l(fā)inux支持的IPC包括傳統(tǒng)的管道、SystemVIPC、即消息隊列/共享內(nèi)存/信號量,以及socket中只有socket支持Client-Server的通信方式。當(dāng)然也可以在這些底層機制上架設(shè)一套協(xié)議來實現(xiàn)Client-Server通信,但這樣增加了系統(tǒng)的復(fù)雜性,在手機這種條件復(fù)雜,資源稀缺的環(huán)境下可靠性也難以保證。另一方面是傳輸性能。socket作為一款通用接口,其傳輸效率低,開銷大,主要用在跨網(wǎng)絡(luò)的進程間通信和本機上進程間的低速通信。消息隊列和管道采用存儲-轉(zhuǎn)發(fā)方式,即數(shù)據(jù)先從發(fā)送方緩存區(qū)拷貝到內(nèi)核開辟的緩存區(qū)中,然后再從內(nèi)核緩存區(qū)拷貝到接收方緩存區(qū),至少有兩次拷貝過程。共享內(nèi)存雖然無需拷貝,但控制復(fù)雜,難以使用。表1各種IPC方式數(shù)據(jù)拷貝次數(shù)IPC數(shù)據(jù)拷貝次數(shù)共享內(nèi)存0Binder1Socket/管道/消息隊列2還有一點是出于安全性考慮。Android作為一個開放式,擁有眾多開發(fā)者的平臺,應(yīng)用程序的來源廣泛,確保智能終端的安全是非常重要的。終端用戶不希望從網(wǎng)上下載的程序在不知情的情況下偷窺隱私數(shù)據(jù),連接無線網(wǎng)絡(luò),長期操作底層設(shè)備導(dǎo)致電池很快耗盡等等。傳統(tǒng)IPC沒有任何安全措施,完全依賴上層協(xié)議來確保。首先傳統(tǒng)IPC的接收方無法獲得對方進程可靠的UID/PID(用戶ID/進程ID),從而無法鑒別對方身份。Android為每個安裝好的應(yīng)用程序分配了自己的UID,故進程的UID是鑒別進程身份的重要標(biāo)志。使用傳統(tǒng)IPC只能由用戶在數(shù)據(jù)包里填入UID/PID,但這樣不可靠,容易被惡意程序利用??煽康纳矸輼?biāo)記只有由IPC機制本身在內(nèi)核中添加。其次傳統(tǒng)IPC訪問接入點是開放的,無法建立私有通道。比如命名管道的名稱、systemV的鍵值、socket的ip地址或文件名都是開放的,只要知道這些接入點的程序都可以和對端建立連接,不管怎樣都無法阻止惡意程序通過猜測接收方地址獲得連接?;谝陨显颍珹ndroid需要建立一套新的IPC機制來滿足系統(tǒng)對通信方式,傳輸性能和安全性的要求,這就是Binder。Binder基于Client-Server通信模式,傳輸過程只需一次拷貝,為發(fā)送發(fā)添加UID/PID身份,既支持實名Binder也支持匿名Binder,安全性高。2.面向?qū)ο蟮腂inderIPCBinder使用Client-Server通信方式:一個進程作為Server提供諸如視頻/音頻解碼,視頻捕獲,地址本查詢,網(wǎng)絡(luò)連接等服務(wù);多個進程作為Client向Server發(fā)起服務(wù)請求,獲得所需要的服務(wù)。要想實現(xiàn)Client-Server通信必須實現(xiàn)以下兩點:一是server必須有確定的訪問接入點或者說地址來接受Client的請求,并且Client可以通過某種途徑獲知Server的地址;二是制定Command-Reply協(xié)議來傳輸數(shù)據(jù)。例如在網(wǎng)絡(luò)通信中Server的訪問接入點就是Server主機的IP地址+端口號,傳輸協(xié)議為TCP協(xié)議。對Binder而言,Binder可以看成Server提供的實現(xiàn)某個特定服務(wù)的訪問接入點,Client通過這個‘地址’向Server發(fā)送請求來使用該服務(wù);對Client而言,Binder可以看成是通向Server的管道入口,要想和某個Server通信首先必須建立這個管道并獲得管道入口。與其它IPC不同,Binder使用了面向?qū)ο蟮乃枷雭砻枋鲎鳛樵L問接入點的Binder及其在Client中的入口:Binder是一個實體位于Server中的對象,該對象提供了一套方法用以實現(xiàn)對服務(wù)的請求,就像類的成員函數(shù)。遍布于client中的入口可以看成指向這個binder對象的‘指針’,一旦獲得了這個‘指針’就可以調(diào)用該對象的方法訪問server。在Client看來,通過Binder‘指針’調(diào)用其提供的方法和通過指針調(diào)用其它任何本地對象的方法并無區(qū)別,盡管前者的實體位于遠(yuǎn)端Server中,而后者實體位于本地內(nèi)存中?!羔槨荂++的術(shù)語,而更通常的說法是引用,即Client通過Binder的引用訪問Server。而軟件領(lǐng)域另一個術(shù)語‘句柄’也可以用來表述Binder在Client中的存在方式。從通信的角度看,Client中的Binder也可以看作是ServerBinder的‘代理’,在本地代表遠(yuǎn)端Server為Client提供服務(wù)。本文中會使用‘引用’或‘句柄’這個兩個廣泛使用的術(shù)語。面向?qū)ο笏枷氲囊雽⑦M程間通信轉(zhuǎn)化為通過對某個Binder對象的引用調(diào)用該對象的方法,而其獨特之處在于Binder對象是一個可以跨進程引用的對象,它的實體位于一個進程中,而它的引用卻遍布于系統(tǒng)的各個進程之中。最誘人的是,這個引用和java里引用一樣既可以是強類型,也可以是弱類型,而且可以從一個進程傳給其它進程,讓大家都能訪問同一Server,就像將一個對象或引用賦值給另一個引用一樣。Binder模糊了進程邊界,淡化了進程間通信過程,整個系統(tǒng)仿佛運行于同一個面向?qū)ο蟮某绦蛑?。形形色色的Binder對象以及星羅棋布的引用仿佛粘接各個應(yīng)用程序的膠水,這也是Binder在英文里的原意。當(dāng)然面向?qū)ο笾皇轻槍?yīng)用程序而言,對于Binder驅(qū)動和內(nèi)核其它模塊一樣使用C語言實現(xiàn),沒有類和對象的概念。Binder驅(qū)動為面向?qū)ο蟮倪M程間通信提供底層支持。3.Binder通信模型Binder框架定義了四個角色:Server,Client,ServiceManager(以后簡稱SMgr)以及驅(qū)動。其中Server,Client,SMgr運行于用戶空間,驅(qū)動運行于內(nèi)核空間。這四個角色的關(guān)系和互聯(lián)網(wǎng)類似:Server是服務(wù)器,Client是客戶終端,SMgr是域名服務(wù)器(DNS),驅(qū)動是路由器。3.1Binder驅(qū)動和路由器一樣,Binder驅(qū)動雖然默默無聞,卻是通信的核心。盡管名叫“驅(qū)動”,實際上和硬件設(shè)備沒有任何關(guān)系,只是實現(xiàn)方式和設(shè)備驅(qū)動程序是一樣的:它工作于內(nèi)核態(tài),提供open(),mmap(),poll(),ioctl()等標(biāo)準(zhǔn)文件操作,以字符驅(qū)動設(shè)備中的misc設(shè)備注冊在設(shè)備目錄/dev下,用戶通過/dev/binder訪問該它。驅(qū)動負(fù)責(zé)進程之間Binder通信的建立,Binder在進程之間的傳遞,Binder引用計數(shù)管理,數(shù)據(jù)包在進程之間的傳遞和交互等一系列底層支持。驅(qū)動和應(yīng)用程序之間定義了一套接口協(xié)議,主要功能由ioctl()接口實現(xiàn),不提供read(),write()接口,因為ioctl()靈活方便,且能夠一次調(diào)用實現(xiàn)先寫后讀以滿足同步交互,而不必分別調(diào)用write()和read()。3.2ServiceManager與實名Binder和DNS類似,SMgr的作用是將字符形式的Binder名字轉(zhuǎn)化成Client中對該Binder的引用,使得Client能夠通過Binder名字獲得對Server中Binder實體的引用。注冊了名字的Binder叫實名Binder,就像每個網(wǎng)站除了有IP地址外都有自己的網(wǎng)址。Server創(chuàng)建了Binder實體,為其取一個字符形式,可讀易記的名字,將這個Binder連同名字以數(shù)據(jù)包的形式通過Binder驅(qū)動發(fā)送給SMgr,通知SMgr注冊一個名叫張三的Binder,它位于某個Server中。驅(qū)動為這個穿過進程邊界的Binder創(chuàng)建位于內(nèi)核中的實體節(jié)點以及SMgr對實體的引用,將名字及新建的引用傳遞給SMgr。SMgr收數(shù)據(jù)包后,從中取出名字和引用填入一張查找表中。細(xì)心的讀者可能會發(fā)現(xiàn)其中的蹊蹺:SMgr是一個進程,Server是另一個進程,Server向SMgr注冊Binder必然會涉及進程間通信。當(dāng)前實現(xiàn)的是進程間通信卻又要用到進程間通信,這就好像蛋可以孵出雞前提卻是要找只雞來孵蛋。Binder的實現(xiàn)比較巧妙:預(yù)先創(chuàng)造一只雞來孵蛋。SMgr和其它進程同樣采用Binder通信,SMgr是Server端,有自己的Binder實體,其它進程都是Client,需要通過這個Binder的引用來實現(xiàn)Binder的注冊,查詢和獲取。SMgr提供的Binder比較特殊,它沒有名字也不需要注冊,當(dāng)一個進程使用BINDER_SET_CONTEXT_MGR命令將自己注冊成SMgr時Binder驅(qū)動會自動為它創(chuàng)建Binder實體(這就是那只預(yù)先造好的雞)。其次這個Binder的引用在所有Client中都固定為0而無須通過其它手段獲得。也就是說,一個Server若要向SMgr注冊自己Binder就必需通過0這個引用和SMgr的Binder通信。類比網(wǎng)絡(luò)通信,0號引用就好比域名服務(wù)器的地址,你必須手工或動態(tài)配置好。要注意這里說的Client是相對SMgr而言的,一個應(yīng)用程序是個提供服務(wù)的Server,但對SMgr來說它仍然是個Client。3.3Client獲得實名Binder的引用Server向SMgr注冊了Binder實體及其名字后,Client就可以通過名字獲得該Binder的引用了。Client也利用保留的0號引用向SMgr請求訪問某個Binder:我申請獲得名字叫張三的Binder的引用。SMgr收到這個連接請求,從請求數(shù)據(jù)包里獲得Binder的名字,在查找表里找到該名字對應(yīng)的條目,從條目中取出Binder的引用,將該引用作為回復(fù)發(fā)送給發(fā)起請求的Client。從面向?qū)ο蟮慕嵌?,這個Binder對象現(xiàn)在有了兩個引用:一個位于SMgr中,一個位于發(fā)起請求的Client中。如果接下來有更多的Client請求該Binder,系統(tǒng)中就會有更多的引用指向該Binder,就象java里一個對象存在多個引用一樣。而且類似的這些指向Binder的引用是強類型,從而確保只要有引用Binder實體就不會被釋放掉。通過以上過程可以看出,SMgr象個火車票代售點,收集了所有火車的車票,可以通過它購買到乘坐各趟火車的票,即得到某個Binder的引用。3.4匿名Binder并不是所有Binder都需要注冊給SMgr廣而告之的。Server端可以通過已經(jīng)建立的Binder連接將創(chuàng)建的Binder實體傳給Client,當(dāng)然這條已經(jīng)建立的Binder連接必須是通過實名Binder實現(xiàn)。由于這個Binder沒有向SMgr注冊名字,所以是個匿名Binder。Client將會收到這個匿名Binder的引用,通過這個引用向位于Server中的實體發(fā)送請求。匿名Binder為通信雙方建立一條私密通道,只要Server沒有把匿名Binder發(fā)給別的進程,別的進程就無法通過窮舉或猜測等任何方式獲得該Binder的引用,向該Binder發(fā)送請求。下圖展示了參與Binder通信的所有角色,將在以后章節(jié)中一一提到。圖1Binder通信示例4.Binder協(xié)議Binder協(xié)議基本格式是(命令+數(shù)據(jù)),使用ioctl(fd,cmd,arg)函數(shù)實現(xiàn)交互。命令由參數(shù)cmd承載,數(shù)據(jù)由參數(shù)arg承載,隨cmd不同而不同。下表列舉了所有命令及其所對應(yīng)的數(shù)據(jù):表2Binder通信命令字命令含義argBINDER_WRITE_READ該命令向Binder寫入或讀取數(shù)據(jù)。參數(shù)分為兩段:寫部分和讀部分。如果write_size不為0就先將write_buffer里的數(shù)據(jù)寫入Binder;如果read_size不為0再從Binder中讀取數(shù)據(jù)存入read_buffer中。write_consumed和read_consumed表示操作完成時Binder驅(qū)動實際寫入或讀出的數(shù)據(jù)個數(shù)。structbinder_write_read{signedlongwrite_size;signedlongwrite_consumed;unsignedlongwrite_buffer;signedlongread_size;signedlongread_consumed;unsignedlongread_buffer;};BINDER_SET_MAX_THREADS該命令告知Binder驅(qū)動接收方(通常是Server端)線程池中最大的線程數(shù)。由于Client是并發(fā)向Server端發(fā)送請求的,Server端必須開辟線程池為這些并發(fā)請求提供服務(wù)。告知驅(qū)動線程池的最大值是為了讓驅(qū)動在線程達(dá)到該值時不要再命令接收端啟動新的線程。intmax_threads;BINDER_SET_CONTEXT_MGR將當(dāng)前進程注冊為SMgr。系統(tǒng)中同時只能存在一個SMgr。只要當(dāng)前的SMgr沒有調(diào)用close()關(guān)閉Binder驅(qū)動就不能有別的進程可以成為SMgr?!狟INDER_THREAD_EXIT通知Binder驅(qū)動當(dāng)前線程退出了。Binder會為所有參與Binder通信的線程(包括Server線程池中的線程和Client發(fā)出請求的線程)建立相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。這些線程在退出時必須通知驅(qū)動釋放相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。—BINDER_VERSION獲得Binder驅(qū)動的版本號。—這其中最常用的命令是BINDER_WRITE_READ。該命令的參數(shù)包括兩部分?jǐn)?shù)據(jù):一部分是向Binder寫入的數(shù)據(jù),一部分是要從Binder讀出的數(shù)據(jù),驅(qū)動程序先處理寫部分再處理讀部分。這樣安排的好處是應(yīng)用程序可以很靈活地處理命令的同步或異步。例如若要發(fā)送異步命令可以只填入寫部分而將read_size置成0;若要只從Binder獲得數(shù)據(jù)可以將寫部分置空即write_size置成0;若要發(fā)送請求并同步等待返回數(shù)據(jù)可以將兩部分都置上。4.1BINDER_WRITE_READ之寫操作Binder寫操作的數(shù)據(jù)時格式同樣也是(命令+數(shù)據(jù))。這時候命令和數(shù)據(jù)都存放在binder_write_read結(jié)構(gòu)write_buffer域指向的內(nèi)存空間里,多條命令可以連續(xù)存放。數(shù)據(jù)緊接著存放在命令后面,格式根據(jù)命令不同而不同。下表列舉了Binder寫操作支持的命令:表3Binder寫操作命令字cmd含義argBC_TRANSACTIONBC_REPLYBC_REPLYBC_TRANSACTION用于寫入請求數(shù)據(jù);BC_REPLY用于寫入回復(fù)數(shù)據(jù)。其后面緊接著一個binder_transaction_data結(jié)構(gòu)體表明要寫入的數(shù)據(jù)。structbinder_transaction_dataBC_ACQUIRE_RESULTBC_ATTEMPT_ACQUIRE暫未實現(xiàn)—BC_FREE_BUFFER釋放一塊映射的內(nèi)存。Binder接收方通過mmap()映射一塊較大的內(nèi)存空間,Binder驅(qū)動基于這片內(nèi)存采用最佳匹配算法實現(xiàn)接收數(shù)據(jù)緩存的動態(tài)分配和釋放,滿足并發(fā)請求對接收緩存區(qū)的需求。應(yīng)用程序處理完這片數(shù)據(jù)后必須盡快使用該命令釋放緩存區(qū),否則會因為緩存區(qū)耗盡而無法接收新數(shù)據(jù)。指向需要釋放的緩存區(qū)的指針;該指針位于收到的Binder數(shù)據(jù)包中BC_INCREFSBC_ACQUIREBC_RELEASE這組命令增加或減少Binder的引用計數(shù),用以實現(xiàn)強指針或弱指針的功能。32位Binder引用號BC_INCREFS_DONEBC_ACQUIRE_DONE第一次增加Binder實體引用計數(shù)時,驅(qū)動向Binder實體所在的進程發(fā)送BR_INCREFS,BR_ACQUIRE消息;Binder實體所在的進程處理完畢回饋BC_INCREFS_DONE,BC_ACQUIRE_DONEvoid*ptr:Binder實體在用戶空間中的指針void*cookie:與該實體相關(guān)的附加數(shù)據(jù)BC_REGISTER_LOOPERBC_ENTER_LOOPERBC_EXIT_LOOPER這組命令同BINDER_SET_MAX_THREADS一道實現(xiàn)Binder驅(qū)動對接收方線程池管理。BC_REGISTER_LOOPER通知驅(qū)動線程池中一個線程已經(jīng)創(chuàng)建了;BC_ENTER_LOOPER通知驅(qū)動該線程已經(jīng)進入主循環(huán),可以接收數(shù)據(jù);BC_EXIT_LOOPER通知驅(qū)動該線程退出主循環(huán),不再接收數(shù)據(jù)?!狟C_REQUEST_DEATH_NOTIFICATION獲得Binder引用的進程通過該命令要求驅(qū)動在Binder實體銷毀得到通知。雖說強指針可以確保只要有引用就不會銷毀實體,但這畢竟是個跨進程的引用,誰也無法保證實體由于所在的Server關(guān)閉Binder驅(qū)動或異常退出而消失,引用者能做的是要求Server在此刻給出通知。uint32*ptr;需要得到死亡通知的Binder引用void**cookie:與死亡通知相關(guān)的信息,驅(qū)動會在發(fā)出死亡通知時返回給發(fā)出請求的進程。BC_DEAD_BINDER_DONE收到實體死亡通知書的進程在刪除引用后用本命令告知驅(qū)動。void**cookie在這些命令中,最常用的是BC_TRANSACTION/BC_REPLY命令對,Binder數(shù)據(jù)通過這對命令發(fā)送給接收方。這對命令所承載的數(shù)據(jù)包由結(jié)構(gòu)體structbinder_transaction_data定義。Binder交互有同步和異步之分,利用binder_transaction_data中flag域區(qū)分。如果flag域的TF_ONE_WAY位為1則為異步交互,即Client端發(fā)送完請求交互即結(jié)束,Server端不再返回BC_REPLY數(shù)據(jù)包;否則Server會返回BC_REPLY數(shù)據(jù)包,Client端必須等待接收完該數(shù)據(jù)包方才完成一次交互。4.2BINDER_WRITE_READ:從Binder讀出數(shù)據(jù)從Binder里讀出的數(shù)據(jù)格式和向Binder中寫入的數(shù)據(jù)格式一樣,采用(消息ID+數(shù)據(jù))形式,并且多條消息可以連續(xù)存放。下表列舉了從Binder讀出的命令字及其相應(yīng)的參數(shù):表4Binder讀操作消息ID消息含義參數(shù)BR_ERROR發(fā)生內(nèi)部錯誤(如內(nèi)存分配失?。狟R_OKBR_NOOP操作完成—BR_SPAWN_LOOPER該消息用于接收方線程池管理。當(dāng)驅(qū)動發(fā)現(xiàn)接收方所有線程都處于忙碌狀態(tài)且線程池里的線程總數(shù)沒有超過BINDER_SET_MAX_THREADS設(shè)置的最大線程數(shù)時,向接收方發(fā)送該命令要求創(chuàng)建更多線程以備接收數(shù)據(jù)?!狟R_TRANSACTIONBR_REPLY這兩條消息分別對應(yīng)發(fā)送方的BC_TRANSACTION和BC_REPLY,表示當(dāng)前接收的數(shù)據(jù)是請求或是回復(fù)。binder_transaction_dataBR_ACQUIRE_RESULTBR_ATTEMPT_ACQUIREBR_FINISHED尚未實現(xiàn)—BR_DEAD_REPLY交互過程中如果發(fā)現(xiàn)對方進程或線程已經(jīng)死亡則返回該消息—BR_TRANSACTION_COMPLETE發(fā)送方通過BC_TRANSACTION或BC_REPLY發(fā)送完一個數(shù)據(jù)包后,都能收到該消息做為成功發(fā)送的反饋。這和BR_REPLY不一樣,是驅(qū)動告知發(fā)送方已經(jīng)發(fā)送成功,而不是接收方返回請求數(shù)據(jù)。所以不管同步還是異步交互接收方都能獲得本消息?!狟R_INCREFSBR_ACQUIREBR_RELEASEBR_DECREFS這一組消息用于管理強/弱指針的引用計數(shù)。只有提供Binder實體的進程才能收到這組消息。void*ptr:Binder實體在用戶空間中的指針void*cookie:與該實體相關(guān)的附加數(shù)據(jù)BR_DEAD_BINDERBR_CLEAR_DEATH_NOTIFICATION_DONE向獲得Binder引用的進程發(fā)送Binder實體死亡通知書;收到死亡通知書的進程接下來會返回BC_DEAD_BINDER_DONE做確認(rèn)。void**cookie:在使用BC_REQUEST_DEATH_NOTIFICATION注冊死亡通知時的附加參數(shù)。BR_FAILED_REPLY如果發(fā)送非法引用號則返回該消息—和寫數(shù)據(jù)一樣,其中最重要的消息是BR_TRANSACTION或BR_REPLY,表明收到了一個格式為binder_transaction_data的請求數(shù)據(jù)包(BR_TRANSACTION)或返回數(shù)據(jù)包(BR_REPLY)。4.3structbinder_transaction_data:收發(fā)數(shù)據(jù)包結(jié)構(gòu)該結(jié)構(gòu)是Binder接收/發(fā)送數(shù)據(jù)包的標(biāo)準(zhǔn)格式,每個成員定義如下:表5Binder收發(fā)數(shù)據(jù)包結(jié)構(gòu):binder_transaction_data成員含義union{size_thandle;void*ptr;}target;對于發(fā)送數(shù)據(jù)包的一方,該成員指明發(fā)送目的地。由于目的是在遠(yuǎn)端,所以這里填入的是對Binder實體的引用,存放在target.handle中。如前述,Binder的引用在代碼中也叫句柄(handle)。當(dāng)數(shù)據(jù)包到達(dá)接收方時,驅(qū)動已將該成員修改成Binder實體,即指向Binder對象內(nèi)存的指針,使用target.ptr來獲得。該指針是接收方在將Binder實體傳輸給其它進程時提交給驅(qū)動的,驅(qū)動程序能夠自動將發(fā)送方填入的引用轉(zhuǎn)換成接收方Binder對象的指針,故接收方可以直接將其當(dāng)做對象指針來使用(通常是將其reinterpret_cast成相應(yīng)類)。void*cookie;發(fā)送方忽略該成員;接收方收到數(shù)據(jù)包時,該成員存放的是創(chuàng)建Binder實體時由該接收方自定義的任意數(shù)值,做為與Binder指針相關(guān)的額外信息存放在驅(qū)動中。驅(qū)動基本上不關(guān)心該成員。unsignedintcode;該成員存放收發(fā)雙方約定的命令碼,驅(qū)動完全不關(guān)心該成員的內(nèi)容。通常是Server端定義的公共接口函數(shù)的編號。unsignedintflags;與交互相關(guān)的標(biāo)志位,其中最重要的是TF_ONE_WAY位。如果該位置上表明這次交互是異步的,接收方不會返回任何數(shù)據(jù)。驅(qū)動利用該位來決定是否構(gòu)建與返回有關(guān)的數(shù)據(jù)結(jié)構(gòu)。另外一位TF_ACCEPT_FDS是出于安全考慮,如果發(fā)起請求的一方不希望在收到的回復(fù)中接收文件形式的Binder可以將該位置上。因為收到一個文件形式的Binder會自動為接收方打開一個文件,使用該位可以防止打開文件過多。pid_tsender_pid;uid_tsender_euid;該成員存放發(fā)送方的進程ID和用戶ID,由驅(qū)動負(fù)責(zé)填入,接收方可以讀取該成員獲知發(fā)送方的身份。size_tdata_size;該成員表示data.buffer指向的緩沖區(qū)存放的數(shù)據(jù)長度。發(fā)送數(shù)據(jù)時由發(fā)送方填入,表示即將發(fā)送的數(shù)據(jù)長度;在接收方用來告知接收到數(shù)據(jù)的長度。size_toffsets_size;驅(qū)動一般情況下不關(guān)心data.buffer里存放什么數(shù)據(jù),但如果有Binder在其中傳輸則需要將其相對data.buffer的偏移位置指出來讓驅(qū)動知道。有可能存在多個Binder同時在數(shù)據(jù)中傳遞,所以須用數(shù)組表示所有偏移位置。本成員表示該數(shù)組的大小。union{struct{constvoid*buffer;constvoid*offsets;}ptr;uint8_tbuf[8];}data;data.bufer存放要發(fā)送或接收到的數(shù)據(jù);data.offsets指向Binder偏移位置數(shù)組,該數(shù)組可以位于data.buffer中,也可以在另外的內(nèi)存空間中,并無限制。buf[8]是為了無論保證32位還是64位平臺,成員data的大小都是8個字節(jié)。這里有必要再強調(diào)一下offsets_size和data.offsets兩個成員,這是Binder通信有別于其它IPC的地方。如前述,Binder采用面向?qū)ο蟮脑O(shè)計思想,一個Binder實體可以發(fā)送給其它進程從而建立許多跨進程的引用;另外這些引用也可以在進程之間傳遞,就象java里將一個引用賦給另一個引用一樣。為Binder在不同進程中建立引用必須有驅(qū)動的參與,由驅(qū)動在內(nèi)核創(chuàng)建并注冊相關(guān)的數(shù)據(jù)結(jié)構(gòu)后接收方才能使用該引用。而且這些引用可以是強類型,需要驅(qū)動為其維護引用計數(shù)。然而這些跨進程傳遞的Binder混雜在引用程序發(fā)送的數(shù)據(jù)包里,數(shù)據(jù)格式完全由用戶定義,如果不把它們一一標(biāo)記出來告知驅(qū)動,驅(qū)動將無法從數(shù)據(jù)中將它們提取出來。于是就使用數(shù)組data.offsets存放用戶數(shù)據(jù)中每個Binder相對data.buffer的偏移量,用offsets_size表示這個數(shù)組的大小。驅(qū)動在發(fā)送數(shù)據(jù)包時會根據(jù)data.offsets和offset_size將散落于data.buffer中的Binder找出來并一一為它們創(chuàng)建相關(guān)的數(shù)據(jù)結(jié)構(gòu)。在數(shù)據(jù)包中傳輸?shù)腂inder是類型為structflat_binder_object的結(jié)構(gòu)體,詳見后文。對于接收方來說,該結(jié)構(gòu)只相當(dāng)于一個定長的消息頭,真正的用戶數(shù)據(jù)存放在data.buffer所指向的緩存區(qū)中。如果發(fā)送方在數(shù)據(jù)中內(nèi)嵌了一個或多個Binder,接收到的數(shù)據(jù)包中同樣會用data.offsets和offset_size指出每個Binder的位置和總個數(shù)。不過通常接收方可以忽略這些信息,因為接收方是知道數(shù)據(jù)格式的,參考雙方約定的格式定義就能知道這些Binder在什么位置。圖2BINDER_WRITE_READ數(shù)據(jù)包實例5.Binder的表述考察一次Binder通信的全過程會發(fā)現(xiàn),Binder存在于系統(tǒng)以下幾個部分中:·應(yīng)用程序進程:又分為Server進程和Client進程·Binder驅(qū)動:Server和Client有不同表述形式·傳輸數(shù)據(jù):由于Binder可以跨進程傳遞,需要在傳輸數(shù)據(jù)中予以表述在系統(tǒng)不同部分,Binder實現(xiàn)的功能不同,表現(xiàn)形式也不一樣的。接下來逐一探討B(tài)inder在各部分所扮演的角色和使用的數(shù)據(jù)結(jié)構(gòu)。5.1Binder在應(yīng)用程序中的表述雖然Binder用到了面向?qū)ο蟮乃枷?,但并不限制?yīng)用程序一定要使用面向?qū)ο蟮恼Z言,無論是C語言還是C++語言都可以很容易的使用Binder來通信。例如盡管Android主要使用java或C++,象SMgr這么重要的進程就是用C語言實現(xiàn)的。不過面向?qū)ο蟮姆绞奖硎銎饋砀奖悖员疚募僭O(shè)應(yīng)用程序是用面向?qū)ο笳Z言實現(xiàn)的。Binder本質(zhì)上只是一種底層通信方式,和具體服務(wù)沒有關(guān)系。為了提供具體服務(wù),Server必須提供一套接口函數(shù)以便Client通過遠(yuǎn)程訪問使用各種服務(wù)。這時通常采用Proxy設(shè)計模式:將接口函數(shù)定義在一個抽象類中,Server和Client都會以該抽象類為基類實現(xiàn)所有接口函數(shù),所不同的是Server端是真正的功能實現(xiàn),而Client端是對這些函數(shù)遠(yuǎn)程調(diào)用請求的包裝。如何將Binder和Proxy設(shè)計模式結(jié)合起來是應(yīng)用程序?qū)崿F(xiàn)面向?qū)ο驜inder通信的根本問題。5.1.1Binder在Server端的表述–Binder實體做為Proxy設(shè)計模式的基礎(chǔ),首先定義一個抽象接口類封裝Server所有功能,其中包含一系列純虛函數(shù)留待Server和Proxy各自實現(xiàn)。由于這些函數(shù)需要跨進程調(diào)用,須為其一一編號,從而Server可以根據(jù)收到的編號決定調(diào)用哪個函數(shù)。其次就要引入Binder了。Server端定義另一個Binder抽象類處理來自Client的Binder請求數(shù)據(jù)包,其中最重要的成員是虛函數(shù)onTransact()。該函數(shù)分析收到的數(shù)據(jù)包,調(diào)用相應(yīng)的接口函數(shù)處理請求。接下來采用繼承方式以接口類和Binder抽象類為基類構(gòu)建Binder在Server中的實體,實現(xiàn)基類里所有的虛函數(shù),包括公共接口函數(shù)以及數(shù)據(jù)包處理函數(shù):onTransact()。這個函數(shù)的輸入是來自Client的binder_transaction_data結(jié)構(gòu)的數(shù)據(jù)包。前面提到,該結(jié)構(gòu)里有個成員code,包含這次請求的接口函數(shù)編號。onTransact()將case-by-case地解析code值,從數(shù)據(jù)包里取出函數(shù)參數(shù),調(diào)用接口類中相應(yīng)的,已經(jīng)實現(xiàn)的公共接口函數(shù)。函數(shù)執(zhí)行完畢,如果需要返回數(shù)據(jù)就再構(gòu)建一個binder_transaction_data包將返回數(shù)據(jù)包填入其中。那么各個Binder實體的onTransact()又是什么時候調(diào)用呢?這就需要驅(qū)動參與了。前面說過,Binder實體須要以Binde傳輸結(jié)構(gòu)flat_binder_object形式發(fā)送給其它進程才能建立Binder通信,而Binder實體指針就存放在該結(jié)構(gòu)的handle域中。驅(qū)動根據(jù)Binder位置數(shù)組從傳輸數(shù)據(jù)中獲取該Binder的傳輸結(jié)構(gòu),為它創(chuàng)建位于內(nèi)核中的Binder節(jié)點,將Binder實體指針記錄在該節(jié)點中。如果接下來有其它進程向該Binder發(fā)送數(shù)據(jù),驅(qū)動會根據(jù)節(jié)點中記錄的信息將Binder實體指針填入binder_transaction_data的target.ptr中返回給接收線程。接收線程從數(shù)據(jù)包中取出該指針,reinterpret_cast成Binder抽象類并調(diào)用onTransact()函數(shù)。由于這是個虛函數(shù),不同的Binder實體中有各自的實現(xiàn),從而可以調(diào)用到不同Binder實體提供的onTransact()。5.1.2Binder在Client端的表述–Binder引用作為Proxy設(shè)計模式的一部分,Client端的Binder同樣要繼承Server提供的公共接口類并實現(xiàn)公共函數(shù)。但這不是真正的實現(xiàn),而是對遠(yuǎn)程函數(shù)調(diào)用的包裝:將函數(shù)參數(shù)打包,通過Binder向Server發(fā)送申請并等待返回值。為此Client端的Binder還要知道Binder實體的相關(guān)信息,即對Binder實體的引用。該引用或是由SMgr轉(zhuǎn)發(fā)過來的,對實名Binder的引用或是由另一個進程直接發(fā)送過來的,匿名Binder的引用。由于繼承了同樣的公共接口類,ClientBinder提供了與ServerBinder一樣的函數(shù)原型,使用戶感覺不出Server是運行在本地還是遠(yuǎn)端。ClientBinder中,公共接口函數(shù)的實現(xiàn)方式是:創(chuàng)建一個binder_transaction_data數(shù)據(jù)包,將其對應(yīng)的編碼填入code域,將調(diào)用該函數(shù)所需的參數(shù)填入data.buffer指向的緩存中,并指明數(shù)據(jù)包的目的地,那就是已經(jīng)獲得的對Binder實體的引用,填入數(shù)據(jù)包的target.handle中。注意這里和Server的區(qū)別:實際上target域是個聯(lián)合體,包括ptr和handle兩個成員,前者用于作為響應(yīng)方的Server,指向Binder實體對應(yīng)的內(nèi)存空間;后者用于作為請求方的Client,存放Binder實體的引用,告知驅(qū)動數(shù)據(jù)包將路由給哪個實體。數(shù)據(jù)包準(zhǔn)備好后,通過驅(qū)動接口發(fā)送出去。經(jīng)過BC_TRANSACTION/BC_REPLY回合完成函數(shù)的遠(yuǎn)程調(diào)用并得到返回值。5.2Binder在傳輸數(shù)據(jù)中的表述Binder可以塞在數(shù)據(jù)包的有效數(shù)據(jù)中越進程邊界從一個進程傳遞給另一個進程,這些傳輸中的Binder用結(jié)構(gòu)flat_binder_object表示,如下表所示:表6Binder傳輸結(jié)構(gòu):flat_binder_object成員含義unsignedlongtype表明該Binder的類型,包括以下幾種:BINDER_TYPE_BINDER:表示傳遞的是Binder實體,并且指向該實體的引用都是強類型;BINDER_TYPE_WEAK_BINDER:表示傳遞的是Binder實體,并且指向該實體的引用都是弱類型;BINDER_TYPE_HANDLE:表示傳遞的是Binder強類型的引用BINDER_TYPE_WEAK_HANDLE:表示傳遞的是Binder弱類型的引用BINDER_TYPE_FD:表示傳遞的是文件形式的Binder,詳見下節(jié)unsignedlongflags該域只對第一次傳遞Binder實體時有效,因為此刻驅(qū)動需要在內(nèi)核中創(chuàng)建相應(yīng)的實體節(jié)點,有些參數(shù)需要從該域取出:第0-7位:代碼中用FLAT_BINDER_FLAG_PRIORITY_MASK取得,表示處理本實體請求數(shù)據(jù)包的線程的最低優(yōu)先級。當(dāng)一個應(yīng)用程序提供多個實體時,可以通過該參數(shù)調(diào)整分配給各個實體的處理能力。第8位:代碼中用FLAT_BINDER_FLAG_ACCEPTS_FDS取得,置1表示該實體可以接收其它進程發(fā)過來的文件形式的Binder。由于接收文件形式的Binder會在本進程中自動打開文件,有些Server可以用該標(biāo)志禁止該功能,以防打開過多文件。union{void*binder;signedlonghandle;};當(dāng)傳遞的是Binder實體時使用binder域,指向Binder實體在應(yīng)用程序中的地址。當(dāng)傳遞的是Binder引用時使用handle域,存放Binder在進程中的引用號。void*cookie;該域只對Binder實體有效,存放與該Binder有關(guān)的附加信息。無論是Binder實體還是對實體的引用都從屬與某個進程,所以該結(jié)構(gòu)不能透明地在進程之間傳輸,必須有驅(qū)動的參與。例如當(dāng)Server把Binder實體傳遞給Client時,在發(fā)送數(shù)據(jù)中,flat_binder_object中的type是BINDER_TYPE_BINDER,binder指向Server進程用戶空間地址。如果透傳給接收端將毫無用處,驅(qū)動必須對數(shù)據(jù)流中的這個Binder做修改:將type該成BINDER_TYPE_HANDLE;為這個Binder在接收進程中創(chuàng)建位于內(nèi)核中的引用并將引用號填入handle中。對于發(fā)生數(shù)據(jù)流中引用類型的Binder也要做同樣轉(zhuǎn)換。經(jīng)過處理后接收進程從數(shù)據(jù)流中取得的Binder引用才是有效的,才可以將其填入數(shù)據(jù)包binder_transaction_data的target.handle域,向Binder實體發(fā)送請求。這樣做也是出于安全性考慮:應(yīng)用程序不能隨便猜測一個引用號填入target.handle中就可以向Server請求服務(wù)了,因為驅(qū)動并沒有為你在內(nèi)核中創(chuàng)建該引用,必定會驅(qū)動被拒絕。唯有經(jīng)過身份認(rèn)證確認(rèn)合法后,由‘權(quán)威機構(gòu)’通過數(shù)據(jù)流授予你的Binder才能使用,因為這時驅(qū)動已經(jīng)在內(nèi)核中為你建立了引用,交給你的引用號是合法的。下表總結(jié)了當(dāng)flat_binder_object結(jié)構(gòu)穿過驅(qū)動時驅(qū)動所做的操作:表7驅(qū)動對flat_binder_object的操作Binder類型(type域)在發(fā)送方的操作在接收方的操作BINDER_TYPE_BINDERBINDER_TYPE_WEAK_BINDER只有實體所在的進程能發(fā)送該類型的Binder。如果是第一次發(fā)送驅(qū)動將創(chuàng)建實體在內(nèi)核中的節(jié)點,并保存binder,cookie,flag域。如果是第一次接收該Binder則創(chuàng)建實體在內(nèi)核中的引用;將handle域替換為新建的引用號;將type域替換為BINDER_TYPE_(WEAK_)HANDLEBINDER_TYPE_HANDLEBINDER_TYPE_WEAK_HANDLE獲得Binder引用的進程都能發(fā)送該類型Binder。驅(qū)動根據(jù)handle域提供的引用號查找建立在內(nèi)核的引用。如果找到說明引用號合法,否則拒絕該發(fā)送請求。如果收到的Binder實體位于接收進程中:將ptr域替換為保存在節(jié)點中的binder值;cookie替換為保存在節(jié)點中的cookie值;type替換為BINDER_TYPE_(WEAK_)BINDER。如果收到的Binder實體不在接收進程中:如果是第一次接收則創(chuàng)建實體在內(nèi)核中的引用;將handle域替換為新建的引用號BINDER_TYPE_FD驗證handle域中提供的打開文件號是否有效,無效則拒絕該發(fā)送請求。在接收方創(chuàng)建新的打開文件號并將其與提供的打開文件描述結(jié)構(gòu)綁定。5.2.1文件形式的Binder除了通常意義上用來通信的Binder,還有一種特殊的Binder:文件Binder。這種Binder的基本思想是:將文件看成Binder實體,進程打開的文件號看成Binder的引用。一個進程可以將它打開文件的文件號傳遞給另一個進程,從而另一個進程也打開了同一個文件,就象Binder的引用在進程之間傳遞一樣。一個進程打開一個文件,就獲得與該文件綁定的打開文件號。從Binder的角度,linux在內(nèi)核創(chuàng)建的打開文件描述結(jié)構(gòu)structfile是Binder的實體,打開文件號是該進程對該實體的引用。既然是Binder那么就可以在進程之間傳遞,故也可以用flat_binder_object結(jié)構(gòu)將文件Binder通過數(shù)據(jù)包發(fā)送至其它進程,只是結(jié)構(gòu)中type域的值為BINDER_TYPE_FD,表明該Binder是文件Binder。而結(jié)構(gòu)中的handle域則存放文件在發(fā)送方進程中的打開文件號。我們知道打開文件號是個局限于某個進程的值,一旦跨進程就沒有意義了。這一點和Binder實體用戶指針或Binder引用號是一樣的,若要跨進程同樣需要驅(qū)動做轉(zhuǎn)換。驅(qū)動在接收Binder的進程空間創(chuàng)建一個新的打開文件號,將它與已有的打開文件描述結(jié)構(gòu)structfile勾連上,從此該Binder實體又多了一個引用。新建的打開文件號覆蓋flat_binder_object中原來的文件號交給接收進程。接收進程利用它可以執(zhí)行read(),write()等文件操作。傳個文件為啥要這么麻煩,直接將文件名用Binder傳過去,接收方用open()打開不就行了嗎?其實這還是有區(qū)別的。首先對同一個打開文件共享的層次不同:使用文件Binder打開的文件共享linuxVFS中的structfile,structdentry,structinode結(jié)構(gòu),這意味著一個進程使用read()/write()/seek()改變了文件指針另一個進程的文件指針也會改變;而如果兩個進程分別使用文件名打開同一文件則有各自的structfile結(jié)構(gòu),從而各自獨立維護文件指針,互不干擾。其次是一些特殊設(shè)備文件要求在structfile一級共享才能使用,例如Android的另一個驅(qū)動ashmem,它和Binder一樣也是misc設(shè)備,用以實現(xiàn)進程間的共享內(nèi)存。一個進程打開的ashmem文件只有通過文件Binder發(fā)送到另一個進程才能實現(xiàn)內(nèi)存共享,這大大提高了內(nèi)存共享的安全性,道理和Binder增強了IPC的安全性是一樣的。5.3Binder在驅(qū)動中的表述驅(qū)動是Binder通信的核心,系統(tǒng)中所有的Binder實體以及每個實體在各個進程中的引用都登記在驅(qū)動中;驅(qū)動需要記錄Binder引用->實體之間多對一的關(guān)系;為引用找到對應(yīng)的實體;在某個進程中為實體創(chuàng)建或查找到對應(yīng)的引用;記錄Binder的歸屬地(位于哪個進程中);通過管理Binder的強/弱引用創(chuàng)建/銷毀Binder實體等等。驅(qū)動中的Binder是什么時候創(chuàng)建的呢?前面提到過,為了實現(xiàn)實名Binder的注冊,系統(tǒng)必須創(chuàng)建第一只雞–為SMgr創(chuàng)建的,注冊實名Binder專用的Binder實體,負(fù)責(zé)實名Binder注冊過程中的進程間通信。既然創(chuàng)建了實體也要有對應(yīng)的引用:驅(qū)動將所有進程中的0號引用都預(yù)留給該Binder實體,即一開始所有進程的0號引用都指注冊實名Binder專用的Binder,無須特殊操作任何進程通過0號引用都可以注冊實名Binder。接下來隨著應(yīng)用程序通過不斷地注冊實名Binder,不斷向SMgr索要Binder的引用,不斷將Binder從一個進程傳遞給另一個進程,越來越多的Binder以傳輸結(jié)構(gòu)–flat_binder_object的形式穿越驅(qū)動做跨進程的遷徙。由于binder_transaction_data中data.offset數(shù)組的存在,所有流經(jīng)驅(qū)動的Binder都逃不過驅(qū)動的眼睛。Binder將對每個穿越進程邊界的Binder做如下操作:檢查傳輸結(jié)構(gòu)的type域,如果是BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER則創(chuàng)建Binder的實體;如果是BINDER_TYPE_HANDLE或BINDER_TYPE_WEAK_HANDLE則創(chuàng)建Binder的引用;如果是BINDER_TYPE_HANDLE則為進程打開文件,無須創(chuàng)建任何數(shù)據(jù)結(jié)構(gòu)。詳細(xì)過程可參考表7。隨著越來越多的Binder實體或引用穿過驅(qū)動在進程間傳遞,驅(qū)動會在內(nèi)核里創(chuàng)建越來越多的節(jié)點或引用,當(dāng)然這個過程對用戶來說是透明的。5.3.1Binder實體在驅(qū)動中的表述驅(qū)動中的Binder實體也叫‘節(jié)點’,隸屬于提供實體的進程,由structbinder_node結(jié)構(gòu)來表示:表8Binder節(jié)點描述結(jié)構(gòu):binder_node成員含義intdebug_id;用于調(diào)試structbinder_workwork;當(dāng)本節(jié)點引用計數(shù)發(fā)生改變,需要通知所屬進程時,通過該成員掛入所屬進程的to-do隊列里,喚醒所屬進程執(zhí)行Binder實體引用計數(shù)的修改。union{structrb_noderb_node;structhlist_nodedead_node;};每個進程都維護一棵紅黑樹,以Binder實體在用戶空間的指針,即本結(jié)構(gòu)的ptr成員為索引存放該進程所有的Binder實體。這樣驅(qū)動可以根據(jù)Binder實體在用戶空間的指針很快找到其位于內(nèi)核的節(jié)點。rb_node用于將本節(jié)點鏈入該紅黑樹中。銷毀節(jié)點時須將rb_node從紅黑樹中摘除,但如果本節(jié)點還有引用沒有切斷,就用dead_node將節(jié)點隔離到另一個鏈表中,直到通知所有進程切斷與該節(jié)點的引用后,該節(jié)點才可能被銷毀。structbinder_proc*proc;本成員指向節(jié)點所屬的進程,即提供該節(jié)點的進程。structhlist_headrefs;本成員是隊列頭,所有指向本節(jié)點的引用都鏈接在該隊列里。這些引用可能隸屬于不同的進程。通過該隊列可以遍歷指向該節(jié)點的所有引用。intinternal_strong_refs;用以實現(xiàn)強指針的計數(shù)器:產(chǎn)生一個指向本節(jié)點的強引用該計數(shù)就會加1。intlocal_weak_refs;驅(qū)動為傳輸中的Binder設(shè)置的弱引用計數(shù)。如果一個Binder打包在數(shù)據(jù)包中從一個進程發(fā)送到另一個進程,驅(qū)動會為該Binder增加引用計數(shù),直到接收進程通過BC_FREE_BUFFER通知驅(qū)動釋放該數(shù)據(jù)包的數(shù)據(jù)區(qū)為止。intlocal_strong_refs;驅(qū)動為傳輸中的Binder設(shè)置的強引用計數(shù)。同上。void__user*ptr;指向用戶空間Binder實體的指針,來自于flat_binder_object的binder成員。void__user*cookie;指向用戶空間的附加指針,來自于flat_binder_object的cookie成員。unsignedhas_strong_ref;unsignedpending_strong_ref;unsignedhas_weak_ref;unsignedpending_weak_ref這一組標(biāo)志用于控制驅(qū)動與Binder實體所在進程交互式修改引用計數(shù)。unsignedhas_async_transaction;該成員表明該節(jié)點在to-do隊列中有異步交互尚未完成。驅(qū)動將所有發(fā)送往接收端的數(shù)據(jù)包暫存在接收進程或線程開辟的to-do隊列里。對于異步交互,驅(qū)動做了適當(dāng)流控:如果to-do隊列里有異步交互尚待處理則該成員置1,這將導(dǎo)致新到的異步交互存放在本結(jié)構(gòu)成員–asynch_todo隊列中,而不直接送到to-do隊列里。目的是為同步交互讓路,避免長時間阻塞發(fā)送端。unsignedaccept_fds表明節(jié)點是否同意接受文件方式的Binder,來自flat_binder_object中flags成員的FLAT_BINDER_FLAG_ACCEPTS_FDS位。由于接收文件Binder會為進程自動打開一個文件,占用有限的文件描述符,節(jié)點可以設(shè)置該位拒絕這種行為。intmin_priority設(shè)置處理Binder請求的線程的最低優(yōu)先級。發(fā)送線程將數(shù)據(jù)提交給接收線程處理時,驅(qū)動會將發(fā)送線程的優(yōu)先級也賦予接收線程,使得數(shù)據(jù)即使跨了進程也能以同樣優(yōu)先級得到處理。不過如果發(fā)送線程優(yōu)先級過低,接收線程將以預(yù)設(shè)的最小值運行。該域的值來自于flat_binder_object中flags成員。structlist_headasync_todo異步交互等待隊列;用于分流發(fā)往本節(jié)點的異步交互包。每個進程都有一棵紅黑樹用于存放創(chuàng)建好的節(jié)點,以Binder在用戶空間的指針作為索引。每當(dāng)在傳輸數(shù)據(jù)中偵測到一個代表Binder實體的flat_binder_object,先以該結(jié)構(gòu)的binder指針為索引搜索紅黑樹;如果沒找到就創(chuàng)建一個新節(jié)點添加到樹中。由于對于同一個進程來說內(nèi)存地址是唯一的,所以不會重復(fù)建設(shè)造成混亂。5.3.2Binder引用在驅(qū)動中的表述和實體一樣,Binder的引用也是驅(qū)動根據(jù)傳輸數(shù)據(jù)中的flat_binder_object創(chuàng)建的,隸屬于獲得該引用的進程,用structbinder_ref結(jié)構(gòu)體表示:表9Binder引用描述結(jié)構(gòu):binder_ref成員含義intdebug_id;調(diào)試用structrb_noderb_node_desc;每個進程有一棵紅黑樹,進程所有引用以引用號(即本結(jié)構(gòu)的desc域)為索引添入該樹中。本成員用做鏈接到該樹的一個節(jié)點。structrb_noderb_node_node;每個進程又有一棵紅黑樹,進程所有引用以節(jié)點實體在驅(qū)動中的內(nèi)存地址(即本結(jié)構(gòu)的node域)為所引添入該樹中。本成員用做鏈接到該樹的一個節(jié)點。structhlist_nodenode_entry;該域?qū)⒈疽米鰹楣?jié)點鏈入所指向的Binder實體結(jié)構(gòu)structhlist_nodenode_entry;該域?qū)⒈疽米鰹楣?jié)點鏈入所指向的Binder實體結(jié)構(gòu)binder_node中的refs隊列。structbinder_proc*proc;本引用所屬的進程structbinder_node*node;本引用所指向的節(jié)點(Binder實體)uint32_tdesc;本結(jié)構(gòu)的引用號intstrong;強引用計數(shù)intweak;弱引用計數(shù)structbinder_ref_death*death;應(yīng)用程序向驅(qū)動發(fā)送BC_REQUEST_DEATH_NOTIFICATION或BC_CLEAR_DEATH_NOTIFICATION命令從而當(dāng)Binder實體銷毀時能夠收到來自驅(qū)動的提醒。該域不為空表明用戶訂閱了對應(yīng)實體銷毀的‘噩耗’。就象一個對象有很多指針一樣,同一個Binder實體可能有很多引用,不同的是這些引用可能分布在不同的進程中。和實體一樣,每個進程使用紅黑樹存放所有該進程正在使用的引用。但Binder的引用可以通過兩個鍵值索引:·對應(yīng)實體在內(nèi)核中的地址。注意這里指的是驅(qū)動創(chuàng)建于內(nèi)核中的binder_node結(jié)構(gòu)的地址,而不是Binder實體在用戶進程中的地址。實體在內(nèi)核中的地址是唯一的,用做索引不會產(chǎn)生二義性;但實體可能來自不同用戶進程,而實體在不同用戶進程中的地址可能重合,不能用來做索引。驅(qū)動利用該紅黑樹在一個進程中快速查找某個Binder實體所對應(yīng)的引用(一個實體在一個進程中只建立一個引用)?!ひ锰?。引用號是驅(qū)動為引用分配的一個32位標(biāo)識,在一個進程內(nèi)是唯一的,而在不同進程中可能會有同樣的值,這和進程的打開文件號很類似。引用號將返回給應(yīng)用程序,可以看作Binder引用在用戶進程中的句柄。除了0號引用在所有進程里都保留給SMgr,其它值由驅(qū)動在創(chuàng)建引用時動態(tài)分配。向Binder發(fā)送數(shù)據(jù)包時,應(yīng)用程序通過將引用號填入binder_transaction_data結(jié)構(gòu)的target.handle域中表明該數(shù)據(jù)包的目的Binder。驅(qū)動根據(jù)該引用號在紅黑樹中找到引用的binder_ref結(jié)構(gòu),進而通過其node域知道目標(biāo)Binder實體所在的進程及其它相關(guān)信息,實現(xiàn)數(shù)據(jù)包的路由。6Binder內(nèi)存映射和接收緩存區(qū)管理暫且撇開Binder,考慮一下傳統(tǒng)的IPC方式中,數(shù)據(jù)是怎樣從發(fā)送端到達(dá)接收端的呢?通常的做法是,發(fā)送方將準(zhǔn)備好的數(shù)據(jù)存放在緩存區(qū)中,調(diào)用API通過系統(tǒng)調(diào)用進入內(nèi)核中。內(nèi)核服務(wù)程序在內(nèi)核空間分配內(nèi)存,將數(shù)據(jù)從發(fā)送方緩存區(qū)復(fù)制到內(nèi)核緩存區(qū)中。接收方讀數(shù)據(jù)時也要提供一塊緩存區(qū),內(nèi)核將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到接收方提供的緩存區(qū)中并喚醒接收線程,完成一次數(shù)據(jù)發(fā)送。這種存儲-轉(zhuǎn)發(fā)機制有兩個缺陷:首先是效率低下,需要做兩次拷貝:用戶空間->內(nèi)核空間->用戶空間。Linux使用copy_from_user()和copy_to_user()實現(xiàn)這兩個跨空間拷貝,在此過程中如果使用了高端內(nèi)存(highmemory),這種拷貝需要臨時建立/取消頁面映射,造成性能損失。其次是接收數(shù)據(jù)的緩存要由接收方提供,可接收方不知道到底要多大的緩存才夠用,只能開辟盡量大的空間或先調(diào)用API接收消息頭獲得消息體大小,再開辟適當(dāng)?shù)目臻g接收消息體。兩種做法都有不足,不是浪費空間就是浪費時間。Binder采用一種全新策略:由Binder驅(qū)動負(fù)責(zé)管理數(shù)據(jù)接收緩存。我們注意到Binder驅(qū)動實現(xiàn)了mmap()系統(tǒng)調(diào)用,這對字符設(shè)備是比較特殊的,因為mmap()通常用在有物理存儲介質(zhì)的文件系統(tǒng)上,而象Binder這樣沒有物理介質(zhì),純粹用來通信的字符設(shè)備沒必要支持mmap()。Binder驅(qū)動當(dāng)然不是為了在物理介質(zhì)和用戶空間做映射,而是用來創(chuàng)建數(shù)據(jù)接收的緩存空間。先看mmap()是如何使用的:fd=open(“/dev/binder”,O_RDWR);mmap(NULL,MAP_SIZE,PROT_READ,MAP_PRIVATE,fd,0);這樣Binder的接收方就有了一片大小為MAP_SIZE的接收緩存區(qū)。mmap()的返回值是內(nèi)存映射在用戶空間的地址,不過這段空間是由驅(qū)動管理,用戶不必也不能直接訪問(映射類型為PROT_READ,只讀映射)。接收緩存區(qū)映射好后就可以作為緩存池接收和存放數(shù)據(jù)了。前面說過,接收數(shù)據(jù)包的結(jié)構(gòu)為binder_transaction_data,但這只是消息頭,真正的有效負(fù)荷位于data.buffer所指向的內(nèi)存中。這片內(nèi)存不需要接收方提供,恰恰是來自mmap()映射的這片緩存池。在數(shù)據(jù)從發(fā)送方向接收方拷貝時,驅(qū)動會根據(jù)發(fā)送數(shù)據(jù)包的大小,使用最佳匹配算法從緩存池中找到一塊大小合適的空間,將數(shù)據(jù)從發(fā)送緩存區(qū)復(fù)制過來。要注意的是,存放binder_transaction_data結(jié)構(gòu)本身以及表4中所有消息的內(nèi)存空間還是得由接收者提供,但這些數(shù)據(jù)大小固定,數(shù)量也不多,不會給接收方造成不便。映射的緩存池要足夠大,因為接收方的線程池可能會同時處理多條并發(fā)的交互,每條交互都需要從緩存池中獲取目的存儲區(qū),一旦緩存池耗竭將產(chǎn)生導(dǎo)致無法預(yù)期的后果。有分配必然有釋放。接收方在處理完數(shù)據(jù)包后,就要通知驅(qū)動釋放data.buffer所指向的內(nèi)存區(qū)。在介紹Binder協(xié)議時已經(jīng)提到,這是由命令BC_FREE_BUFFER完成的。通過上面介紹可以看到,驅(qū)動為接收方分擔(dān)了最為繁瑣的任務(wù):分配/釋放大小不等,難以預(yù)測的有效負(fù)荷緩存區(qū),而接收方只需要提供緩存來存放大小固定,可以預(yù)測的消息頭即可。在效率上,由于mmap()分配的內(nèi)存是映射在接收方用戶空間里的,所有總體效果就相當(dāng)于對有效負(fù)荷數(shù)據(jù)做了一次從發(fā)送方用戶空間到接收方用戶空間的直接數(shù)據(jù)拷貝,省去了內(nèi)核中暫存這個步驟,提升了一倍的性能。順便再提一點,Linux內(nèi)核實際上沒有從一個用戶空間到另一個用戶空間直接拷貝的函數(shù),需要先用copy_from_user()拷貝到內(nèi)核空間,再用copy_to_user()拷貝到另一個用戶空間。為了實現(xiàn)用戶空間到用戶空間的拷貝,mmap()分配的內(nèi)存除了映射進了接收方進程里,還映射進了內(nèi)核空間。所以調(diào)用copy_from_user()將數(shù)據(jù)拷貝進內(nèi)核空間也相當(dāng)于拷貝進了接收方的用戶空間,這就是Binder只需一次拷貝的‘秘密’。7.Binder接收線程管理Binder通信實際上是位于不同進程中的線程之間的通信。假如進程S是Server端,提供Binder實體,線程T1從Client進程C1中通過Binder的引用向進程S發(fā)送請求。S為了處理這個請求需要啟動線程T2,而此時線程T1處于接收返回數(shù)據(jù)的等待狀態(tài)。T2處理完請求就會將處理結(jié)果返回給T1,T1被喚醒得到處理結(jié)果。在這過程中,T2仿佛T1在進程S中的代理,代表T1執(zhí)行遠(yuǎn)程任務(wù),而給T1的感覺就是像穿越到S中執(zhí)行一段代碼又回到了C1。為了使這種穿越更加真實,驅(qū)動會將T1的一些屬性賦給T2,特別是T1的優(yōu)先級nice,這樣T2會使用和T1類似的時間完成任務(wù)。很多資料會用‘線程遷移’來形容這種現(xiàn)象,容易讓人產(chǎn)生誤解。一來線程根本不可能在進程之間跳來跳去,二來T2除了和T1優(yōu)先級一樣,其它沒有相同之處,包括身份,打開文件,棧大小,信號處理,私有數(shù)據(jù)等。對于Server進程S,可能會有許多Client同時發(fā)起請求,為了提高效率往往開辟線程池并發(fā)處理收到的請求。怎樣使用線程池實現(xiàn)并發(fā)處理呢?這和具體的IPC機制有關(guān)。拿socket舉例,Server端的socket設(shè)置為偵聽模式,有一個專門的線程使用該socket偵聽來自Client的連接請求,即阻塞在accept()上。這個socket就象一只會生蛋的雞,一旦收到來自Client的請求就會生一個蛋–創(chuàng)建新socket并從accept()返回。偵聽線程從線程池中啟動一個工作線程并將剛下的蛋交給該線程。后續(xù)業(yè)務(wù)處理就由該線程完成并通過這個單與Client實現(xiàn)交互。可是對于Binder來說,既沒有偵聽模式也不會下蛋,怎樣管理線程池呢?一種簡單的做法是,不管三七二十一,先創(chuàng)建一堆線程,每個線程都用BINDER_WRITE_READ
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年陶瓷水杯采購與市場渠道建設(shè)合同3篇
- 二零二五年度美發(fā)店美容美發(fā)行業(yè)投資咨詢與評估合同4篇
- 二零二五年度民政局官方版自愿離婚協(xié)議書及子女撫養(yǎng)協(xié)議4篇
- 二零二五版文化旅游用地租賃及項目合作協(xié)議3篇
- 保險賠償流程解析模板
- 2025年度個人旅游貸款合同樣本11篇
- 二零二五版購房公積金提取使用合同3篇
- 運籌學(xué)課程設(shè)計excel求解
- 2025年度汽車租賃市場風(fēng)險控制與應(yīng)對策略合同4篇
- 年度功能飲料競爭策略分析報告
- 2024老年人靜脈血栓栓塞癥防治中國專家共識(完整版)
- 騰訊營銷師認(rèn)證考試題庫(附答案)
- 鄰近鐵路營業(yè)線施工安全監(jiān)測技術(shù)規(guī)程 (TB 10314-2021)
- 四年級上冊脫式計算100題及答案
- 資本市場與財務(wù)管理
- 河南近10年中考真題數(shù)學(xué)含答案(2023-2014)
- 八年級上學(xué)期期末家長會課件
- 2024年大學(xué)試題(宗教學(xué))-佛教文化歷年考試高頻考點試題附帶答案
- HGE系列電梯安裝調(diào)試手冊(ELS05系統(tǒng)SW00004269,A.4 )
- 尤文肉瘤的護理查房
- 儲能電站火災(zāi)應(yīng)急預(yù)案演練
評論
0/150
提交評論