前言
本文基于 Android S。
Binder 是什么
Android 設(shè)計(jì)了一個(gè)輕量級(jí)的進(jìn)程間通信機(jī)制,也稱 遠(yuǎn)程調(diào)用機(jī)制,Binder 是這個(gè)機(jī)制中的 遠(yuǎn)程對(duì)象 的基礎(chǔ)類。
即,Binder 對(duì)象實(shí)現(xiàn)了一些接口,供遠(yuǎn)程進(jìn)程調(diào)用。
為了被遠(yuǎn)程進(jìn)程調(diào)用,它必須遵循某種定義好的協(xié)議,這個(gè)協(xié)議為 IBinder。
同時(shí),為了使遠(yuǎn)程進(jìn)程有統(tǒng)一調(diào)用其方法的方式,Android 規(guī)定它實(shí)現(xiàn)的接口必須繼承自 IInterface。
google 官網(wǎng) - Binder:Base class for a remotable object, the core part of a lightweight remote procedure call mechanism defined by IBinder.

其核心是如何被遠(yuǎn)程進(jìn)程調(diào)用,即,其遵循的由 IBinder 接口描述的協(xié)議。這個(gè)協(xié)議由 RPC 協(xié)議衍變而來。
RPC 協(xié)議
一個(gè)通用的 RPC 框架實(shí)現(xiàn)如下:

其重點(diǎn)是:代理模式 和 傳輸。
代理模式指在 Client 使用 Server 端接口對(duì)象的代理。這樣 Client 端在調(diào)用接口時(shí),無需關(guān)注 RPC 實(shí)現(xiàn),和調(diào)用本地對(duì)象的方法一樣使用就可以。

傳輸即 RPC 協(xié)議定義的東西。為了使兩端進(jìn)程能理解對(duì)方的 方法/參數(shù),我們需要定義 序列化/反序列化 及 打包/解包 的標(biāo)準(zhǔn)。就 Binder 而言,因?yàn)橹虚g涉及到 Java 代碼和 C++ 代碼的轉(zhuǎn)換,所以我們?cè)趥鬏斨氨仨毎?參數(shù)/返回值類型 序列化成 JNI 可以理解的基礎(chǔ)類型。

Android 中封裝了一個(gè) Parcel 類來負(fù)責(zé) 序列化/反序列化 及 打包/解包。
通常 RPC 框架里面還會(huì)涉及到一個(gè)服務(wù)中心,所有意圖公開給所有用戶使用的服務(wù)都在里面注冊(cè),然后想使用這個(gè)服務(wù)的客戶端從中取出服務(wù)來使用。
Binder 架構(gòu)中的 ServiceManager#addService/ServiceManager#getService 采用的就是這個(gè)設(shè)計(jì)。

線程遷移
線程遷移指進(jìn)程間的 IPC 看起來像是,Client 端的線程跳到 Server 端執(zhí)行代碼,再帶著結(jié)果跳回來。
但實(shí)際上,Binder IPC 機(jī)制,并不是使用線程遷移來實(shí)現(xiàn),而是采用一種模擬線程遷移的方式。
Binder 系統(tǒng)的用戶空間代碼在它運(yùn)行的每個(gè)進(jìn)程中維護(hù)了一個(gè)線程池,這個(gè)線程池中的線程用來處理來自其它進(jìn)程的 IPC。內(nèi)核模塊通過以下手段模擬線程遷移:在分派 IPC 時(shí)跨進(jìn)程傳遞線程優(yōu)先級(jí),并確保如果 IPC 遞歸回原始進(jìn)程,由其原始線程處理。

Binder IPC 機(jī)制中的 RPC 架構(gòu)
先看下總的流程圖:

我們可以看到 Client 端調(diào)用 Binder 代理的方法,方法和參數(shù)最后被封裝后傳遞給了 Server 端,Server 端再把方法和參數(shù)解析出來,調(diào)用真正的 Binder 對(duì)象執(zhí)行方法,再把結(jié)果返回給 Client 端。
其中一次 RPC 的來回在 Android 的 Binder 機(jī)制中被稱為 transaction(事務(wù))。

其中的核心是如何把封裝好的數(shù)據(jù)從 Client 端傳到 Server 端。
Binder 機(jī)制中的數(shù)據(jù)傳輸
Binder 機(jī)制中的數(shù)據(jù)傳輸,是通過內(nèi)存映射來實(shí)現(xiàn)的。這個(gè)內(nèi)存映射機(jī)制由位于內(nèi)核中的 Binder 驅(qū)動(dòng)實(shí)現(xiàn)。

可以看到映射的重點(diǎn)為 Server 進(jìn)程中的 binder_mmap() 方法。
binder_mmap() 方法對(duì)應(yīng)的代碼在 binder.c 中。
其主要流程如圖:

Binder 的服務(wù)中心
上面的數(shù)據(jù)傳輸中有個(gè)問題,即,Client 進(jìn)程如何知道 Server 進(jìn)程 binder_proc->buffer 所指向的物理地址空間?
答案是,通過一個(gè)服務(wù)中心:ServiceManager 進(jìn)程實(shí)現(xiàn)。

當(dāng)愿意公開自己的 Server 通過 Binder 驅(qū)動(dòng)向服務(wù)中心 ServiceManager 注冊(cè)自己時(shí),Binder 驅(qū)動(dòng)會(huì)做以下事情:
- 為這個(gè) Server 創(chuàng)建其對(duì)應(yīng)的 binder_node,binder_node 中包含了 binder_proc 對(duì)象;
- 創(chuàng)建與 binder_node 對(duì)應(yīng)的 binder_ref,并向服務(wù)中心注冊(cè) [服務(wù)名稱,binder_ref 對(duì)象]。
當(dāng) Client 需要獲取 Server 時(shí),只需要使用 服務(wù)名稱 通過 Binder 驅(qū)動(dòng)向服務(wù)中心查找該服務(wù)名稱對(duì)應(yīng)的 binder_ref 對(duì)象即可。服務(wù)中心會(huì)通過 Binder 驅(qū)動(dòng)把 Server 對(duì)應(yīng)的 binder_ref 返回給 Client 進(jìn)程。
上面過程有個(gè)問題,即,Binder 驅(qū)動(dòng)是如何知道發(fā)送給它的請(qǐng)求是需要轉(zhuǎn)交給 ServiceManager 進(jìn)程的呢?
答,開機(jī)時(shí),ServiceManager 會(huì)向 Binder 驅(qū)動(dòng)設(shè)置自己為上下文管理者。其它進(jìn)程只要把命令發(fā)送給上下文管理者就可以了。

創(chuàng)建 ServiceManager 的 BBinder,并設(shè)置為 Binder 驅(qū)動(dòng)的上下文管理者
BBinder: Server 進(jìn)程中,native 代碼中的 本地(local) Binder;
BpBinder: Client 進(jìn)程中,native 代碼中的 遠(yuǎn)端(remote) Binder,即 Binder 代理;
IInterfaceImpl.Stub: Server 進(jìn)程中,Java 代碼中的本地 Binder;
IInterfaceImpl.Stub.Proxy: Client 進(jìn)程中,Java 代碼中的 Binder 代理。
開機(jī)時(shí),ServiceManager 會(huì)向 Binder 驅(qū)動(dòng)設(shè)置自己為上下文管理者,并把自己加入到 ServiceManager 的 [name, BBinder] Map 中。

在 Java 代碼中獲取 ServiceManager(獲取 BpBinder 在 Java 層的對(duì)象)

具體流程如下:

公共 Server 向 ServiceManager 注冊(cè)自己

簡單概括為:
- Server 進(jìn)程新建一個(gè)要公開的 Binder,Java 層為 Stub 對(duì)象,native 層為 JavaBBinder 對(duì)象;
- 把 Binder 對(duì)象放入 Parcel 中;
- 調(diào)用 ServiceManager 的 Binder 代理(BpBinder)的 addService() 方法;
- 通過 Binder 驅(qū)動(dòng)把請(qǐng)求轉(zhuǎn)發(fā)給 ServiceManager 的本地 Binder(ServiceManager 的 BBinder);
- 從 Parcel 中解析出封裝的要做為公開 Server 的 BBinder 對(duì)象,并創(chuàng)建其 BpBinder;
- 并加入到 ServiceManager 的 map 中。

具體流程如下:

Client 向 ServiceManager 獲取一個(gè)公共 Server

其主要過程和添加 Server 大致一致,唯一不一樣的是,transact 后會(huì)返回從 Parcel reply 中讀取出來的 BinderProxy:

其具體代碼流程為:

取出公開的 Server 后,調(diào)用其 BinderProxy 接口完成一個(gè)普通的 RPC 流程的例子

匿名 Server 的獲取
一個(gè)匿名 Binder Server 與實(shí)名 Server 的差異主要就在于后者是通過 Service Manager 來獲取對(duì)它的引用;而前者則是以其它實(shí)名 Server 為中介來傳遞這一引用信息,僅此而已。另外,對(duì)于 Binder 驅(qū)動(dòng)而言,只要 “路過” 它且以前沒有出現(xiàn)過的 Binder 對(duì)象,都會(huì)被記錄下來。
—— 林學(xué)森《深入理解 Android 內(nèi)核設(shè)計(jì)思想》

我們以 bindService 為例:
- 客戶端應(yīng)用調(diào)用 IActivityManager#bindService 時(shí)傳入 IServiceConnection;
- IActivityManager 在做 RPC 時(shí)就創(chuàng)建了 IServiceConnection 的本地 Binder 和其遠(yuǎn)程代理 Binder;
- IActivityManager 的本地 Binder 實(shí)現(xiàn)者 AMS 在執(zhí)行 bindService 時(shí)收到了 IServiceConnection 的 Binder 代理 BinderProxy,并通過 asInterface 接口把 BinderProxy 轉(zhuǎn)換成 IServiceConnection 接口即 IServiceConnection.Stub.Proxy;
- AMS 啟動(dòng) Service 成功后,調(diào)用 IServiceConnection#connected 把 Service.Stub 放入 Parcel;
- RPC 過程中會(huì)為 Service 創(chuàng)建本地 Binder 和 遠(yuǎn)程 Binder 代理;
- 客戶端應(yīng)用的 IServiceConnection 本地 Binder 在執(zhí)行 connected() 方法時(shí)可以獲得 Service Binder 代理 IInterfaceImpl.Stub.Proxy;
- 客戶端應(yīng)用通過 IInterfaceImpl.Stub.Proxy 調(diào)用 Service 的方法。
總結(jié)
Binder 是 Android 實(shí)現(xiàn)的遠(yuǎn)程過程調(diào)用機(jī)制中的遠(yuǎn)程對(duì)象,它提供一系列接口給遠(yuǎn)程進(jìn)程使用。Server 端實(shí)現(xiàn)接口,Client 端調(diào)用接口。Server 端創(chuàng)建本地 Binder 并通過 Binder 驅(qū)動(dòng)把 Binder 代理提供給 Client 端。Client 端在調(diào)用接口時(shí)把封裝好的 方法名、參數(shù)、返回值、RPC 類型 放入 Server 進(jìn)程的內(nèi)核物理空間,因?yàn)樵撐锢砜臻g同時(shí)被映射到了 Server 進(jìn)程的用戶虛擬地址空間,所以 Server 進(jìn)程的專為 RPC 調(diào)用而創(chuàng)建的線程可以直接從內(nèi)核物理空間取出封裝好的數(shù)據(jù)。數(shù)據(jù)取出后,Server 進(jìn)程的本地 Binder 將數(shù)據(jù)解包,并執(zhí)行其實(shí)現(xiàn)方法。
參考鏈接:
OpenBinder 官網(wǎng)之 Binder IPC 機(jī)制
google 官網(wǎng):Binder
林學(xué)森《深入理解 Android 內(nèi)核設(shè)計(jì)思想》
skywangkw:Android Binder機(jī)制(三) ServiceManager守護(hù)進(jìn)程
skywangkw:Android Binder機(jī)制(一) Binder的設(shè)計(jì)和框架
小林coding:內(nèi)存管理
《Linux 是怎樣工作的》
gityuan:徹底理解 Android Binder 通信架構(gòu)
gityuan:Binder 系列10—總結(jié)
原創(chuàng)文章,歡迎轉(zhuǎn)載,但請(qǐng)注明出處。