乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Android Bander設(shè)計(jì)與實(shí)現(xiàn)

       WUCANADA 2017-12-13

      關(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ù)

      IPC

      數(shù)據(jù)拷貝次數(shù)

      共享內(nèi)存

      0

      Binder

      1

      Socket/管道/消息隊(duì)列

      2

      還有一點(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 IPC

      Binder使用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é)中一一提到。

      binder_overview

      圖 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通信命令字

      命令

      含義

      arg

      BINDER_WRITE_READ

      該命令向Binder寫入或讀取數(shù)據(jù)。參數(shù)分為兩段:寫部分和讀部分。如果write_size不為0就先將write_buffer里的數(shù)據(jù)寫入Binder;如果read_size不為0再?gòu)腂inder中讀取數(shù)據(jù)存入read_buffer中。write_consumed和read_consumed表示操作完成時(shí)Binder驅(qū)動(dòng)實(shí)際寫入或讀出的數(shù)據(jù)個(gè)數(shù)。

      struct binder_write_read {

      signed long write_size;

      signed long write_consumed;

      unsigned long write_buffer;

      signed long read_size;

      signed long read_consumed;

      unsigned long read_buffer;

      };

      BINDER_SET_MAX_THREADS

      該命令告知Binder驅(qū)動(dòng)接收方(通常是Server端)線程池中最大的線程數(shù)。由于Client是并發(fā)向Server端發(fā)送請(qǐng)求的,Server端必須開(kāi)辟線程池為這些并發(fā)請(qǐng)求提供服務(wù)。告知驅(qū)動(dòng)線程池的最大值是為了讓驅(qū)動(dòng)發(fā)現(xiàn)線程數(shù)達(dá)到該值時(shí)不要再命令接收端啟動(dòng)新的線程。

      int max_threads;

      BINDER_SET_CONTEXT_MGR

      將當(dāng)前進(jìn)程注冊(cè)為SMgr。系統(tǒng)中同時(shí)只能存在一個(gè)SMgr。只要當(dāng)前的SMgr沒(méi)有調(diào)用close()關(guān)閉Binder驅(qū)動(dòng)就不能有別的進(jìn)程可以成為SMgr。

      ---

      BINDER_THREAD_EXIT

      通知Binder驅(qū)動(dòng)當(dāng)前線程退出了。Binder會(huì)為所有參與Binder通信的線程(包括Server線程池中的線程和Client發(fā)出請(qǐng)求的線程)建立相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。這些線程在退出時(shí)必須通知驅(qū)動(dòng)釋放相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。

      ---

      BINDER_VERSION

      獲得Binder驅(qū)動(dòng)的版本號(hào)。

      ---

      這其中最常用的命令是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寫操作命令字

      cmd

      含義

      arg

      BC_TRANSACTION
      BC_REPLY

      BC_TRANSACTION用于Client向Server發(fā)送請(qǐng)求數(shù)據(jù);BC_REPLY用于Server向Client發(fā)送回復(fù)(應(yīng)答)數(shù)據(jù)。其后面緊接著一個(gè)binder_transaction_data結(jié)構(gòu)體表明要寫入的數(shù)據(jù)。

      struct binder_transaction_data

      BC_ACQUIRE_RESULT
      BC_ATTEMPT_ACQUIRE

      暫未實(shí)現(xiàn)

      ---

      BC_FREE_BUFFER

      釋放一塊映射的內(nèi)存。Binder接收方通過(guò)mmap()映射一塊較大的內(nèi)存空間,Binder驅(qū)動(dòng)基于這片內(nèi)存采用最佳匹配算法實(shí)現(xiàn)接收數(shù)據(jù)緩存的動(dòng)態(tài)分配和釋放,滿足并發(fā)請(qǐng)求對(duì)接收緩存區(qū)的需求。應(yīng)用程序處理完這片數(shù)據(jù)后必須盡快使用該命令釋放緩存區(qū),否則會(huì)因?yàn)榫彺鎱^(qū)耗盡而無(wú)法接收新數(shù)據(jù)。

      指向需要釋放的緩存區(qū)的指針;該指針位于收到的Binder數(shù)據(jù)包中

      BC_INCREFS
      BC_ACQUIRE
      BC_RELEASE
      BC_DECREFS

      這組命令增加或減少Binder的引用計(jì)數(shù),用以實(shí)現(xiàn)強(qiáng)指針或弱指針的功能。

      32位Binder引用號(hào)

      BC_INCREFS_DONE
      BC_ACQUIRE_DONE

      第一次增加Binder實(shí)體引用計(jì)數(shù)時(shí),驅(qū)動(dòng)向Binder實(shí)體所在的進(jìn)程發(fā)送BR_INCREFS, BR_ACQUIRE消息;Binder實(shí)體所在的進(jìn)程處理完畢回饋BC_INCREFS_DONE,BC_ACQUIRE_DONE

      void *ptr:Binder實(shí)體在用戶空間中的指針

      void *cookie:與該實(shí)體相關(guān)的附加數(shù)據(jù)

      BC_REGISTER_LOOPER
      BC_ENTER_LOOPER
      BC_EXIT_LOOPER

      這組命令同BINDER_SET_MAX_THREADS一道實(shí)現(xiàn)Binder驅(qū)動(dòng)對(duì)接收方線程池管理。BC_REGISTER_LOOPER通知驅(qū)動(dòng)線程池中一個(gè)線程已經(jīng)創(chuàng)建了;BC_ENTER_LOOPER通知驅(qū)動(dòng)該線程已經(jīng)進(jìn)入主循環(huán),可以接收數(shù)據(jù);BC_EXIT_LOOPER通知驅(qū)動(dòng)該線程退出主循環(huán),不再接收數(shù)據(jù)。

      ---

      BC_REQUEST_DEATH_NOTIFICATION

      獲得Binder引用的進(jìn)程通過(guò)該命令要求驅(qū)動(dòng)在Binder實(shí)體銷毀得到通知。雖說(shuō)強(qiáng)指針可以確保只要有引用就不會(huì)銷毀實(shí)體,但這畢竟是個(gè)跨進(jìn)程的引用,誰(shuí)也無(wú)法保證實(shí)體由于所在的Server關(guān)閉Binder驅(qū)動(dòng)或異常退出而消失,引用者能做的是要求Server在此刻給出通知。

      uint32 *ptr; 需要得到死亡通知的Binder引用

      void **cookie: 與死亡通知相關(guān)的信息,驅(qū)動(dòng)會(huì)在發(fā)出死亡通知時(shí)返回給發(fā)出請(qǐng)求的進(jìn)程。

      BC_DEAD_BINDER_DONE

      收到實(shí)體死亡通知書(shū)的進(jìn)程在刪除引用后用本命令告知驅(qū)動(dòng)。

      void **cookie

      在這些命令中,最常用的是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ù)

      BR_ERROR

      發(fā)生內(nèi)部錯(cuò)誤(如內(nèi)存分配失敗)

      ---

      BR_OK
      BR_NOOP

      操作完成

      ---

      BR_SPAWN_LOOPER

      該消息用于接收方線程池管理。當(dāng)驅(qū)動(dòng)發(fā)現(xiàn)接收方所有線程都處于忙碌狀態(tài)且線程池里的線程總數(shù)沒(méi)有超過(guò)BINDER_SET_MAX_THREADS設(shè)置的最大線程數(shù)時(shí),向接收方發(fā)送該命令要求創(chuàng)建更多線程以備接收數(shù)據(jù)。

      ---

      BR_TRANSACTION
      BR_REPLY

      這兩條消息分別對(duì)應(yīng)發(fā)送方的BC_TRANSACTION和BC_REPLY,表示當(dāng)前接收的數(shù)據(jù)是請(qǐng)求還是回復(fù)。

      binder_transaction_data

      BR_ACQUIRE_RESULT
      BR_ATTEMPT_ACQUIRE
      BR_FINISHED

      尚未實(shí)現(xiàn)

      ---

      BR_DEAD_REPLY

      交互過(guò)程中如果發(fā)現(xiàn)對(duì)方進(jìn)程或線程已經(jīng)死亡則返回該消息

      ---

      BR_TRANSACTION_COMPLETE

      發(fā)送方通過(guò)BC_TRANSACTION或BC_REPLY發(fā)送完一個(gè)數(shù)據(jù)包后,都能收到該消息做為成功發(fā)送的反饋。這和BR_REPLY不一樣,是驅(qū)動(dòng)告知發(fā)送方已經(jīng)發(fā)送成功,而不是Server端返回請(qǐng)求數(shù)據(jù)。所以不管同步還是異步交互接收方都能獲得本消息。

      ---

      BR_INCREFS
      BR_ACQUIRE
      BR_RELEASE
      BR_DECREFS

      這一組消息用于管理強(qiáng)/弱指針的引用計(jì)數(shù)。只有提供Binder實(shí)體的進(jìn)程才能收到這組消息。

      void *ptr:Binder實(shí)體在用戶空間中的指針

      void *cookie:與該實(shí)體相關(guān)的附加數(shù)據(jù)

      BR_DEAD_BINDER
      BR_CLEAR_DEATH_NOTIFICATION_DONE

      向獲得Binder引用的進(jìn)程發(fā)送Binder實(shí)體死亡通知書(shū);收到死亡通知書(shū)的進(jìn)程接下來(lái)會(huì)返回BC_DEAD_BINDER_DONE做確認(rèn)。

      void **cookie:在使用BC_REQUEST_DEATH_NOTIFICATION注冊(cè)死亡通知時(shí)的附加參數(shù)。

      BR_FAILED_REPLY

      如果發(fā)送非法引用號(hào)則返回該消息

      ---

      和寫數(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

      成員

      含義

      union {

      size_t handle;

      void *ptr;

      } target;

      對(duì)于發(fā)送數(shù)據(jù)包的一方,該成員指明發(fā)送目的地。由于目的是在遠(yuǎn)端,所以這里填入的是對(duì)Binder實(shí)體的引用,存放在target.handle中。如前述,Binder的引用在代碼中也叫句柄(handle)。

      當(dāng)數(shù)據(jù)包到達(dá)接收方時(shí),驅(qū)動(dòng)已將該成員修改成Binder實(shí)體,即指向Binder對(duì)象內(nèi)存的指針,使用target.ptr來(lái)獲得。該指針是接收方在將Binder實(shí)體傳輸給其它進(jìn)程時(shí)提交給驅(qū)動(dòng)的,驅(qū)動(dòng)程序能夠自動(dòng)將發(fā)送方填入的引用轉(zhuǎn)換成接收方Binder對(duì)象的指針,故接收方可以直接將其當(dāng)做對(duì)象指針來(lái)使用(通常是將其reinterpret_cast成相應(yīng)類)。

      void *cookie;

      發(fā)送方忽略該成員;接收方收到數(shù)據(jù)包時(shí),該成員存放的是創(chuàng)建Binder實(shí)體時(shí)由該接收方自定義的任意數(shù)值,做為與Binder指針相關(guān)的額外信息存放在驅(qū)動(dòng)中。驅(qū)動(dòng)基本上不關(guān)心該成員。

      unsigned int code;

      該成員存放收發(fā)雙方約定的命令碼,驅(qū)動(dòng)完全不關(guān)心該成員的內(nèi)容。通常是Server端定義的公共接口函數(shù)的編號(hào)。

      unsigned int flags;

      與交互相關(guān)的標(biāo)志位,其中最重要的是TF_ONE_WAY位。如果該位置上表明這次交互是異步的,Server端不會(huì)返回任何數(shù)據(jù)。驅(qū)動(dòng)利用該位來(lái)決定是否構(gòu)建與返回有關(guān)的數(shù)據(jù)結(jié)構(gòu)。另外一位TF_ACCEPT_FDS是出于安全考慮,如果發(fā)起請(qǐng)求的一方不希望在收到的回復(fù)中接收文件形式的Binder可以將該位置上。因?yàn)槭盏揭粋€(gè)文件形式的Binder會(huì)自動(dòng)為數(shù)據(jù)接收方打開(kāi)一個(gè)文件,使用該位可以防止打開(kāi)文件過(guò)多。

      pid_t sender_pid;

      uid_t sender_euid;

      該成員存放發(fā)送方的進(jìn)程ID和用戶ID,由驅(qū)動(dòng)負(fù)責(zé)填入,接收方可以讀取該成員獲知發(fā)送方的身份。

      size_t data_size;

      該成員表示data.buffer指向的緩沖區(qū)存放的數(shù)據(jù)長(zhǎng)度。發(fā)送數(shù)據(jù)時(shí)由發(fā)送方填入,表示即將發(fā)送的數(shù)據(jù)長(zhǎng)度;在接收方用來(lái)告知接收到數(shù)據(jù)的長(zhǎng)度。

      size_t offsets_size;

      驅(qū)動(dòng)一般情況下不關(guān)心data.buffer里存放什么數(shù)據(jù),但如果有Binder在其中傳輸則需要將其相對(duì)data.buffer的偏移位置指出來(lái)讓驅(qū)動(dòng)知道。有可能存在多個(gè)Binder同時(shí)在數(shù)據(jù)中傳遞,所以須用數(shù)組表示所有偏移位置。本成員表示該數(shù)組的大小。

      union {

      struct {

      const void *buffer;

      const void *offsets;

      } ptr;

      uint8_t buf[8];

      } data;

      data.bufer存放要發(fā)送或接收到的數(shù)據(jù);data.offsets指向Binder偏移位置數(shù)組,該數(shù)組可以位于data.buffer中,也可以在另外的內(nèi)存空間中,并無(wú)限制。buf[8]是為了無(wú)論保證32位還是64位平臺(tái),成員data的大小都是8個(gè)字節(jié)。

      這里有必要再?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在什么位置。

      binder_proto

      圖 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

      成員

      含義

      unsigned long type

      表明該Binder的類型,包括以下幾種:

      BINDER_TYPE_BINDER:表示傳遞的是Binder實(shí)體,并且指向該實(shí)體的引用都是強(qiáng)類型;

      BINDER_TYPE_WEAK_BINDER:表示傳遞的是Binder實(shí)體,并且指向該實(shí)體的引用都是弱類型;

      BINDER_TYPE_HANDLE:表示傳遞的是Binder強(qiáng)類型的引用

      BINDER_TYPE_WEAK_HANDLE:表示傳遞的是Binder弱類型的引用

      BINDER_TYPE_FD:表示傳遞的是文件形式的Binder,詳見(jiàn)下節(jié)

      unsigned long flags

      該域只對(duì)第一次傳遞Binder實(shí)體時(shí)有效,因?yàn)榇丝舔?qū)動(dòng)需要在內(nèi)核中創(chuàng)建相應(yīng)的實(shí)體節(jié)點(diǎn),有些參數(shù)需要從該域取出:

      第0-7位:代碼中用FLAT_BINDER_FLAG_PRIORITY_MASK取得,表示處理本實(shí)體請(qǐng)求數(shù)據(jù)包的線程的最低優(yōu)先級(jí)。當(dāng)一個(gè)應(yīng)用程序提供多個(gè)實(shí)體時(shí),可以通過(guò)該參數(shù)調(diào)整分配給各個(gè)實(shí)體的處理能力。

      第8位:代碼中用FLAT_BINDER_FLAG_ACCEPTS_FDS取得,置1表示該實(shí)體可以接收其它進(jìn)程發(fā)過(guò)來(lái)的文件形式的Binder。由于接收文件形式的Binder會(huì)在本進(jìn)程中自動(dòng)打開(kāi)文件,有些Server可以用該標(biāo)志禁止該功能,以防打開(kāi)過(guò)多文件。

      union {

      void *binder;

      signed long handle;

      };

      當(dāng)傳遞的是Binder實(shí)體時(shí)使用binder域,指向Binder實(shí)體在應(yīng)用程序中的地址。

      當(dāng)傳遞的是Binder引用時(shí)使用handle域,存放Binder在進(jìn)程中的引用號(hào)。

      void *cookie;

      該域只對(duì)Binder實(shí)體有效,存放與該Binder有關(guān)的附加信息。

      無(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的操作

      Binder 類型( type 域)

      在發(fā)送方的操作

      在接收方的操作

      BINDER_TYPE_BINDER

      BINDER_TYPE_WEAK_BINDER

      只有實(shí)體所在的進(jìn)程能發(fā)送該類型的Binder。如果是第一次發(fā)送驅(qū)動(dòng)將創(chuàng)建實(shí)體在內(nèi)核中的節(jié)點(diǎn),并保存binder,cookie,flag域。

      如果是第一次接收該Binder則創(chuàng)建實(shí)體在內(nèi)核中的引用;將handle域替換為新建的引用號(hào);將type域替換為BINDER_TYPE_(WEAK_)HANDLE

      BINDER_TYPE_HANDLE

      BINDER_TYPE_WEAK_HANDLE

      獲得Binder引用的進(jìn)程都能發(fā)送該類型Binder。驅(qū)動(dòng)根據(jù)handle域提供的引用號(hào)查找建立在內(nèi)核的引用。如果找到說(shuō)明引用號(hào)合法,否則拒絕該發(fā)送請(qǐng)求。

      如果收到的Binder實(shí)體位于接收進(jìn)程中:將ptr域替換為保存在節(jié)點(diǎn)中的binder值;cookie替換為保存在節(jié)點(diǎn)中的cookie值;type替換為BINDER_TYPE_(WEAK_)BINDER。

      如果收到的Binder實(shí)體不在接收進(jìn)程中:如果是第一次接收則創(chuàng)建實(shí)體在內(nèi)核中的引用;將handle域替換為新建的引用號(hào)

      BINDER_TYPE_FD

      驗(yàn)證handle域中提供的打開(kāi)文件號(hào)是否有效,無(wú)效則拒絕該發(fā)送請(qǐng)求。

      在接收方創(chuàng)建新的打開(kāi)文件號(hào)并將其與提供的打開(kāi)文件描述結(jié)構(gòu)綁定。

      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

      成員

      含義

      int debug_id;

      用于調(diào)試

      struct binder_work work;

      當(dāng)本節(jié)點(diǎn)引用計(jì)數(shù)發(fā)生改變,需要通知所屬進(jìn)程時(shí),通過(guò)該成員掛入所屬進(jìn)程的to-do隊(duì)列里,喚醒所屬進(jìn)程執(zhí)行Binder實(shí)體引用計(jì)數(shù)的修改。

      union {

      struct rb_node rb_node;

      struct hlist_node dead_node;

      };

      每個(gè)進(jìn)程都維護(hù)一棵紅黑樹(shù),以Binder實(shí)體在用戶空間的指針,即本結(jié)構(gòu)的ptr成員為索引存放該進(jìn)程所有的Binder實(shí)體。這樣驅(qū)動(dòng)可以根據(jù)Binder實(shí)體在用戶空間的指針很快找到其位于內(nèi)核的節(jié)點(diǎn)。rb_node用于將本節(jié)點(diǎn)鏈入該紅黑樹(shù)中。

      銷毀節(jié)點(diǎn)時(shí)須將rb_node從紅黑樹(shù)中摘除,但如果本節(jié)點(diǎn)還有引用沒(méi)有切斷,就用dead_node將節(jié)點(diǎn)隔離到另一個(gè)鏈表中,直到通知所有進(jìn)程切斷與該節(jié)點(diǎn)的引用后,該節(jié)點(diǎn)才可能被銷毀。

      struct binder_proc *proc;

      本成員指向節(jié)點(diǎn)所屬的進(jìn)程,即提供該節(jié)點(diǎn)的進(jìn)程。

      struct hlist_head refs;

      本成員是隊(duì)列頭,所有指向本節(jié)點(diǎn)的引用都鏈接在該隊(duì)列里。這些引用可能隸屬于不同的進(jìn)程。通過(guò)該隊(duì)列可以遍歷指向該節(jié)點(diǎn)的所有引用。

      int internal_strong_refs;

      用以實(shí)現(xiàn)強(qiáng)指針的計(jì)數(shù)器:產(chǎn)生一個(gè)指向本節(jié)點(diǎn)的強(qiáng)引用該計(jì)數(shù)就會(huì)加1。

      int local_weak_refs;

      驅(qū)動(dòng)為傳輸中的Binder設(shè)置的弱引用計(jì)數(shù)。如果一個(gè)Binder打包在數(shù)據(jù)包中從一個(gè)進(jìn)程發(fā)送到另一個(gè)進(jìn)程,驅(qū)動(dòng)會(huì)為該Binder增加引用計(jì)數(shù),直到接收進(jìn)程通過(guò)BC_FREE_BUFFER通知驅(qū)動(dòng)釋放該數(shù)據(jù)包的數(shù)據(jù)區(qū)為止。

      int local_strong_refs;

      驅(qū)動(dòng)為傳輸中的Binder設(shè)置的強(qiáng)引用計(jì)數(shù)。同上。

      void __user *ptr;

      指向用戶空間Binder實(shí)體的指針,來(lái)自于flat_binder_object的binder成員

      void __user *cookie;

      指向用戶空間的附加指針,來(lái)自于flat_binder_object的cookie成員

      unsigned has_strong_ref;

      unsigned pending_strong_ref;

      unsigned has_weak_ref;

      unsigned pending_weak_ref

      這一組標(biāo)志用于控制驅(qū)動(dòng)與Binder實(shí)體所在進(jìn)程交互式修改引用計(jì)數(shù)

      unsigned has_async_transaction;

      該成員表明該節(jié)點(diǎn)在to-do隊(duì)列中有異步交互尚未完成。驅(qū)動(dòng)將所有發(fā)送往接收端的數(shù)據(jù)包暫存在接收進(jìn)程或線程開(kāi)辟的to-do隊(duì)列里。對(duì)于異步交互,驅(qū)動(dòng)做了適當(dāng)流控:如果to-do隊(duì)列里有異步交互尚待處理則該成員置1,這將導(dǎo)致新到的異步交互存放在本結(jié)構(gòu)成員 – asynch_todo隊(duì)列中,而不直接送到to-do隊(duì)列里。目的是為同步交互讓路,避免長(zhǎng)時(shí)間阻塞發(fā)送端。

      unsigned accept_fds

      表明節(jié)點(diǎn)是否同意接受文件方式的Binder,來(lái)自flat_binder_object中flags成員的FLAT_BINDER_FLAG_ACCEPTS_FDS位。由于接收文件Binder會(huì)為進(jìn)程自動(dòng)打開(kāi)一個(gè)文件,占用有限的文件描述符,節(jié)點(diǎn)可以設(shè)置該位拒絕這種行為。

      int min_priority

      設(shè)置處理Binder請(qǐng)求的線程的最低優(yōu)先級(jí)。發(fā)送線程將數(shù)據(jù)提交給接收線程處理時(shí),驅(qū)動(dòng)會(huì)將發(fā)送線程的優(yōu)先級(jí)也賦予接收線程,使得數(shù)據(jù)即使跨了進(jìn)程也能以同樣優(yōu)先級(jí)得到處理。不過(guò)如果發(fā)送線程優(yōu)先級(jí)過(guò)低,接收線程將以預(yù)設(shè)的最小值運(yùn)行。

      該域的值來(lái)自于flat_binder_object中flags成員。

      struct list_head async_todo

      異步交互等待隊(duì)列;用于分流發(fā)往本節(jié)點(diǎn)的異步交互包

      每個(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

      成員

      含義

      int debug_id;

      調(diào)試用

      struct rb_node rb_node_desc;

      每個(gè)進(jìn)程有一棵紅黑樹(shù),進(jìn)程所有引用以引用號(hào)(即本結(jié)構(gòu)的desc域)為索引添入該樹(shù)中。本成員用做鏈接到該樹(shù)的一個(gè)節(jié)點(diǎn)。

      struct rb_node rb_node_node;

      每個(gè)進(jìn)程又有一棵紅黑樹(shù),進(jìn)程所有引用以節(jié)點(diǎn)實(shí)體在驅(qū)動(dòng)中的內(nèi)存地址(即本結(jié)構(gòu)的node域)為所引添入該樹(shù)中。本成員用做鏈接到該樹(shù)的一個(gè)節(jié)點(diǎn)。

      struct hlist_node node_entry;

      該域?qū)⒈疽米鰹楣?jié)點(diǎn)鏈入所指向的Binder實(shí)體結(jié)構(gòu)binder_node中的refs隊(duì)列

      struct binder_proc *proc;

      本引用所屬的進(jìn)程

      struct binder_node *node;

      本引用所指向的節(jié)點(diǎn)(Binder實(shí)體)

      uint32_t desc;

      本結(jié)構(gòu)的引用號(hào)

      int strong;

      強(qiáng)引用計(jì)數(shù)

      int weak;

      弱引用計(jì)數(shù)

      struct binder_ref_death *death;

      應(yīng)用程序向驅(qū)動(dòng)發(fā)送BC_REQUEST_DEATH_NOTIFICATION或BC_CLEAR_DEATH_NOTIFICATION命令從而當(dāng)Binder實(shí)體銷毀時(shí)能夠收到來(lái)自驅(qū)動(dòng)的提醒。該域不為空表明用戶訂閱了對(duì)應(yīng)實(shí)體銷毀的‘噩耗’。

      就象一個(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)程間通信的中流砥柱。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多