介紹
1,首先進程間的通信方式:管道、消息隊列氧苍、共享內(nèi)存夜矗、信號量、信號让虐、socket套接字紊撕、信號等
2, binder 的優(yōu)勢:
- 拷貝次數(shù):Binder數(shù)據(jù)拷貝只需要一次,而管道赡突、消息隊列对扶、Socket都需要2次,但共享內(nèi)存方式一次內(nèi)存拷貝都不需要惭缰;從性能角度看浪南,Binder性能僅次于共享內(nèi)存。
- 穩(wěn)定性:基于C/S架構漱受,職能分工明確
- 安全性: Android系統(tǒng)中對外只暴露Client端络凿,Client端將任務發(fā)送給Server端,Server端會根據(jù)權限控制策略昂羡,判斷UID/PID是否滿足訪問權限絮记。
- 架構:在Zygote孵化出system_server進程后,在system_server進程中出初始化支持整個Android framework的各種各樣的Service虐先,而這些Service從大的方向來劃分怨愤,分為Java層Framework和Native Framework層(C++)的Service,幾乎都是基于BInder IPC機制蛹批。
Java framework:作為Server端繼承(或間接繼承)于Binder類撰洗,Client端繼承(或間接繼承)于 BinderProxy類篮愉。例如 ActivityManagerService(用于控制Activity、Service差导、進程等) 這個服務作為Server端试躏,間接繼承Binder類,而相應的ActivityManager作為Client端柿汛,間接繼承于BinderProxy類冗酿。
Native Framework層:這是C++層,作為Server端繼承(或間接繼承)于BBinder類络断,Client端繼承(或間接繼承)于BpBinder裁替。例如MediaPlayService(用于多媒體相關)作為Server端,繼承于BBinder類貌笨,而相應的MediaPlay作為Client端弱判,間接繼承于BpBinder類.
通信
跨進程通信是需要內(nèi)核空間做支持的。傳統(tǒng)的 IPC 機制如管道锥惋、Socket 都是內(nèi)核的一部分昌腰,因此通過內(nèi)核支持來實現(xiàn)進程間通信自然是沒問題的。
但是 Binder 并不是 Linux 系統(tǒng)內(nèi)核的一部分膀跌,通過 Linux 的動態(tài)內(nèi)核可加載模塊(Loadable Kernel Module遭商,LKM)的機制。
模塊是具有獨立功能的程序捅伤,它可以被單獨編譯劫流,但是不能獨立運行。它在運行時被鏈接到內(nèi)核作為內(nèi)核的一部分運行丛忆。這樣祠汇,Android 系統(tǒng)就可以通過動態(tài)添加一個內(nèi)核模塊運行在內(nèi)核空間,用戶進程之間通過這個內(nèi)核模塊作為橋梁來實現(xiàn)通信熄诡。在 Android 系統(tǒng)中可很,這個模塊就是 Binder 驅動(Binder Dirver)
Binder IPC 機制中涉及到的內(nèi)存映射通過 mmap() 來實現(xiàn),mmap() 是操作系統(tǒng)中一種內(nèi)存映射的方法凰浮。內(nèi)存映射簡單的講就是將用戶空間的一塊內(nèi)存區(qū)域映射到內(nèi)核空間我抠。映射關系建立后,用戶對這塊內(nèi)存區(qū)域的修改可以直接反應到內(nèi)核空間袜茧;反之內(nèi)核空間對這段區(qū)域的修改也能直接反應到用戶空間屿良。內(nèi)存映射能減少數(shù)據(jù)拷貝次數(shù),實現(xiàn)用戶空間和內(nèi)核空間的高效互動惫周。兩個空間各自的修改能直接反映在映射的內(nèi)存區(qū)域,從而被對方空間及時感知康栈。也正因為如此递递,內(nèi)存映射能夠提供對進程間通信的支持喷橙。
原理
進程中的用戶區(qū)域是不能直接和物理設備打交道的,如果想要把磁盤上的數(shù)據(jù)讀取到進程的用戶區(qū)域登舞,需要兩次拷貝(磁盤-->內(nèi)核空間-->用戶空間)贰逾;通常在這種場景下 mmap() 就能發(fā)揮作用,通過在物理介質(zhì)和用戶空間之間建立映射菠秒,減少數(shù)據(jù)的拷貝次數(shù)疙剑,用內(nèi)存讀寫取代I/O讀寫,提高文件讀取效率践叠。
一次完整的 Binder IPC 通信過程通常如下:
1, 首先 Binder 驅動在內(nèi)核空間創(chuàng)建一個數(shù)據(jù)接收緩存區(qū)言缤;
2, 接著在內(nèi)核空間開辟一塊內(nèi)核緩存區(qū),建立內(nèi)核緩存區(qū)和數(shù)據(jù)接收緩存區(qū)之間的映射關系禁灼,以及數(shù)據(jù)接收緩存區(qū)和接收進程用戶空間地址的映射關系管挟;
3, 發(fā)送方進程通過系統(tǒng)調(diào)用 copyfromuser() 將數(shù)據(jù) copy 到內(nèi)核中的內(nèi)核緩存區(qū),由于內(nèi)核緩存區(qū)和接收進程的用戶空間存在內(nèi)存映射弄捕,因此也就相當于把數(shù)據(jù)發(fā)送到了接收進程的用戶空間僻孝,這樣便完成了一次進程間的通信。
通信模型
Binder 是基于 C/S 架構的棺亭,由Client钦购、Server热芹、ServiceManager、Binder 驅動 四部分組成荞雏。其中 Client、Server譬猫、Service Manager 運行在用戶空間讯檐,Binder 驅動運行在內(nèi)核空間。其中 Service Manager 和 Binder 驅動由系統(tǒng)提供染服,而 Client别洪、Server 由應用程序來實現(xiàn)。Client柳刮、Server 和 ServiceManager 均是通過系統(tǒng)調(diào)用 open挖垛、mmap 和 ioctl 來訪問設備文件 /dev/binder,從而實現(xiàn)與 Binder 驅動的交互來間接的實現(xiàn)跨進程通信秉颗。
Binder 驅動
Binder 驅動是整個通信的核心痢毒;驅動負責進程之間 Binder 通信的建立,Binder 在進程之間的傳遞蚕甥,Binder 引用計數(shù)管理哪替,數(shù)據(jù)包在進程之間的傳遞和交互等一系列底層支持。
ServiceManager 與 Server Binder
Server 創(chuàng)建了 Binder菇怀,并為它起一個字符形式凭舶,可讀易記得名字晌块,將這個 Binder 實體連同名字一起以數(shù)據(jù)包的形式通過 Binder 驅動發(fā)送給 ServiceManager ,通知 ServiceManager 注冊 Binder帅霜。驅動為這個穿越進程邊界的 Binder 創(chuàng)建位于內(nèi)核中的實體節(jié)點以及 ServiceManager 對實體的引用匆背,將名字以及新建的引用打包傳給 ServiceManager。ServiceManger 收到數(shù)據(jù)后從中取出名字和引用填入查找表身冀。
我們知道钝尸,ServierManager 是一個進程,Server 是另一個進程搂根,Server 向 ServiceManager 中注冊 Binder 必然涉及到進程間通信珍促。ServiceManager 和其他進程同樣采用 Binder 通信,ServiceManager 是 Server 端兄墅,有自己的 Binder 實體踢星,其他進程都是 Client。ServiceManager 提供的 Binder 比較特殊隙咸,它沒有名字也不需要注冊沐悦。當一個進程使用 BINDERSETCONTEXT_MGR 命令將自己注冊成 ServiceManager 時Binder 驅動會自動為它創(chuàng)建 Binder 實體。其次這個 Binder 實體的引用在所有 Client 中都固定為 0 而無需通過其它手段獲得五督。也就是說藏否,一個 Server 想要向 ServiceManager 注冊自己的 Binder 就必須通過這個 0 號引用和 ServiceManager 的 Binder 通信。
ServiceManager 與 Client Binder
Server 向 ServiceManager 中注冊了 Binder 以后充包, Client 就能通過名字獲得 Binder 的引用了副签。Client 也利用保留的 0 號引用向 ServiceManager 請求訪問某個 Binder,ServiceManager 收到這個請求后從請求數(shù)據(jù)包中取出 Binder 名稱基矮,在查找表里找到對應的條目淆储,取出對應的 Binder 引用作為回復發(fā)送給發(fā)起請求的 Client。從面向對象的角度看家浇,Server 中的 Binder 實體現(xiàn)在有兩個引用:一個位于 ServiceManager 中本砰,一個位于發(fā)起請求的 Client 中。如果接下來有更多的 Client 請求該 Binder钢悲,系統(tǒng)中就會有更多的引用指向該 Binder 点额。
大致總結出 Binder 通信過程:
1, 一個進程使用 BINDERSETCONTEXT_MGR 命令通過 Binder 驅動將自己注冊成為 ServiceManager;
2, Server 通過驅動向 ServiceManager 中注冊 Binder(Server 中的 Binder 實體)莺琳,表明可以對外提供服務还棱。驅動為這個 Binder 創(chuàng)建位于內(nèi)核中的實體節(jié)點以及 ServiceManager 對實體的引用,將名字以及新建的引用打包傳給 ServiceManager惭等,ServiceManger 將其填入查找表珍手。
3, Client 通過名字,在 Binder 驅動的幫助下從 ServiceManager 中獲取到對 Binder 實體的引用,通過這個引用就能實現(xiàn)和 Server 進程的通信琳要。
其中是從綠色的箭頭開始料扰,server -> Binder 驅動 -> ServiceManager , Client -> Binder 驅動 -> ServiceManager -> Binder驅動 -> Client
- Binder客戶端或者服務端向Binder Driver發(fā)送的命令都是以BC_開頭,例如本文的BC_TRANSACTION和BC_REPLY, 所有Binder Driver向Binder客戶端或者服務端發(fā)送的命令則都是以BR_開頭, 例如本文中的BR_TRANSACTION和BR_REPLY.
- 只有當BC_TRANSACTION或者BC_REPLY時, 才調(diào)用binder_transaction()來處理事務. 并且都會回應調(diào)用者一個BINDER_WORK_TRANSACTION_COMPLETE事務, 經(jīng)過binder_thread_read()會轉變成BR_TRANSACTION_COMPLETE.
- 在A端向B寫完數(shù)據(jù)之后,A會返回給自己一個BR_TRANSACTION_COMPLETE命令焙蹭,告知自己數(shù)據(jù)已經(jīng)成功寫入到B的Binder內(nèi)核空間中去了,如果是需要回復嫂伞,在處理完 BR_TRANSACTION_COMPLETE 命令后會繼續(xù)阻塞等待結果的返回
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult){
...
while (1) {
if ((err=talkWithDriver()) < NO_ERROR) break;
cmd = mIn.readInt32();
switch (cmd) {
<!--關鍵點1 -->
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;
<!--關鍵點2 -->
case BR_REPLY:
{
binder_transaction_data tr;
// free buffer孔厉,先設置數(shù)據(jù),直接
if (reply) {
if ((tr.flags & TF_STATUS_CODE) == 0) {
// 牽扯到數(shù)據(jù)利用帖努,與內(nèi)存釋放
reply->ipcSetDataReference(...)
}
goto finish;
}
finish:
...
return err;
}
客戶端通過talkWithDriver等待結果返回撰豺,如果沒有返回值,直接break拼余,否則會執(zhí)行到關鍵點2污桦,就上圖來說,就是發(fā)送了 BR_TRANSACTION匙监,而不會有 BC_REPLY凡橱。
Android 對 Binder 的支持
由于Android 的app都是從Zygote進程fork出來的,Zygote.forkAndSpecialize()用來 fork 新進程亭姥,通過RuntimeInit.nativeZygoteInit來初始化一些環(huán)境稼钩,通過 runSelectLoop來循環(huán)監(jiān)聽 socket,等待fork請求达罗。
首先坝撑,ProcessState::self()函數(shù)會調(diào)用open()打開/dev/binder設備,這個時候能夠作為Client通過Binder進行遠程通信粮揉;其次巡李,proc->startThreadPool()負責新建一個binder線程,監(jiān)聽Binder設備扶认,這樣進程就具備了作為Binder服務端的資格侨拦。每個APP的進程都會通過onZygoteInit打開Binder,既能作為Client蝠引,也能作為Server阳谍,這就是Android進程天然支持Binder通信的原因。
問:Android APP有多少Binder線程螃概,是固定的么
答:Android APP上層應用的進程一般是開啟一個Binder線程矫夯,而對于SystemServer或者media服務等使用頻率高,服務復雜的進程吊洼,一般都是開啟兩個或者更多训貌。驅動會根據(jù)目標進程中是否存在足夠多的Binder線程來告訴進程是不是要新建Binder線程,所以是不固定的
int main(int argc, char** argv)
{ ...
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
參考:
https://zhuanlan.zhihu.com/p/35519585
http://gityuan.com/2014/01/01/binder-gaishu/
https://juejin.im/post/58c90816a22b9d006413f624