關(guān)鍵詞 Binder Android IPC Linux 內(nèi)核 驅(qū)動(dòng) 摘要 Binder是Android系統(tǒng)進(jìn)程間通信(IPC)方式之一。Linux已經(jīng)擁有管道,system V IPC,socket等IPC手段,卻還要倚賴Binder來(lái)實(shí)現(xiàn)進(jìn)程間通信,說(shuō)明Binder具有無(wú)可比擬的優(yōu)勢(shì)。深入了解Binder并將之與傳統(tǒng)IPC做對(duì)比有助于我們深入領(lǐng)會(huì)進(jìn)程間通信的實(shí)現(xiàn)和性能優(yōu)化。本文將對(duì)Binder的設(shè)計(jì)細(xì)節(jié)做一個(gè)全面的闡述,首先通過(guò)介紹Binder通信模型和Binder通信協(xié)議了解Binder的設(shè)計(jì)需求;然后分別闡述Binder在系統(tǒng)不同部分的表述方式和起的作用;最后還會(huì)解釋Binder在數(shù)據(jù)接收端的設(shè)計(jì)考慮,包括線程池管理,內(nèi)存映射和等待隊(duì)列管理等。通過(guò)本文對(duì)Binder的詳細(xì)介紹以及與其它IPC通信方式的對(duì)比,讀者將對(duì)Binder的優(yōu)勢(shì)和使用Binder作為Android主要IPC方式的原因有深入了解。 1 引言基于Client-Server的通信方式廣泛應(yīng)用于從互聯(lián)網(wǎng)和數(shù)據(jù)庫(kù)訪問(wèn)到嵌入式手持設(shè)備內(nèi)部通信等各個(gè)領(lǐng)域。智能手機(jī)平臺(tái)特別是Android系統(tǒng)中,為了向應(yīng)用開(kāi)發(fā)者提供豐富多樣的功能,這種通信方式更是無(wú)處不在,諸如媒體播放,視音頻頻捕獲,到各種讓手機(jī)更智能的傳感器(加速度,方位,溫度,光亮度等)都由不同的Server負(fù)責(zé)管理,應(yīng)用程序只需做為Client與這些Server建立連接便可以使用這些服務(wù),花很少的時(shí)間和精力就能開(kāi)發(fā)出令人眩目的功能。Client-Server方式的廣泛采用對(duì)進(jìn)程間通信(IPC)機(jī)制是一個(gè)挑戰(zhàn)。目前l(fā)inux支持的IPC包括傳統(tǒng)的管道,System V IPC,即消息隊(duì)列/共享內(nèi)存/信號(hào)量,以及socket中只有socket支持Client-Server的通信方式。當(dāng)然也可以在這些底層機(jī)制上架設(shè)一套協(xié)議來(lái)實(shí)現(xiàn)Client-Server通信,但這樣增加了系統(tǒng)的復(fù)雜性,在手機(jī)這種條件復(fù)雜,資源稀缺的環(huán)境下可靠性也難以保證。 另一方面是傳輸性能。socket作為一款通用接口,其傳輸效率低,開(kāi)銷大,主要用在跨網(wǎng)絡(luò)的進(jìn)程間通信和本機(jī)上進(jìn)程間的低速通信。消息隊(duì)列和管道采用存儲(chǔ)-轉(zhuǎn)發(fā)方式,即數(shù)據(jù)先從發(fā)送方緩存區(qū)拷貝到內(nèi)核開(kāi)辟的緩存區(qū)中,然后再?gòu)膬?nèi)核緩存區(qū)拷貝到接收方緩存區(qū),至少有兩次拷貝過(guò)程。共享內(nèi)存雖然無(wú)需拷貝,但控制復(fù)雜,難以使用。 表 1 各種IPC方式數(shù)據(jù)拷貝次數(shù)
還有一點(diǎn)是出于安全性考慮。Android作為一個(gè)開(kāi)放式,擁有眾多開(kāi)發(fā)者的的平臺(tái),應(yīng)用程序的來(lái)源廣泛,確保智能終端的安全是非常重要的。終端用戶不希望從網(wǎng)上下載的程序在不知情的情況下偷窺隱私數(shù)據(jù),連接無(wú)線網(wǎng)絡(luò),長(zhǎng)期操作底層設(shè)備導(dǎo)致電池很快耗盡等等。傳統(tǒng)IPC沒(méi)有任何安全措施,完全依賴上層協(xié)議來(lái)確保。首先傳統(tǒng)IPC的接收方無(wú)法獲得對(duì)方進(jìn)程可靠的UID/PID(用戶ID/進(jìn)程ID),從而無(wú)法鑒別對(duì)方身份。Android為每個(gè)安裝好的應(yīng)用程序分配了自己的UID,故進(jìn)程的UID是鑒別進(jìn)程身份的重要標(biāo)志。使用傳統(tǒng)IPC只能由用戶在數(shù)據(jù)包里填入U(xiǎn)ID/PID,但這樣不可靠,容易被惡意程序利用。可靠的身份標(biāo)記只有由IPC機(jī)制本身在內(nèi)核中添加。其次傳統(tǒng)IPC訪問(wèn)接入點(diǎn)是開(kāi)放的,無(wú)法建立私有通道。比如命名管道的名稱,system V的鍵值,socket的ip地址或文件名都是開(kāi)放的,只要知道這些接入點(diǎn)的程序都可以和對(duì)端建立連接,不管怎樣都無(wú)法阻止惡意程序通過(guò)猜測(cè)接收方地址獲得連接。 基于以上原因,Android需要建立一套新的IPC機(jī)制來(lái)滿足系統(tǒng)對(duì)通信方式,傳輸性能和安全性的要求,這就是Binder。Binder基于Client-Server通信模式,傳輸過(guò)程只需一次拷貝,為發(fā)送發(fā)添加UID/PID身份,既支持實(shí)名Binder也支持匿名Binder,安全性高。 2 面向?qū)ο蟮?Binder IPCBinder使用Client-Server通信方式:一個(gè)進(jìn)程作為Server提供諸如視頻/音頻解碼,視頻捕獲,地址本查詢,網(wǎng)絡(luò)連接等服務(wù);多個(gè)進(jìn)程作為Client向Server發(fā)起服務(wù)請(qǐng)求,獲得所需要的服務(wù)。要想實(shí)現(xiàn)Client-Server通信據(jù)必須實(shí)現(xiàn)以下兩點(diǎn):一是server必須有確定的訪問(wèn)接入點(diǎn)或者說(shuō)地址來(lái)接受Client的請(qǐng)求,并且Client可以通過(guò)某種途徑獲知Server的地址;二是制定Command-Reply協(xié)議來(lái)傳輸數(shù)據(jù)。例如在網(wǎng)絡(luò)通信中Server的訪問(wèn)接入點(diǎn)就是Server主機(jī)的IP地址+端口號(hào),傳輸協(xié)議為TCP協(xié)議。對(duì)Binder而言,Binder可以看成Server提供的實(shí)現(xiàn)某個(gè)特定服務(wù)的訪問(wèn)接入點(diǎn), Client通過(guò)這個(gè)‘地址’向Server發(fā)送請(qǐng)求來(lái)使用該服務(wù);對(duì)Client而言,Binder可以看成是通向Server的管道入口,要想和某個(gè)Server通信首先必須建立這個(gè)管道并獲得管道入口。 與其它IPC不同,Binder使用了面向?qū)ο蟮乃枷雭?lái)描述作為訪問(wèn)接入點(diǎn)的Binder及其在Client中的入口:Binder是一個(gè)實(shí)體位于Server中的對(duì)象,該對(duì)象提供了一套方法用以實(shí)現(xiàn)對(duì)服務(wù)的請(qǐng)求,就象類的成員函數(shù)。遍布于client中的入口可以看成指向這個(gè)binder對(duì)象的‘指針’,一旦獲得了這個(gè)‘指針’就可以調(diào)用該對(duì)象的方法訪問(wèn)server。在Client看來(lái),通過(guò)Binder‘指針’調(diào)用其提供的方法和通過(guò)指針調(diào)用其它任何本地對(duì)象的方法并無(wú)區(qū)別,盡管前者的實(shí)體位于遠(yuǎn)端Server中,而后者實(shí)體位于本地內(nèi)存中?!羔槨荂++的術(shù)語(yǔ),而更通常的說(shuō)法是引用,即Client通過(guò)Binder的引用訪問(wèn)Server。而軟件領(lǐng)域另一個(gè)術(shù)語(yǔ)‘句柄’也可以用來(lái)表述Binder在Client中的存在方式。從通信的角度看,Client中的Binder也可以看作是Server Binder的‘代理’,在本地代表遠(yuǎn)端Server為Client提供服務(wù)。本文中會(huì)使用‘引用’或‘句柄’這個(gè)兩廣泛使用的術(shù)語(yǔ)。 面向?qū)ο笏枷氲囊雽⑦M(jìn)程間通信轉(zhuǎn)化為通過(guò)對(duì)某個(gè)Binder對(duì)象的引用調(diào)用該對(duì)象的方法,而其獨(dú)特之處在于Binder對(duì)象是一個(gè)可以跨進(jìn)程引用的對(duì)象,它的實(shí)體位于一個(gè)進(jìn)程中,而它的引用卻遍布于系統(tǒng)的各個(gè)進(jìn)程之中。最誘人的是,這個(gè)引用和java里引用一樣既可以是強(qiáng)類型,也可以是弱類型,而且可以從一個(gè)進(jìn)程傳給其它進(jìn)程,讓大家都能訪問(wèn)同一Server,就象將一個(gè)對(duì)象或引用賦值給另一個(gè)引用一樣。Binder模糊了進(jìn)程邊界,淡化了進(jìn)程間通信過(guò)程,整個(gè)系統(tǒng)仿佛運(yùn)行于同一個(gè)面向?qū)ο蟮某绦蛑?。形形色色的Binder對(duì)象以及星羅棋布的引用仿佛粘接各個(gè)應(yīng)用程序的膠水,這也是Binder在英文里的原意。 當(dāng)然面向?qū)ο笾皇轻槍?duì)應(yīng)用程序而言,對(duì)于Binder驅(qū)動(dòng)和內(nèi)核其它模塊一樣使用C語(yǔ)言實(shí)現(xiàn),沒(méi)有類和對(duì)象的概念。Binder驅(qū)動(dòng)為面向?qū)ο蟮倪M(jìn)程間通信提供底層支持。 3 Binder 通信模型Binder框架定義了四個(gè)角色:Server,Client,ServiceManager(以后簡(jiǎn)稱SMgr)以及Binder驅(qū)動(dòng)。其中Server,Client,SMgr運(yùn)行于用戶空間,驅(qū)動(dòng)運(yùn)行于內(nèi)核空間。這四個(gè)角色的關(guān)系和互聯(lián)網(wǎng)類似:Server是服務(wù)器,Client是客戶終端,SMgr是域名服務(wù)器(DNS),驅(qū)動(dòng)是路由器。 3.1 Binder 驅(qū)動(dòng)和路由器一樣,Binder驅(qū)動(dòng)雖然默默無(wú)聞,卻是通信的核心。盡管名叫‘驅(qū)動(dòng)’,實(shí)際上和硬件設(shè)備沒(méi)有任何關(guān)系,只是實(shí)現(xiàn)方式和設(shè)備驅(qū)動(dòng)程序是一樣的:它工作于內(nèi)核態(tài),提供open(),mmap(),poll(),ioctl()等標(biāo)準(zhǔn)文件操作,以字符驅(qū)動(dòng)設(shè)備中的misc設(shè)備注冊(cè)在設(shè)備目錄/dev下,用戶通過(guò)/dev/binder訪問(wèn)該它。驅(qū)動(dòng)負(fù)責(zé)進(jìn)程之間Binder通信的建立,Binder在進(jìn)程之間的傳遞,Binder引用計(jì)數(shù)管理,數(shù)據(jù)包在進(jìn)程之間的傳遞和交互等一系列底層支持。驅(qū)動(dòng)和應(yīng)用程序之間定義了一套接口協(xié)議,主要功能由ioctl()接口實(shí)現(xiàn),不提供read(),write()接口,因?yàn)閕octl()靈活方便,且能夠一次調(diào)用實(shí)現(xiàn)先寫后讀以滿足同步交互,而不必分別調(diào)用write()和read()。Binder驅(qū)動(dòng)的代碼位于linux目錄的drivers/misc/binder.c中。 3.2 ServiceManager 與實(shí)名Binder和DNS類似,SMgr的作用是將字符形式的Binder名字轉(zhuǎn)化成Client中對(duì)該Binder的引用,使得Client能夠通過(guò)Binder名字獲得對(duì)Server中Binder實(shí)體的引用。注冊(cè)了名字的Binder叫實(shí)名Binder,就象每個(gè)網(wǎng)站除了有IP地址外還有自己的網(wǎng)址。Server創(chuàng)建了Binder實(shí)體,為其取一個(gè)字符形式,可讀易記的名字,將這個(gè)Binder連同名字以數(shù)據(jù)包的形式通過(guò)Binder驅(qū)動(dòng)發(fā)送給SMgr,通知SMgr注冊(cè)一個(gè)名叫張三的Binder,它位于某個(gè)Server中。驅(qū)動(dòng)為這個(gè)穿過(guò)進(jìn)程邊界的Binder創(chuàng)建位于內(nèi)核中的實(shí)體節(jié)點(diǎn)以及SMgr對(duì)實(shí)體的引用,將名字及新建的引用打包傳遞給SMgr。SMgr收數(shù)據(jù)包后,從中取出名字和引用填入一張查找表中。 細(xì)心的讀者可能會(huì)發(fā)現(xiàn)其中的蹊蹺:SMgr是一個(gè)進(jìn)程,Server是另一個(gè)進(jìn)程,Server向SMgr注冊(cè)Binder必然會(huì)涉及進(jìn)程間通信。當(dāng)前實(shí)現(xiàn)的是進(jìn)程間通信卻又要用到進(jìn)程間通信,這就好象蛋可以孵出雞前提卻是要找只雞來(lái)孵蛋。Binder的實(shí)現(xiàn)比較巧妙:預(yù)先創(chuàng)造一只雞來(lái)孵蛋:SMgr和其它進(jìn)程同樣采用Binder通信,SMgr是Server端,有自己的Binder對(duì)象(實(shí)體),其它進(jìn)程都是Client,需要通過(guò)這個(gè)Binder的引用來(lái)實(shí)現(xiàn)Binder的注冊(cè),查詢和獲取。SMgr提供的Binder比較特殊,它沒(méi)有名字也不需要注冊(cè),當(dāng)一個(gè)進(jìn)程使用BINDER_SET_CONTEXT_MGR命令將自己注冊(cè)成SMgr時(shí)Binder驅(qū)動(dòng)會(huì)自動(dòng)為它創(chuàng)建Binder實(shí)體(這就是那只預(yù)先造好的雞)。其次這個(gè)Binder的引用在所有Client中都固定為0而無(wú)須通過(guò)其它手段獲得。也就是說(shuō),一個(gè)Server若要向SMgr注冊(cè)自己Binder就必需通過(guò)0這個(gè)引用號(hào)和SMgr的Binder通信。類比網(wǎng)絡(luò)通信,0號(hào)引用就好比域名服務(wù)器的地址,你必須預(yù)先手工或動(dòng)態(tài)配置好。要注意這里說(shuō)的Client是相對(duì)SMgr而言的,一個(gè)應(yīng)用程序可能是個(gè)提供服務(wù)的Server,但對(duì)SMgr來(lái)說(shuō)它仍然是個(gè)Client。 3.3 Client 獲得實(shí)名Binder的引用Server向SMgr注冊(cè)了Binder實(shí)體及其名字后,Client就可以通過(guò)名字獲得該Binder的引用了。Client也利用保留的0號(hào)引用向SMgr請(qǐng)求訪問(wèn)某個(gè)Binder:我申請(qǐng)獲得名字叫張三的Binder的引用。SMgr收到這個(gè)連接請(qǐng)求,從請(qǐng)求數(shù)據(jù)包里獲得Binder的名字,在查找表里找到該名字對(duì)應(yīng)的條目,從條目中取出Binder的引用,將該引用作為回復(fù)發(fā)送給發(fā)起請(qǐng)求的Client。從面向?qū)ο蟮慕嵌?,這個(gè)Binder對(duì)象現(xiàn)在有了兩個(gè)引用:一個(gè)位于SMgr中,一個(gè)位于發(fā)起請(qǐng)求的Client中。如果接下來(lái)有更多的Client請(qǐng)求該Binder,系統(tǒng)中就會(huì)有更多的引用指向該Binder,就象java里一個(gè)對(duì)象存在多個(gè)引用一樣。而且類似的這些指向Binder的引用是強(qiáng)類型,從而確保只要有引用Binder實(shí)體就不會(huì)被釋放掉。通過(guò)以上過(guò)程可以看出,SMgr象個(gè)火車票代售點(diǎn),收集了所有火車的車票,可以通過(guò)它購(gòu)買到乘坐各趟火車的票-得到某個(gè)Binder的引用。 3.4 匿名 Binder并不是所有Binder都需要注冊(cè)給SMgr廣而告之的。Server端可以通過(guò)已經(jīng)建立的Binder連接將創(chuàng)建的Binder實(shí)體傳給Client,當(dāng)然這條已經(jīng)建立的Binder連接必須是通過(guò)實(shí)名Binder實(shí)現(xiàn)。由于這個(gè)Binder沒(méi)有向SMgr注冊(cè)名字,所以是個(gè)匿名Binder。Client將會(huì)收到這個(gè)匿名Binder的引用,通過(guò)這個(gè)引用向位于Server中的實(shí)體發(fā)送請(qǐng)求。匿名Binder為通信雙方建立一條私密通道,只要Server沒(méi)有把匿名Binder發(fā)給別的進(jìn)程,別的進(jìn)程就無(wú)法通過(guò)窮舉或猜測(cè)等任何方式獲得該Binder的引用,向該Binder發(fā)送請(qǐng)求。 下圖展示了參與Binder通信的所有角色,將在以后章節(jié)中一一提到。 圖 1 Binder通信示例 4 Binder 協(xié)議Binder協(xié)議基本格式是(命令+數(shù)據(jù)),使用ioctl(fd, cmd, arg)函數(shù)實(shí)現(xiàn)交互。命令由參數(shù)cmd承載,數(shù)據(jù)由參數(shù)arg承載,隨cmd不同而不同。下表列舉了所有命令及其所對(duì)應(yīng)的數(shù)據(jù): 表 2 Binder通信命令字
這其中最常用的命令是BINDER_WRITE_READ。該命令的參數(shù)包括兩部分?jǐn)?shù)據(jù):一部分是向Binder寫入的數(shù)據(jù),一部分是要從Binder讀出的數(shù)據(jù),驅(qū)動(dòng)程序先處理寫部分再處理讀部分。這樣安排的好處是應(yīng)用程序可以很靈活地處理命令的同步或異步。例如若要發(fā)送異步命令可以只填入寫部分而將read_size置成0;若要只從Binder獲得數(shù)據(jù)可以將寫部分置空即write_size置成0;若要發(fā)送請(qǐng)求并同步等待返回?cái)?shù)據(jù)可以將兩部分都置上。 4.1 BINDER_WRITE_READ 之寫操作Binder寫操作的數(shù)據(jù)時(shí)格式同樣也是(命令+數(shù)據(jù))。這時(shí)候命令和數(shù)據(jù)都存放在binder_write_read 結(jié)構(gòu)write_buffer域指向的內(nèi)存空間里,多條命令可以連續(xù)存放。數(shù)據(jù)緊接著存放在命令后面,格式根據(jù)命令不同而不同。下表列舉了Binder寫操作支持的命令: 表 3 Binder寫操作命令字
在這些命令中,最常用的是BC_TRANSACTION/BC_REPLY命令對(duì),Binder請(qǐng)求和應(yīng)答數(shù)據(jù)就是通過(guò)這對(duì)命令發(fā)送給接收方。這對(duì)命令所承載的數(shù)據(jù)包由結(jié)構(gòu)體struct binder_transaction_data定義。Binder交互有同步和異步之分,利用binder_transaction_data中flag域區(qū)分。如果flag域的TF_ONE_WAY位為1則為異步交互,即Client端發(fā)送完請(qǐng)求交互即結(jié)束, Server端不再返回BC_REPLY數(shù)據(jù)包;否則Server會(huì)返回BC_REPLY數(shù)據(jù)包,Client端必須等待接收完該數(shù)據(jù)包方才完成一次交互。 4.2 BINDER_WRITE_READ :從Binder讀出數(shù)據(jù)從Binder里讀出的數(shù)據(jù)格式和向Binder中寫入的數(shù)據(jù)格式一樣,采用(消息ID+數(shù)據(jù))形式,并且多條消息可以連續(xù)存放。下表列舉了從Binder讀出的命令字及其相應(yīng)的參數(shù): 表 4 Binder讀操作消息ID
和寫數(shù)據(jù)一樣,其中最重要的消息是BR_TRANSACTION 或BR_REPLY,表明收到了一個(gè)格式為binder_transaction_data的請(qǐng)求數(shù)據(jù)包(BR_TRANSACTION)或返回?cái)?shù)據(jù)包(BR_REPLY)。 4.3 struct binder_transaction_data :收發(fā)數(shù)據(jù)包結(jié)構(gòu)該結(jié)構(gòu)是Binder接收/發(fā)送數(shù)據(jù)包的標(biāo)準(zhǔn)格式,每個(gè)成員定義如下: 表 5 Binder收發(fā)數(shù)據(jù)包結(jié)構(gòu):binder_transaction_data
這里有必要再?gòu)?qiáng)調(diào)一下offsets_size和data.offsets兩個(gè)成員,這是Binder通信有別于其它IPC的地方。如前述,Binder采用面向?qū)ο蟮脑O(shè)計(jì)思想,一個(gè)Binder實(shí)體可以發(fā)送給其它進(jìn)程從而建立許多跨進(jìn)程的引用;另外這些引用也可以在進(jìn)程之間傳遞,就象java里將一個(gè)引用賦給另一個(gè)引用一樣。為Binder在不同進(jìn)程中建立引用必須有驅(qū)動(dòng)的參與,由驅(qū)動(dòng)在內(nèi)核創(chuàng)建并注冊(cè)相關(guān)的數(shù)據(jù)結(jié)構(gòu)后接收方才能使用該引用。而且這些引用可以是強(qiáng)類型,需要驅(qū)動(dòng)為其維護(hù)引用計(jì)數(shù)。然而這些跨進(jìn)程傳遞的Binder混雜在應(yīng)用程序發(fā)送的數(shù)據(jù)包里,數(shù)據(jù)格式由用戶定義,如果不把它們一一標(biāo)記出來(lái)告知驅(qū)動(dòng),驅(qū)動(dòng)將無(wú)法從數(shù)據(jù)中將它們提取出來(lái)。于是就使用數(shù)組data.offsets存放用戶數(shù)據(jù)中每個(gè)Binder相對(duì)data.buffer的偏移量,用offsets_size表示這個(gè)數(shù)組的大小。驅(qū)動(dòng)在發(fā)送數(shù)據(jù)包時(shí)會(huì)根據(jù)data.offsets和offset_size將散落于data.buffer中的Binder找出來(lái)并一一為它們創(chuàng)建相關(guān)的數(shù)據(jù)結(jié)構(gòu)。在數(shù)據(jù)包中傳輸?shù)腂inder是類型為struct flat_binder_object的結(jié)構(gòu)體,詳見(jiàn)后文。 對(duì)于接收方來(lái)說(shuō),該結(jié)構(gòu)只相當(dāng)于一個(gè)定長(zhǎng)的消息頭,真正的用戶數(shù)據(jù)存放在data.buffer所指向的緩存區(qū)中。如果發(fā)送方在數(shù)據(jù)中內(nèi)嵌了一個(gè)或多個(gè)Binder,接收到的數(shù)據(jù)包中同樣會(huì)用data.offsets和offset_size指出每個(gè)Binder的位置和總個(gè)數(shù)。不過(guò)通常接收方可以忽略這些信息,因?yàn)榻邮辗绞侵罃?shù)據(jù)格式的,參考雙方約定的格式定義就能知道這些Binder在什么位置。 圖 2 BINDER_WRITE_READ數(shù)據(jù)包實(shí)例 5 Binder 的表述考察一次Binder通信的全過(guò)程會(huì)發(fā)現(xiàn),Binder存在于系統(tǒng)以下幾個(gè)部分中: · 應(yīng)用程序進(jìn)程:分別位于Server進(jìn)程和Client進(jìn)程中 · Binder驅(qū)動(dòng):分別管理為Server端的Binder實(shí)體和Client端的引用 · 傳輸數(shù)據(jù):由于Binder可以跨進(jìn)程傳遞,需要在傳輸數(shù)據(jù)中予以表述 在系統(tǒng)不同部分,Binder實(shí)現(xiàn)的功能不同,表現(xiàn)形式也不一樣。接下來(lái)逐一探討B(tài)inder在各部分所扮演的角色和使用的數(shù)據(jù)結(jié)構(gòu)。 5.1 Binder 在應(yīng)用程序中的表述雖然Binder用到了面向?qū)ο蟮乃枷?,但并不限制?yīng)用程序一定要使用面向?qū)ο蟮恼Z(yǔ)言,無(wú)論是C語(yǔ)言還是C++語(yǔ)言都可以很容易的使用Binder來(lái)通信。例如盡管Android主要使用java/C++,象SMgr這么重要的進(jìn)程就是用C語(yǔ)言實(shí)現(xiàn)的。不過(guò)面向?qū)ο蟮姆绞奖硎銎饋?lái)更方便,所以本文假設(shè)應(yīng)用程序是用面向?qū)ο笳Z(yǔ)言實(shí)現(xiàn)的。 Binder本質(zhì)上只是一種底層通信方式,和具體服務(wù)沒(méi)有關(guān)系。為了提供具體服務(wù),Server必須提供一套接口函數(shù)以便Client通過(guò)遠(yuǎn)程訪問(wèn)使用各種服務(wù)。這時(shí)通常采用Proxy設(shè)計(jì)模式:將接口函數(shù)定義在一個(gè)抽象類中,Server和Client都會(huì)以該抽象類為基類實(shí)現(xiàn)所有接口函數(shù),所不同的是Server端是真正的功能實(shí)現(xiàn),而Client端是對(duì)這些函數(shù)遠(yuǎn)程調(diào)用請(qǐng)求的包裝。如何將Binder和Proxy設(shè)計(jì)模式結(jié)合起來(lái)是應(yīng)用程序?qū)崿F(xiàn)面向?qū)ο驜inder通信的根本問(wèn)題。 5.1.1 Binder 在Server端的表述 – Binder實(shí)體做為Proxy設(shè)計(jì)模式的基礎(chǔ),首先定義一個(gè)抽象接口類封裝Server所有功能,其中包含一系列純虛函數(shù)留待Server和Proxy各自實(shí)現(xiàn)。由于這些函數(shù)需要跨進(jìn)程調(diào)用,須為其一一編號(hào),從而Server可以根據(jù)收到的編號(hào)決定調(diào)用哪個(gè)函數(shù)。其次就要引入Binder了。Server端定義另一個(gè)Binder抽象類處理來(lái)自Client的Binder請(qǐng)求數(shù)據(jù)包,其中最重要的成員是虛函數(shù)onTransact()。該函數(shù)分析收到的數(shù)據(jù)包,調(diào)用相應(yīng)的接口函數(shù)處理請(qǐng)求。 接下來(lái)采用繼承方式以接口類和Binder抽象類為基類構(gòu)建Binder在Server中的實(shí)體,實(shí)現(xiàn)基類里所有的虛函數(shù),包括公共接口函數(shù)以及數(shù)據(jù)包處理函數(shù):onTransact()。這個(gè)函數(shù)的輸入是來(lái)自Client的binder_transaction_data結(jié)構(gòu)的數(shù)據(jù)包。前面提到,該結(jié)構(gòu)里有個(gè)成員code,包含這次請(qǐng)求的接口函數(shù)編號(hào)。onTransact()將case-by-case地解析code值,從數(shù)據(jù)包里取出函數(shù)參數(shù),調(diào)用接口類中相應(yīng)的,已經(jīng)實(shí)現(xiàn)的公共接口函數(shù)。函數(shù)執(zhí)行完畢,如果需要返回?cái)?shù)據(jù)就再構(gòu)建一個(gè)binder_transaction_data包將返回?cái)?shù)據(jù)包填入其中。 那么各個(gè)Binder實(shí)體的onTransact()又是什么時(shí)候調(diào)用呢?這就需要驅(qū)動(dòng)參與了。前面說(shuō)過(guò),Binder實(shí)體須要以Binde傳輸結(jié)構(gòu)flat_binder_object形式發(fā)送給其它進(jìn)程才能建立Binder通信,而B(niǎo)inder實(shí)體指針就存放在該結(jié)構(gòu)的handle域中。驅(qū)動(dòng)根據(jù)Binder位置數(shù)組從傳輸數(shù)據(jù)中獲取該Binder的傳輸結(jié)構(gòu),為它創(chuàng)建位于內(nèi)核中的Binder節(jié)點(diǎn),將Binder實(shí)體指針記錄在該節(jié)點(diǎn)中。如果接下來(lái)有其它進(jìn)程向該Binder發(fā)送數(shù)據(jù),驅(qū)動(dòng)會(huì)根據(jù)節(jié)點(diǎn)中記錄的信息將Binder實(shí)體指針填入binder_transaction_data的target.ptr中返回給接收線程。接收線程從數(shù)據(jù)包中取出該指針,reinterpret_cast成Binder抽象類并調(diào)用onTransact()函數(shù)。由于這是個(gè)虛函數(shù),不同的Binder實(shí)體中有各自的實(shí)現(xiàn),從而可以調(diào)用到不同Binder實(shí)體提供的onTransact()。 5.1.2 Binder 在Client端的表述 – Binder引用做為Proxy設(shè)計(jì)模式的一部分,Client端的Binder同樣要繼承Server提供的公共接口類并實(shí)現(xiàn)公共函數(shù)。但這不是真正的實(shí)現(xiàn),而是對(duì)遠(yuǎn)程函數(shù)調(diào)用的包裝:將函數(shù)參數(shù)打包,通過(guò)Binder向Server發(fā)送申請(qǐng)并等待返回值。為此Client端的Binder還要知道Binder實(shí)體的相關(guān)信息,即對(duì)Binder實(shí)體的引用。該引用或是由SMgr轉(zhuǎn)發(fā)過(guò)來(lái)的,對(duì)實(shí)名Binder的引用或是由另一個(gè)進(jìn)程直接發(fā)送過(guò)來(lái)的,對(duì)匿名Binder的引用。 由于繼承了同樣的公共接口類,Client Binder提供了與Server Binder一樣的函數(shù)原型,使用戶感覺(jué)不出Server是運(yùn)行在本地還是遠(yuǎn)端。Client Binder中,公共接口函數(shù)的包裝方式是:創(chuàng)建一個(gè)binder_transaction_data數(shù)據(jù)包,將其對(duì)應(yīng)的編碼填入code域,將調(diào)用該函數(shù)所需的參數(shù)填入data.buffer指向的緩存中,并指明數(shù)據(jù)包的目的地,那就是已經(jīng)獲得的對(duì)Binder實(shí)體的引用,填入數(shù)據(jù)包的target.handle中。注意這里和Server的區(qū)別:實(shí)際上target域是個(gè)聯(lián)合體,包括ptr和handle兩個(gè)成員,前者用于接收數(shù)據(jù)包的Server,指向 Binder實(shí)體對(duì)應(yīng)的內(nèi)存空間;后者用于作為請(qǐng)求方的Client,存放Binder實(shí)體的引用,告知驅(qū)動(dòng)數(shù)據(jù)包將路由給哪個(gè)實(shí)體。數(shù)據(jù)包準(zhǔn)備好后,通過(guò)驅(qū)動(dòng)接口發(fā)送出去。經(jīng)過(guò)BC_TRANSACTION/BC_REPLY回合完成函數(shù)的遠(yuǎn)程調(diào)用并得到返回值。 5.2 Binder 在傳輸數(shù)據(jù)中的表述Binder可以塞在數(shù)據(jù)包的有效數(shù)據(jù)中越進(jìn)程邊界從一個(gè)進(jìn)程傳遞給另一個(gè)進(jìn)程,這些傳輸中的Binder用結(jié)構(gòu)flat_binder_object表示,如下表所示: 表 6 Binder傳輸結(jié)構(gòu):flat_binder_object
無(wú)論是Binder實(shí)體還是對(duì)實(shí)體的引用都從屬與某個(gè)進(jìn)程,所以該結(jié)構(gòu)不能透明地在進(jìn)程之間傳輸,必須經(jīng)過(guò)驅(qū)動(dòng)翻譯。例如當(dāng)Server把Binder實(shí)體傳遞給Client時(shí),在發(fā)送數(shù)據(jù)流中,flat_binder_object中的type是BINDER_TYPE_BINDER,binder指向Server進(jìn)程用戶空間地址。如果透?jìng)鹘o接收端將毫無(wú)用處,驅(qū)動(dòng)必須對(duì)數(shù)據(jù)流中的這個(gè)Binder做修改:將type該成BINDER_TYPE_HANDLE;為這個(gè)Binder在接收進(jìn)程中創(chuàng)建位于內(nèi)核中的引用并將引用號(hào)填入handle中。對(duì)于發(fā)生數(shù)據(jù)流中引用類型的Binder也要做同樣轉(zhuǎn)換。經(jīng)過(guò)處理后接收進(jìn)程從數(shù)據(jù)流中取得的Binder引用才是有效的,才可以將其填入數(shù)據(jù)包binder_transaction_data的target.handle域,向Binder實(shí)體發(fā)送請(qǐng)求。 這樣做也是出于安全性考慮:應(yīng)用程序不能隨便猜測(cè)一個(gè)引用號(hào)填入target.handle中就可以向Server請(qǐng)求服務(wù)了,因?yàn)轵?qū)動(dòng)并沒(méi)有為你在內(nèi)核中創(chuàng)建該引用,必定會(huì)被驅(qū)動(dòng)拒絕。唯有經(jīng)過(guò)身份認(rèn)證確認(rèn)合法后,由‘權(quán)威機(jī)構(gòu)’(Binder驅(qū)動(dòng))親手授予你的Binder才能使用,因?yàn)檫@時(shí)驅(qū)動(dòng)已經(jīng)在內(nèi)核中為你使用該Binder做了注冊(cè),交給你的引用號(hào)是合法的。 下表總結(jié)了當(dāng)flat_binder_object結(jié)構(gòu)穿過(guò)驅(qū)動(dòng)時(shí)驅(qū)動(dòng)所做的操作: 表 7 驅(qū)動(dòng)對(duì)flat_binder_object的操作
5.2.1 文件形式的 Binder除了通常意義上用來(lái)通信的Binder,還有一種特殊的Binder:文件Binder。這種Binder的基本思想是:將文件看成Binder實(shí)體,進(jìn)程打開(kāi)的文件號(hào)看成Binder的引用。一個(gè)進(jìn)程可以將它打開(kāi)文件的文件號(hào)傳遞給另一個(gè)進(jìn)程,從而另一個(gè)進(jìn)程也打開(kāi)了同一個(gè)文件,就象Binder的引用在進(jìn)程之間傳遞一樣。 一個(gè)進(jìn)程打開(kāi)一個(gè)文件,就獲得與該文件綁定的打開(kāi)文件號(hào)。從Binder的角度,linux在內(nèi)核創(chuàng)建的打開(kāi)文件描述結(jié)構(gòu)struct file是Binder的實(shí)體,打開(kāi)文件號(hào)是該進(jìn)程對(duì)該實(shí)體的引用。既然是Binder那么就可以在進(jìn)程之間傳遞,故也可以用flat_binder_object結(jié)構(gòu)將文件Binder通過(guò)數(shù)據(jù)包發(fā)送至其它進(jìn)程,只是結(jié)構(gòu)中type域的值為BINDER_TYPE_FD,表明該Binder是文件Binder。而結(jié)構(gòu)中的handle域則存放文件在發(fā)送方進(jìn)程中的打開(kāi)文件號(hào)。我們知道打開(kāi)文件號(hào)是個(gè)局限于某個(gè)進(jìn)程的值,一旦跨進(jìn)程就沒(méi)有意義了。這一點(diǎn)和Binder實(shí)體用戶指針或Binder引用號(hào)是一樣的,若要跨進(jìn)程同樣需要驅(qū)動(dòng)做轉(zhuǎn)換。驅(qū)動(dòng)在接收Binder的進(jìn)程空間創(chuàng)建一個(gè)新的打開(kāi)文件號(hào),將它與已有的打開(kāi)文件描述結(jié)構(gòu)struct file勾連上,從此該Binder實(shí)體又多了一個(gè)引用。新建的打開(kāi)文件號(hào)覆蓋flat_binder_object中原來(lái)的文件號(hào)交給接收進(jìn)程。接收進(jìn)程利用它可以執(zhí)行read(),write()等文件操作。 傳個(gè)文件為啥要這么麻煩,直接將文件名用Binder傳過(guò)去,接收方用open()打開(kāi)不就行了嗎?其實(shí)這還是有區(qū)別的。首先對(duì)同一個(gè)打開(kāi)文件共享的層次不同:使用文件Binder打開(kāi)的文件共享linux VFS中的struct file,struct dentry,struct inode結(jié)構(gòu),這意味著一個(gè)進(jìn)程使用read()/write()/seek()改變了文件指針,另一個(gè)進(jìn)程的文件指針也會(huì)改變;而如果兩個(gè)進(jìn)程分別使用同一文件名打開(kāi)文件則有各自的struct file結(jié)構(gòu),從而各自獨(dú)立維護(hù)文件指針,互不干擾。其次是一些特殊設(shè)備文件要求在struct file一級(jí)共享才能使用,例如android的另一個(gè)驅(qū)動(dòng)ashmem,它和Binder一樣也是misc設(shè)備,用以實(shí)現(xiàn)進(jìn)程間的共享內(nèi)存。一個(gè)進(jìn)程打開(kāi)的ashmem文件只有通過(guò)文件Binder發(fā)送到另一個(gè)進(jìn)程才能實(shí)現(xiàn)內(nèi)存共享,這大大提高了內(nèi)存共享的安全性,道理和Binder增強(qiáng)了IPC的安全性是一樣的。 5.3 Binder 在驅(qū)動(dòng)中的表述驅(qū)動(dòng)是Binder通信的核心,系統(tǒng)中所有的Binder實(shí)體以及每個(gè)實(shí)體在各個(gè)進(jìn)程中的引用都登記在驅(qū)動(dòng)中;驅(qū)動(dòng)需要記錄Binder引用->實(shí)體之間多對(duì)一的關(guān)系;為引用找到對(duì)應(yīng)的實(shí)體;在某個(gè)進(jìn)程中為實(shí)體創(chuàng)建或查找到對(duì)應(yīng)的引用;記錄Binder的歸屬地(位于哪個(gè)進(jìn)程中);通過(guò)管理Binder的強(qiáng)/弱引用創(chuàng)建/銷毀Binder實(shí)體等等。 驅(qū)動(dòng)里的Binder是什么時(shí)候創(chuàng)建的呢?前面提到過(guò),為了實(shí)現(xiàn)實(shí)名Binder的注冊(cè),系統(tǒng)必須創(chuàng)建第一只雞–為SMgr創(chuàng)建的,用于注冊(cè)實(shí)名Binder的Binder實(shí)體,負(fù)責(zé)實(shí)名Binder注冊(cè)過(guò)程中的進(jìn)程間通信。既然創(chuàng)建了實(shí)體就要有對(duì)應(yīng)的引用:驅(qū)動(dòng)將所有進(jìn)程中的0號(hào)引用都預(yù)留給該Binder實(shí)體,即所有進(jìn)程的0號(hào)引用天然地都指向注冊(cè)實(shí)名Binder專用的Binder,無(wú)須特殊操作即可以使用0號(hào)引用來(lái)注冊(cè)實(shí)名Binder。接下來(lái)隨著應(yīng)用程序不斷地注冊(cè)實(shí)名Binder,不斷向SMgr索要Binder的引用,不斷將Binder從一個(gè)進(jìn)程傳遞給另一個(gè)進(jìn)程,越來(lái)越多的Binder以傳輸結(jié)構(gòu) - flat_binder_object的形式穿越驅(qū)動(dòng)做跨進(jìn)程的遷徙。由于binder_transaction_data中data.offset數(shù)組的存在,所有流經(jīng)驅(qū)動(dòng)的Binder都逃不過(guò)驅(qū)動(dòng)的眼睛。Binder將對(duì)這些穿越進(jìn)程邊界的Binder做如下操作:檢查傳輸結(jié)構(gòu)的type域,如果是BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER則創(chuàng)建Binder的實(shí)體;如果是BINDER_TYPE_HANDLE或BINDER_TYPE_WEAK_HANDLE則創(chuàng)建Binder的引用;如果是BINDER_TYPE_HANDLE則為進(jìn)程打開(kāi)文件,無(wú)須創(chuàng)建任何數(shù)據(jù)結(jié)構(gòu)。詳細(xì)過(guò)程可參考表7。隨著越來(lái)越多的Binder實(shí)體或引用在進(jìn)程間傳遞,驅(qū)動(dòng)會(huì)在內(nèi)核里創(chuàng)建越來(lái)越多的節(jié)點(diǎn)或引用,當(dāng)然這個(gè)過(guò)程對(duì)用戶來(lái)說(shuō)是透明的。 5.3.1 Binder 實(shí)體在驅(qū)動(dòng)中的表述驅(qū)動(dòng)中的Binder實(shí)體也叫‘節(jié)點(diǎn)’,隸屬于提供實(shí)體的進(jìn)程,由struct binder_node結(jié)構(gòu)來(lái)表示: 表 8 Binder節(jié)點(diǎn)描述結(jié)構(gòu):binder_node
每個(gè)進(jìn)程都有一棵紅黑樹(shù)用于存放創(chuàng)建好的節(jié)點(diǎn),以Binder在用戶空間的指針作為索引。每當(dāng)在傳輸數(shù)據(jù)中偵測(cè)到一個(gè)代表Binder實(shí)體的flat_binder_object,先以該結(jié)構(gòu)的binder指針為索引搜索紅黑樹(shù);如果沒(méi)找到就創(chuàng)建一個(gè)新節(jié)點(diǎn)添加到樹(shù)中。由于對(duì)于同一個(gè)進(jìn)程來(lái)說(shuō)內(nèi)存地址是唯一的,所以不會(huì)重復(fù)建設(shè)造成混亂。 5.3.2 Binder 引用在驅(qū)動(dòng)中的表述和實(shí)體一樣,Binder的引用也是驅(qū)動(dòng)根據(jù)傳輸數(shù)據(jù)中的flat_binder_object創(chuàng)建的,隸屬于獲得該引用的進(jìn)程,用struct binder_ref結(jié)構(gòu)體表示: 表 9 Binder引用描述結(jié)構(gòu):binder_ref
就象一個(gè)對(duì)象有很多指針一樣,同一個(gè)Binder實(shí)體可能有很多引用,不同的是這些引用可能分布在不同的進(jìn)程中。和實(shí)體一樣,每個(gè)進(jìn)程使用紅黑樹(shù)存放所有正在使用的引用。不同的是Binder的引用可以通過(guò)兩個(gè)鍵值索引: · 對(duì)應(yīng)實(shí)體在內(nèi)核中的地址。注意這里指的是驅(qū)動(dòng)創(chuàng)建于內(nèi)核中的binder_node結(jié)構(gòu)的地址,而不是Binder實(shí)體在用戶進(jìn)程中的地址。實(shí)體在內(nèi)核中的地址是唯一的,用做索引不會(huì)產(chǎn)生二義性;但實(shí)體可能來(lái)自不同用戶進(jìn)程,而實(shí)體在不同用戶進(jìn)程中的地址可能重合,不能用來(lái)做索引。驅(qū)動(dòng)利用該紅黑樹(shù)在一個(gè)進(jìn)程中快速查找某個(gè)Binder實(shí)體所對(duì)應(yīng)的引用(一個(gè)實(shí)體在一個(gè)進(jìn)程中只建立一個(gè)引用)。 · 引用號(hào)。引用號(hào)是驅(qū)動(dòng)為引用分配的一個(gè)32位標(biāo)識(shí),在一個(gè)進(jìn)程內(nèi)是唯一的,而在不同進(jìn)程中可能會(huì)有同樣的值,這和進(jìn)程的打開(kāi)文件號(hào)很類似。引用號(hào)將返回給應(yīng)用程序,可以看作Binder引用在用戶進(jìn)程中的句柄。除了0號(hào)引用在所有進(jìn)程里都固定保留給SMgr,其它值由驅(qū)動(dòng)動(dòng)態(tài)分配。向Binder發(fā)送數(shù)據(jù)包時(shí),應(yīng)用程序?qū)⒁锰?hào)填入binder_transaction_data結(jié)構(gòu)的target.handle域中表明該數(shù)據(jù)包的目的Binder。驅(qū)動(dòng)根據(jù)該引用號(hào)在紅黑樹(shù)中找到引用的binder_ref結(jié)構(gòu),進(jìn)而通過(guò)其node域知道目標(biāo)Binder實(shí)體所在的進(jìn)程及其它相關(guān)信息,實(shí)現(xiàn)數(shù)據(jù)包的路由。 6 Binder 內(nèi)存映射和接收緩存區(qū)管理暫且撇開(kāi)Binder,考慮一下傳統(tǒng)的IPC方式中,數(shù)據(jù)是怎樣從發(fā)送端到達(dá)接收端的呢?通常的做法是,發(fā)送方將準(zhǔn)備好的數(shù)據(jù)存放在緩存區(qū)中,調(diào)用API通過(guò)系統(tǒng)調(diào)用進(jìn)入內(nèi)核中。內(nèi)核服務(wù)程序在內(nèi)核空間分配內(nèi)存,將數(shù)據(jù)從發(fā)送方緩存區(qū)復(fù)制到內(nèi)核緩存區(qū)中。接收方讀數(shù)據(jù)時(shí)也要提供一塊緩存區(qū),內(nèi)核將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到接收方提供的緩存區(qū)中并喚醒接收線程,完成一次數(shù)據(jù)發(fā)送。這種存儲(chǔ)-轉(zhuǎn)發(fā)機(jī)制有兩個(gè)缺陷:首先是效率低下,需要做兩次拷貝:用戶空間->內(nèi)核空間->用戶空間。Linux使用copy_from_user()和copy_to_user()實(shí)現(xiàn)這兩個(gè)跨空間拷貝,在此過(guò)程中如果使用了高端內(nèi)存(high memory),這種拷貝需要臨時(shí)建立/取消頁(yè)面映射,造成性能損失。其次是接收數(shù)據(jù)的緩存要由接收方提供,可接收方不知道到底要多大的緩存才夠用,只能開(kāi)辟盡量大的空間或先調(diào)用API接收消息頭獲得消息體大小,再開(kāi)辟適當(dāng)?shù)目臻g接收消息體。兩種做法都有不足,不是浪費(fèi)空間就是浪費(fèi)時(shí)間。 Binder采用一種全新策略:由Binder驅(qū)動(dòng)負(fù)責(zé)管理數(shù)據(jù)接收緩存。我們注意到Binder驅(qū)動(dòng)實(shí)現(xiàn)了mmap()系統(tǒng)調(diào)用,這對(duì)字符設(shè)備是比較特殊的,因?yàn)閙map()通常用在有物理存儲(chǔ)介質(zhì)的文件系統(tǒng)上,而象Binder這樣沒(méi)有物理介質(zhì),純粹用來(lái)通信的字符設(shè)備沒(méi)必要支持mmap()。Binder驅(qū)動(dòng)當(dāng)然不是為了在物理介質(zhì)和用戶空間做映射,而是用來(lái)創(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)存映射在用戶空間的地址,不過(guò)這段空間是由驅(qū)動(dòng)管理,用戶不必也不能直接訪問(wèn)(映射類型為PROT_READ,只讀映射)。 接收緩存區(qū)映射好后就可以做為緩存池接收和存放數(shù)據(jù)了。前面說(shuō)過(guò),接收數(shù)據(jù)包的結(jié)構(gòu)為binder_transaction_data,但這只是消息頭,真正的有效負(fù)荷位于data.buffer所指向的內(nèi)存中。這片內(nèi)存不需要接收方提供,恰恰是來(lái)自mmap()映射的這片緩存池。在數(shù)據(jù)從發(fā)送方向接收方拷貝時(shí),驅(qū)動(dòng)會(huì)根據(jù)發(fā)送數(shù)據(jù)包的大小,使用最佳匹配算法從緩存池中找到一塊大小合適的空間,將數(shù)據(jù)從發(fā)送緩存區(qū)復(fù)制過(guò)來(lái)。要注意的是,存放binder_transaction_data結(jié)構(gòu)本身以及表4中所有消息的內(nèi)存空間還是得由接收者提供,但這些數(shù)據(jù)大小固定,數(shù)量也不多,不會(huì)給接收方造成不便。映射的緩存池要足夠大,因?yàn)榻邮辗降木€程池可能會(huì)同時(shí)處理多條并發(fā)的交互,每條交互都需要從緩存池中獲取目的存儲(chǔ)區(qū),一旦緩存池耗竭將產(chǎn)生導(dǎo)致無(wú)法預(yù)期的后果。 有分配必然有釋放。接收方在處理完數(shù)據(jù)包后,就要通知驅(qū)動(dòng)釋放data.buffer所指向的內(nèi)存區(qū)。在介紹Binder協(xié)議時(shí)已經(jīng)提到,這是由命令BC_FREE_BUFFER完成的。 通過(guò)上面介紹可以看到,驅(qū)動(dòng)為接收方分擔(dān)了最為繁瑣的任務(wù):分配/釋放大小不等,難以預(yù)測(cè)的有效負(fù)荷緩存區(qū),而接收方只需要提供緩存來(lái)存放大小固定,最大空間可以預(yù)測(cè)的消息頭即可。在效率上,由于mmap()分配的內(nèi)存是映射在接收方用戶空間里的,所有總體效果就相當(dāng)于對(duì)有效負(fù)荷數(shù)據(jù)做了一次從發(fā)送方用戶空間到接收方用戶空間的直接數(shù)據(jù)拷貝,省去了內(nèi)核中暫存這個(gè)步驟,提升了一倍的性能。順便再提一點(diǎn),Linux內(nèi)核實(shí)際上沒(méi)有從一個(gè)用戶空間到另一個(gè)用戶空間直接拷貝的函數(shù),需要先用copy_from_user()拷貝到內(nèi)核空間,再用copy_to_user()拷貝到另一個(gè)用戶空間。為了實(shí)現(xiàn)用戶空間到用戶空間的拷貝,mmap()分配的內(nèi)存除了映射進(jìn)了接收方進(jìn)程里,還映射進(jìn)了內(nèi)核空間。所以調(diào)用copy_from_user()將數(shù)據(jù)拷貝進(jìn)內(nèi)核空間也相當(dāng)于拷貝進(jìn)了接收方的用戶空間,這就是Binder只需一次拷貝的‘秘密’。 7 Binder 接收線程管理Binder通信實(shí)際上是位于不同進(jìn)程中的線程之間的通信。假如進(jìn)程S是Server端,提供Binder實(shí)體,線程T1從Client進(jìn)程C1中通過(guò)Binder的引用向進(jìn)程S發(fā)送請(qǐng)求。S為了處理這個(gè)請(qǐng)求需要啟動(dòng)線程T2,而此時(shí)線程T1處于接收返回?cái)?shù)據(jù)的等待狀態(tài)。T2處理完請(qǐng)求就會(huì)將處理結(jié)果返回給T1,T1被喚醒得到處理結(jié)果。在這過(guò)程中,T2仿佛T1在進(jìn)程S中的代理,代表T1執(zhí)行遠(yuǎn)程任務(wù),而給T1的感覺(jué)就是象穿越到S中執(zhí)行一段代碼又回到了C1。為了使這種穿越更加真實(shí),驅(qū)動(dòng)會(huì)將T1的一些屬性賦給T2,特別是T1的優(yōu)先級(jí)nice,這樣T2會(huì)使用和T1類似的時(shí)間完成任務(wù)。很多資料會(huì)用‘線程遷移’來(lái)形容這種現(xiàn)象,容易讓人產(chǎn)生誤解。一來(lái)線程根本不可能在進(jìn)程之間跳來(lái)跳去,二來(lái)T2除了和T1優(yōu)先級(jí)一樣,其它沒(méi)有相同之處,包括身份,打開(kāi)文件,棧大小,信號(hào)處理,私有數(shù)據(jù)等。 對(duì)于Server進(jìn)程S,可能會(huì)有許多Client同時(shí)發(fā)起請(qǐng)求,為了提高效率往往開(kāi)辟線程池并發(fā)處理收到的請(qǐng)求。怎樣使用線程池實(shí)現(xiàn)并發(fā)處理呢?這和具體的IPC機(jī)制有關(guān)。拿socket舉例,Server端的socket設(shè)置為偵聽(tīng)模式,有一個(gè)專門的線程使用該socket偵聽(tīng)來(lái)自Client的連接請(qǐng)求,即阻塞在accept()上。這個(gè)socket就象一只會(huì)生蛋的雞,一旦收到來(lái)自Client的請(qǐng)求就會(huì)生一個(gè)蛋 – 創(chuàng)建新socket并從accept()返回。偵聽(tīng)線程從線程池中啟動(dòng)一個(gè)工作線程并將剛下的蛋交給該線程。后續(xù)業(yè)務(wù)處理就由該線程完成并通過(guò)這個(gè)單與Client實(shí)現(xiàn)交互。 可是對(duì)于Binder來(lái)說(shuō),既沒(méi)有偵聽(tīng)模式也不會(huì)下蛋,怎樣管理線程池呢?一種簡(jiǎn)單的做法是,不管三七二十一,先創(chuàng)建一堆線程,每個(gè)線程都用BINDER_WRITE_READ命令讀Binder。這些線程會(huì)阻塞在驅(qū)動(dòng)為該Binder設(shè)置的等待隊(duì)列上,一旦有來(lái)自Client的數(shù)據(jù)驅(qū)動(dòng)會(huì)從隊(duì)列中喚醒一個(gè)線程來(lái)處理。這樣做簡(jiǎn)單直觀,省去了線程池,但一開(kāi)始就創(chuàng)建一堆線程有點(diǎn)浪費(fèi)資源。于是Binder協(xié)議引入了專門命令或消息幫助用戶管理線程池,包括: · INDER_SET_MAX_THREADS · BC_REGISTER_LOOP · BC_ENTER_LOOP · BC_EXIT_LOOP · BR_SPAWN_LOOPER 首先要管理線程池就要知道池子有多大,應(yīng)用程序通過(guò)INDER_SET_MAX_THREADS告訴驅(qū)動(dòng)最多可以創(chuàng)建幾個(gè)線程。以后每個(gè)線程在創(chuàng)建,進(jìn)入主循環(huán),退出主循環(huán)時(shí)都要分別使用BC_REGISTER_LOOP,BC_ENTER_LOOP,BC_EXIT_LOOP告知驅(qū)動(dòng),以便驅(qū)動(dòng)收集和記錄當(dāng)前線程池的狀態(tài)。每當(dāng)驅(qū)動(dòng)接收完數(shù)據(jù)包返回讀Binder的線程時(shí),都要檢查一下是不是已經(jīng)沒(méi)有閑置線程了。如果是,而且線程總數(shù)不會(huì)超出線程池最大線程數(shù),就會(huì)在當(dāng)前讀出的數(shù)據(jù)包后面再追加一條BR_SPAWN_LOOPER消息,告訴用戶線程即將不夠用了,請(qǐng)?jiān)賳?dòng)一些,否則下一個(gè)請(qǐng)求可能不能及時(shí)響應(yīng)。新線程一啟動(dòng)又會(huì)通過(guò)BC_xxx_LOOP告知驅(qū)動(dòng)更新?tīng)顟B(tài)。這樣只要線程沒(méi)有耗盡,總是有空閑線程在等待隊(duì)列中隨時(shí)待命,及時(shí)處理請(qǐng)求。 關(guān)于工作線程的啟動(dòng),Binder驅(qū)動(dòng)還做了一點(diǎn)小小的優(yōu)化。當(dāng)進(jìn)程P1的線程T1向進(jìn)程P2發(fā)送請(qǐng)求時(shí),驅(qū)動(dòng)會(huì)先查看一下線程T1是否也正在處理來(lái)自P2某個(gè)線程請(qǐng)求但尚未完成(沒(méi)有發(fā)送回復(fù))。這種情況通常發(fā)生在兩個(gè)進(jìn)程都有Binder實(shí)體并互相對(duì)發(fā)時(shí)請(qǐng)求時(shí)。假如驅(qū)動(dòng)在進(jìn)程P2中發(fā)現(xiàn)了這樣的線程,比如說(shuō)T2,就會(huì)要求T2來(lái)處理T1的這次請(qǐng)求。因?yàn)門2既然向T1發(fā)送了請(qǐng)求尚未得到返回包,說(shuō)明T2肯定(或?qū)?huì))阻塞在讀取返回包的狀態(tài)。這時(shí)候可以讓T2順便做點(diǎn)事情,總比等在那里閑著好。而且如果T2不是線程池中的線程還可以為線程池分擔(dān)部分工作,減少線程池使用率。 8 數(shù)據(jù)包接收隊(duì)列與(線程)等待隊(duì)列管理通常數(shù)據(jù)傳輸?shù)慕邮斩擞袃蓚€(gè)隊(duì)列:數(shù)據(jù)包接收隊(duì)列和(線程)等待隊(duì)列,用以緩解供需矛盾。當(dāng)超市里的進(jìn)貨(數(shù)據(jù)包)太多,貨物會(huì)堆積在倉(cāng)庫(kù)里;購(gòu)物的人(線程)太多,會(huì)排隊(duì)等待在收銀臺(tái),道理是一樣的。在驅(qū)動(dòng)中,每個(gè)進(jìn)程有一個(gè)全局的接收隊(duì)列,也叫to-do隊(duì)列,存放不是發(fā)往特定線程的數(shù)據(jù)包;相應(yīng)地有一個(gè)全局等待隊(duì)列,所有等待從全局接收隊(duì)列里收數(shù)據(jù)的線程在該隊(duì)列里排隊(duì)。每個(gè)線程有自己私有的to-do隊(duì)列,存放發(fā)送給該線程的數(shù)據(jù)包;相應(yīng)的每個(gè)線程都有各自私有等待隊(duì)列,專門用于本線程等待接收自己to-do隊(duì)列里的數(shù)據(jù)。雖然名叫隊(duì)列,其實(shí)線程私有等待隊(duì)列中最多只有一個(gè)線程,即它自己。 由于發(fā)送時(shí)沒(méi)有特別標(biāo)記,驅(qū)動(dòng)怎么判斷哪些數(shù)據(jù)包該送入全局to-do隊(duì)列,哪些數(shù)據(jù)包該送入特定線程的to-do隊(duì)列呢?這里有兩條規(guī)則。規(guī)則1:Client發(fā)給Server的請(qǐng)求數(shù)據(jù)包都提交到Server進(jìn)程的全局to-do隊(duì)列。不過(guò)有個(gè)特例,就是上節(jié)談到的Binder對(duì)工作線程啟動(dòng)的優(yōu)化。經(jīng)過(guò)優(yōu)化,來(lái)自T1的請(qǐng)求不是提交給P2的全局to-do隊(duì)列,而是送入了T2的私有to-do隊(duì)列。規(guī)則2:對(duì)同步請(qǐng)求的返回?cái)?shù)據(jù)包(由BC_REPLY發(fā)送的包)都發(fā)送到發(fā)起請(qǐng)求的線程的私有to-do隊(duì)列中。如上面的例子,如果進(jìn)程P1的線程T1發(fā)給進(jìn)程P2的線程T2的是同步請(qǐng)求,那么T2返回的數(shù)據(jù)包將送進(jìn)T1的私有to-do隊(duì)列而不會(huì)提交到P1的全局to-do隊(duì)列。 數(shù)據(jù)包進(jìn)入接收隊(duì)列的潛規(guī)則也就決定了線程進(jìn)入等待隊(duì)列的潛規(guī)則,即一個(gè)線程只要不接收返回?cái)?shù)據(jù)包則應(yīng)該在全局等待隊(duì)列中等待新任務(wù),否則就應(yīng)該在其私有等待隊(duì)列中等待Server的返回?cái)?shù)據(jù)。還是上面的例子,T1在向T2發(fā)送同步請(qǐng)求后就必須等待在它私有等待隊(duì)列中,而不是在P1的全局等待隊(duì)列中排隊(duì),否則將得不到T2的返回的數(shù)據(jù)包。 這些潛規(guī)則是驅(qū)動(dòng)對(duì)Binder通信雙方施加的限制條件,體現(xiàn)在應(yīng)用程序上就是同步請(qǐng)求交互過(guò)程中的線程一致性:1) Client端,等待返回包的線程必須是發(fā)送請(qǐng)求的線程,而不能由一個(gè)線程發(fā)送請(qǐng)求包,另一個(gè)線程等待接收包,否則將收不到返回包;2) Server端,發(fā)送對(duì)應(yīng)返回?cái)?shù)據(jù)包的線程必須是收到請(qǐng)求數(shù)據(jù)包的線程,否則返回的數(shù)據(jù)包將無(wú)法送交發(fā)送請(qǐng)求的線程。這是因?yàn)榉祷財(cái)?shù)據(jù)包的目的Binder不是用戶指定的,而是驅(qū)動(dòng)記錄在收到請(qǐng)求數(shù)據(jù)包的線程里,如果發(fā)送返回包的線程不是收到請(qǐng)求包的線程驅(qū)動(dòng)將無(wú)從知曉返回包將送往何處。 接下來(lái)探討一下Binder驅(qū)動(dòng)是如何遞交同步交互和異步交互的。我們知道,同步交互和異步交互的區(qū)別是同步交互的請(qǐng)求端(client)在發(fā)出請(qǐng)求數(shù)據(jù)包后須要等待應(yīng)答端(Server)的返回?cái)?shù)據(jù)包,而異步交互的發(fā)送端發(fā)出請(qǐng)求數(shù)據(jù)包后交互即結(jié)束。對(duì)于這兩種交互的請(qǐng)求數(shù)據(jù)包,驅(qū)動(dòng)可以不管三七二十一,統(tǒng)統(tǒng)丟到接收端的to-do隊(duì)列中一個(gè)個(gè)處理。但驅(qū)動(dòng)并沒(méi)有這樣做,而是對(duì)異步交互做了限流,令其為同步交互讓路,具體做法是:對(duì)于某個(gè)Binder實(shí)體,只要有一個(gè)異步交互沒(méi)有處理完畢,例如正在被某個(gè)線程處理或還在任意一條to-do隊(duì)列中排隊(duì),那么接下來(lái)發(fā)給該實(shí)體的異步交互包將不再投遞到to-do隊(duì)列中,而是阻塞在驅(qū)動(dòng)為該實(shí)體開(kāi)辟的異步交互接收隊(duì)列(Binder節(jié)點(diǎn)的async_todo域)中,但這期間同步交互依舊不受限制直接進(jìn)入to-do隊(duì)列獲得處理。一直到該異步交互處理完畢下一個(gè)異步交互方可以脫離異步交互隊(duì)列進(jìn)入to-do隊(duì)列中。之所以要這么做是因?yàn)橥浇换サ恼?qǐng)求端需要等待返回包,必須迅速處理完畢以免影響請(qǐng)求端的響應(yīng)速度,而異步交互屬于‘發(fā)射后不管’,稍微延時(shí)一點(diǎn)不會(huì)阻塞其它線程。所以用專門隊(duì)列將過(guò)多的異步交互暫存起來(lái),以免突發(fā)大量異步交互擠占Server端的處理能力或耗盡線程池里的線程,進(jìn)而阻塞同步交互。 9 總結(jié)Binder使用Client-Server通信方式,安全性好,簡(jiǎn)單高效,再加上其面向?qū)ο蟮脑O(shè)計(jì)思想,獨(dú)特的接收緩存管理和線程池管理方式,成為Android進(jìn)程間通信的中流砥柱。 |
|