1. 進程間通信的本質(2個進程)
用戶空間的進程如果想相互通信, 必須經過內核, 因為不同進程的用戶地址空間是獨立的, 但是共享同一個內核空間.
內核為了支持進程間通信, 一般會有一個驅動, 以字符設備的形式存在(也可以是其它形式, 這個驅動的本質就是在不同的進程間傳遞數據). 兩個進程間通信一般以client/server的形式進行, 大致流程如下:
server進程打開內核設備節點, 並開始監聽該節點是否有數據寫入;
client進程打開內核設備節點, 往這個節點里面寫入一個數據(copy_from_user);
內核驅動喚醒阻塞在該節點上的server進程, 並把數據傳遞給它(copy_to_user);
server進程收到數據, 解析數據, 然后執行相應的動作.
在Android中, binder驅動充當上述流程中的內核驅動, 它是一個字符設備, 設備節點是/dev/binder.
為了便於理解, 我們先假設整個系統只有2個進程, 一個是Client進程, 另外一個是Server進程. 它們通信的圖示如下:
1.1 ProcessState & IPCThreadState
為了方便進程間通信, Android封裝了兩個類: frameworks/native/libs/binder/ProcessState.cpp 和 frameworks/native/libs/binder/IPCThreadState.cpp.
這兩個cpp文件里面既包含了Server進程需要用到的一些接口, 也包含了Client進程需要用到的一些接口(額.., 不清楚Android為啥不分開?). 當這兩個文件被編譯到Server進程時, 它們就是Server進程的一部分; 當被編譯到Client進程時, 就是Client進程的一部分.
ProcessState提供的接口主要有:
ProcessState::self() : 主要目的是open(/dev/binder), Server/Client都會用到, 因為它們都得打開內核設備節點, 讀寫數據
ProcessState::startThreadPool() : Server進程會用到, 主要目的是創建一個線程, 在線程里面while循環, 監聽是否有數據寫入設備節點.
IPCThreadState的主要目的是通過ioctl與/dev/binder交互, 也就是說它負責讀寫設備節點,完成用戶空間與內核空間的數據交互. 主要接口有:
IPCThreadState::transact : Client進程會用到, 目的是往設備節點寫入數據.
transact里面會分waitForResponse(reply)和waitForResponse(NULL)這兩種情況, 前者的意思是Client發送一個數據(請求/命令...)給Server后, 需要等待Server的執行結果, 結果存儲在reply中; 后者的意思是Client只是發送個請求, 不需要等待對應的執行結果.
waitForResponse會調用talkWithDriver, 后者會調用ioctl與/dev/binder交互
IPCThreadState::joinThreadPool : Server進程會用到, ProcessState::startThreadPool()最終就是調用的IPCThreadState::joinThreadPool, 目的就是監聽設備節點. joinThreadPool會調用getAndExecuteCommand(), 后者會調用talkWithDriver()監聽設備節點, 當收到一個數據后, 就解析並執行相應的操作(ExecuteCommand).
加上ProcessState & IPCThreadState后, 圖示變成了下面這個樣子:
1.2 IBinder, BBinder, BpBinder, BpRefBase
為了更進一步封裝, Android系統抽象出了IBinder, BBinder, BpBinder,BpRefBase這4個類. 抽象出這4個類的原因不僅僅是為了進一步封裝, 更是為了多個進程間的通信, 由於本章暫時只討論2個進程, 所以關於多進程的細節后文在說, 本章只考慮雙進程的情況下, 這4個類扮演什么樣的角色.
這3個類的頭文件以及實現文件如下:
IBinder : frameworks/native/include/binder/IBinder.h
IBinder是一個接口類, 也就是C++中的純虛基類. 它是BBinder和BpBinder的基類, 它里面定義的一個重要接口函數是transact.
BpBinder : frameworks/native/include/binder/BpBinder.h
實現文件 : frameworks/native/libs/binder/BpBinder.cpp
BpBinder繼承了IBinder, 實現了IBinder中定義的transact函數.
BpBinder用於Client端, BpBinder::transact會調用IPCThreadState::transact, 繼而往/dev/binder節點寫入數據, 完成封裝動作.
Client端的代碼會繼承BpBinder, 當Client想往Server發送數據時, 直接調用BpBinder:: transact函數即可? 但實際上並不是這樣, Client端的代碼實際上會繼承BpRefBase, 通過BpRefBase調用BpBinder:: transact函數. 至於Android系統為什么要這么設計, 我也不清楚原因.
BpRefBase : frameworks/native/include/binder/Binder.h
實現文件 : frameworks/native/libs/binder/Binder.cpp
BpRefBase里面定義了一個變量 : IBinder* const mRemote; 這個mRemote實際上指的就是BpBinder.
BpRefBase用於Client端, Client端的代碼會繼承BpRefBase, 當Client端想往Server發送數據時, 會使用mRemote->transact()的形式, 實際上也就是調用BpBinder:: transact
BBinder : frameworks/native/include/binder/Binder.h
實現文件 : frameworks/native/libs/binder/Binder.cpp
BBinder繼承了IBinder, 也實現了IBinder中定義的transact函數.前文我們說過Server端會開啟一個線程, 監聽/dev/binder節點. 當收到數據時, Server端的這個線程會調用BBinder::transact.
BBinder用於Server端, BBinder除了繼承了IBinder中的transact接口, 它自己也定義了一個onTransact接口, Server端的代碼一般會實現onTransact接口. BBinder::transact里面會調用onTransact函數, 繼而把Client端通過內核傳遞過來的數據交給Server端的代碼進行處理, 從而完成一次進程間通信.
加上IBinder, BBinder, BpBinder, BpRefBase后, 圖示變成了下面這個樣子:
1.3 Ixxx, BpInterface, BnInterface
前文我們看到了Android系統提供的很多封裝, 通過這些封裝, 進程與進程之間的數據管道已經建立完畢了, 類似於網絡傳輸中的數據鏈路層已經准備好了, 此時Client和Server已經可以盡情的傳遞數據了.
但依舊還有一個問題需要考慮: 類似於網絡傳輸中的應用層協議, Server/Client端也需要約定相應的協議, 例如Client端和Server端事先約定”消息打印”服務的cmd id為0, 當Client端想要Server端printf一個消息時, 就會發送數字0給Server, Server收到0后知道應該去執行打印服務.
這種”應用”層的協議Android系統沒法幫你封裝, 需要Server/Client編寫者自己去約定, 不過Android(更確切的說是C++)提供了一種機制, 讓程序員可以更加方便的完成這種”約定”.
這種機制就是程序員事先定義一個接口類Ixxx, 在Ixxx中定義一個枚舉類型, 代表cmd id; 然后定義相應的接口函數, 每個接口函數會對應一個cmd id. 例如如下這個接口類:
//frameworks/native/include/binder/IPermissionController.h
class IPermissionController : public IInterface
{
public:
DECLARE_META_INTERFACE(PermissionController); //暫時忽略, 這個地方也很重要, 不過主要是跟多進程間的通信相關
virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid) = 0;
virtual void getPackagesForUid(const uid_t uid, Vector<String16> &packages) = 0;
virtual bool isRuntimePermission(const String16& permission) = 0;
enum {
CHECK_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
GET_PACKAGES_FOR_UID_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 1,
IS_RUNTIME_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 2
};
};
然后Client端的代碼會繼承Ixxx & BpRefBase, 實現Ixxx中定義的接口函數, 在接口函數中會調用mRemote->transact函數, 繼而把數據寫往/dev/binder節點.
在Android系統中, 幾乎所有Client端的代碼都會雙繼承Ixxx & BpRefBase, 因此Android系統定義了一個模板類BpInterface, 方便這種雙繼承的動作.
下面我們看看代碼中BpInterface的定義以及IPermissionController的客戶端代碼:
//frameworks/native/include/binder/IInterface.h
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
BpInterface(const sp<IBinder>& remote);
protected:
virtual IBinder* onAsBinder();
};
//frameworks/native/libs/binder/IPermissionController.cpp
class BpPermissionController : public BpInterface<IPermissionController>
{
public:
BpPermissionController(const sp<IBinder>& impl)
: BpInterface<IPermissionController>(impl)
{
}
virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid)
{
Parcel data, reply;
data.writeInterfaceToken(IPermissionController::getInterfaceDescriptor());
data.writeString16(permission);
data.writeInt32(pid);
data.writeInt32(uid);
//remote()調用會返回mRemote這個變量
remote()->transact(CHECK_PERMISSION_TRANSACTION, data, &reply);
// fail on exception
if (reply.readExceptionCode() != 0) return 0;
return reply.readInt32() != 0;
}
......
}
再來看看Server端, Server端的代碼會繼承Ixxx & BBinder. 首先會實現BBinder中定義的onTransact函數, 在onTransact函數會中解析/dev/binder節點傳上來的數據, 並根據不同的數據調用Ixxx中定義的不同的接口函數. 而這些接口函數的實現都是在Server端的代碼中, Server端的代碼會實現Ixxx中定義的所有的接口函數.
類似Client端, Server端的代碼幾乎都會雙繼承Ixxx & BBinder, 為了方便這種雙繼承動作, Android系統定義了一個模板類BnInterface.
相關實例代碼如下:
//frameworks/native/include/binder/IInterface.h
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual const String16& getInterfaceDescriptor() const;
protected:
virtual IBinder* onAsBinder();
};
//frameworks/native/include/binder/IPermissionController.h
class BnPermissionController : public BnInterface<IPermissionController>
{
public:
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
//frameworks/native/libs/binder/IPermissionController.cpp
status_t BnPermissionController::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch(code) {
case CHECK_PERMISSION_TRANSACTION: {
CHECK_INTERFACE(IPermissionController, data, reply);
String16 permission = data.readString16();
int32_t pid = data.readInt32();
int32_t uid = data.readInt32();
bool res = checkPermission(permission, pid, uid);
reply->writeNoException();
reply->writeInt32(res ? 1 : 0);
return NO_ERROR;
} break;
......
}
注意, BnPermissionController不能被實例化哦, 因此它繼承了IPermissionController, 但是IPermissionController中定義的checkPermission這個存虛函數在BnPermissionController這個類中還是沒有定義
一般情況下, 我們還需要寫一個Server端的cpp文件, 定義一個類, 繼承BnPermissionController, 然后實現Ixxx中所有的存虛函數, 然后就萬事大吉了.
前述的這些封裝是為了實現面向對象的通信方式而對協議層的封裝, 至此管道,協議都已經准備好, Client端和Server端就可以用面向對象的方式進行通信了.
2. 多進程通信
在第一章我們假設系統中只有2個進程, 這種情況下很多事情都變得簡單了, 例如: client端只管打開/dev/binder節點, 然后往節點傳送數據; server端只管監聽/dev/binder節點, 收到數據后就開始解析數據, 執行相應的服務.
但是真實系統中會有很多個進程, client端在往/dev/binder節點寫入數據時, 必須告訴binder驅動這些數據該送往哪個server進程; 同樣, 所有的server進程都會監聽/dev/binder節點, 這種監聽動作其實就是block在/dev/binder節點上, 理想的情況應該是只有收到與本server相關的數據時, server進程才會被un-block並開始解析數據.
如何解決這個問題? Binder系統采用的辦法簡單來說就是“handle”! 這個”handle”其實是一個int32型的整數, 類似於linux中的文件描述符”fd”.
在linux中, 當我們想與一個文件交互時, 調用open得到此文件的fd, 后面操作此fd即可, 也就是說fd就代表這個文件.
在binder中, 當client想與某個service通信時, 它首先會得到此service的handle, 后面操作此handle即可, 也就是說handle就代表這個service.
fd是用open得到的一個隨機整數, handle也是一個隨機整數, 只不過它不是用open得到的, 而是從ServiceManager得到的; 另外handle >=1. 為什么0不行? 因為0號handle有特殊用途, 它是專門代表ServiceManager這個service的.
還不是很明白? 沒關系, 接下來看看這個神秘的ServiceManger!
2.1 0號handle/ServiceManager
servicemanager在Android中是一個獨立的進程, 它的實現文件是frameworks/native/cmds/servicemanager/service_manager.c , 它會被編譯成一個可執行文件servicemanager, 在init.rc中把它作為一個service運行.
servicemanager負責管理Android中所有Server進程, 它主要提供了兩個接口: addSerivce和getService.
Server進程會調用addSerivce, 把自己向servicemanager注冊. 注冊的時候需要提供service的name和Service的基類(BBinder)
Client進程會調用getService(name), 通過name向servicemanager查詢Server, 最終的結果就是會獲取到name對應的Server線程的編號(handle).
從這里可以看出, name在servicemanager中必須是唯一的, 否則servicemanager就無法區分不同個Server了. 隨便提一下, 這個name是字符串.
注意, 從servicemanager的角度來說, Server進程和Client進程都是自己的client. 它們在調用addSerivce/getService時, 必須跨進程通信, 因為servicemanager / Server進程 / Client進程 這三者是不同的進程.
回想一下前述內容, 如果要跨進程通信, 本質上來說, 就是client進程得拿到Server的編號. 但Server進程/Client進程此時沒有任何手段能”查詢”到servicemanager這個進程的編號(因為不知道向誰去查). 如何解決? 很簡單, 把servicemanager這個特殊進程的編號寫死, 置為0. 這樣當Server進程/Client進程想與servicemanager通信時, 直接通過0號即可.
既然servicemanager作為所有Server進程/Client進程的服務端, 那是不是應該提供相應的客戶端的封裝, 以便客戶端能方便與servicemanager通信呢? Android系統確實提供了這樣的封裝, 它就是frameworks/native/libs/binder/IServiceManager.cpp. 這個文件的細節現在就不看了, 等讀完本文的所有內容, 你自然就能理解了.
接下來, 我們詳細看看Server端是如何向ServiceManger注冊的以及Client端是如何獲取handle的.
為了能更詳細的說明整個過程, 我們必須先介紹內核Binder驅動中的幾個重要的數據結構, 它們是 : binder_proc; binder_node; binder_ref; 在此之后, 在介紹注冊服務(addService)和獲取服務(getService).
2.2 Binder驅動
代碼路徑
kernel/drivers/ (不同Linux分支路徑略有不同)
或
- android/binder.c
Binder驅動是Android專用的,但底層的驅動架構與Linux驅動一樣。binder驅動在以misc設備進行注冊,作為虛擬字符設備,沒有直接操作硬件,只是對設備內存的處理。主要是驅動設備的初始化(binder_init),打開 (binder_open),映射(binder_mmap),數據操作(binder_ioctl).
2.2.1 binder_proc
《1.1》 節中有提到, 不管是Server進程還是Client進程, 都會調用ProcessState::self().
ProcessState::self()會open(/dev/binder), 這個open動作會導致驅動層創建一個binder_proc結構. 如下:
也就是說, 每個用戶空間的進程, 如果想通過binder通信, 不管是Server進程還是Client進程, 在內核空間都會有一個與之對應的數據結構binder_proc.
另外, 由於ProcessState被設計為單例模式, 因此每個進程只會open一次/dev/binder, 也就是說每個進程只會存在唯一一個對應的binder_proc.
如果說我們把binder通信想象成電力傳輸: 電從一端傳到另外一端需要通過電線, binder_open就相當於在內核空間創建了這樣一根”電線”, 通過這根”電線”, Client/Server就可以交換數據了. binder_proc相當於這個電線的最外層包皮.
但是單有這個最外層包皮是無法傳輸的, 我們還需要電線里面的“銅芯線”, 這個“銅芯線”就是下面介紹的binder_node.
2.2.2 binder_node
一根電線里面會有多跟“銅芯線”, 典型的是3根(三相交流). 同樣的, 一個Server進程可以提供一個或者多個Service, 例如main_mediaserver.cpp這個進程就注冊了MediaPlayerService、CameraService等多個服務. 每個服務就相當於一根“銅芯線”.
在binder驅動層面, 每個服務都對應一個binder_node. 類似於“銅芯線”之與“外層包皮”, 每個binder_node都是隸屬於對應進程的binder_proc, 通過紅黑色進行管理, 代碼片段如下:
nodes就是本binder_proc下所有binder_node的根節點.
《2.2.1》我們提到Server進程和Client進程都會創建對應的binder_proc, 那是不是它倆也都會創建binder_node呢? 這個問題的根本在於進程中是否有注冊服務, 一般來講Server進程肯定會注冊服務的, 因此Server進程的binder_proc下會掛有一個或多個binder_node; Client進程如果是純粹的只獲取服務, 那么它的binder_proc下不會掛有binder_node, 但有的Client進程即需要獲取服務, 也會想它人提供自己的服務, 此時它的binder_proc下任然會掛有binder_node.
想一下binder_node里面會主要會存儲什么東西? 前文說過在服務的注冊過程中, 會傳遞service的name和BBinder, 這里的BBinder其實就是Service實現代碼的基類指針, 通過它我們可以訪問Service的所有實現函數(因為C++規定, 基類指針可以指向它的任何派生類的對象). binder_node里面會存儲這個BBinder的指針, 代碼如下:
ptr和cookie都是用於存儲BBinder的, 兩個稍微有點區別(ptr == BBinder->getWeakRefs(); cookie == BBinder), 這個區別暫時不用去管它. 主要用到的是cookie.
存儲BBinder是很有意義的. 設想一下, 服務端的代碼派生了BBinder, 然后實現了很多自己的功能函數, 客戶端與服務端通信的主要目的就是想調用服務端自己實現的這些功能函數, 只不過兩者屬於不同進程, 不可直接調用, 所以Android才有了Binder這套通信機制.
前面我們說過, 客戶端要想與服務端通信, 首先會拿到一個handle, 然后操作這個handle即可. 更進一步來說, 客戶端會把這個handle傳給內核, 然后跟內核說”我要調用這個handle對應的服務的函數”, 內核拿到這個handle之后, 如果能通過某種機制, 找到它對應的binder_node(handle代表一個服務, 而每個服務在內核層都會創建一個binder_node, 所以肯定能找到), 繼而就能知道對應的binder_node->cookie, 也就是服務的BBinder, 然后通過BBinder就能調用服務端的相應函數了.
實事上目前的Binder機制確實是這樣做的, handle與binder_node的對應關系是靠下一節介紹的binder_ref維護的.
還有一個值得思考的問題, binder_proc是在open時創建的, 那binder_node是在何時創建的呢? 答案是在注冊服務(addService)的時候.
2.2.3 binder_ref
《2.2.2》說到當Server進程注冊服務的時候, 驅動層會創建對應的binder_node; 對應的, Client進程在獲取服務的時候, 驅動層會創建對應的binder_ref.
binder_ref的主要目的是維護handle和binder_node之間的對應關系, 代碼如下:
desc就是handle, node指向對應的服務的binder_node.
binder_ref是在獲取服務(getService)的時候創建的, 當Client想獲取某個服務時, 它會把服務的name傳給ServiceManager, 在這個過程中, 驅動層會創建對應的binder_ref, 依靠ServiceManager通過name找到對應的binder_node, 賦值給binder_ref->node; 然后驅動層會分配一個整型數, 也就是desc; 然后這個desc會反饋給Client進程, 也就是Client拿到的handle.
反過來在看看Client與Server通信的過程. 假設Client已經拿到了Server的handle, 當Client想與Server交互時, 它會把這個handle寫入內核. Binder驅動收到這個handle之后, 會從當前進程(此時是Client)的binder_proc中找到與handle對應的binder_ref, 繼而就找到了binder_ref->node, 這個node就是代指對應的服務, 從而完成Client與Server的一次通信.
2.3 注冊服務(addService)
注冊服務是指的Server進程把自己的某個服務注冊到ServiceManager里面, 此時Server進程是客戶端, ServiceManager進程是服務端. 兩者通過0號handle通信.
首先必須意識到, ServiceManger也是一個進程, 因此它在驅動層存在一個與之對應的binder_proc; 另外這個進程也提供了自己的服務(addService/getService), 因此在驅動層存在一個與之對應的binder_node, 這個node的名字叫binder_context_mgr_node. 它是一個特殊的node, 整個binder驅動里面有且只能有一個binder_context_mgr_node. 這個node固定的與0號handle對應, 也就是說任何時候, 只要驅動收到的handle是0, 那就一定會對應到binder_context_mgr_node, 這就是為什么任何進程在與ServiceManager交互的時候直接使用0號handle即可的根本原因.
我們在從需要注冊服務的Server進程側看一下, 它首先會得到ServiceManager的代理IServiceManager, 這個過程其實就是把0號handle層層封裝, 最后變成IServiceManager, 具體代碼細節請參考這里. 然后它會把自己的”Service name”和”Service BBinder指針”通過IServiceManager寫入內核驅動, 意思就是說: 嘿, ServiceManger進程, 請把我的”name” 和 “BBinder指針”保存起來.
IServiceManager收到”Service name”和”Service BBinder指針”后, 會把它倆層層封裝, 變成一個數據包, 然后通過ioctl把這個數據包寫入內核驅動. 注意IServiceManager是ServiceManger進程在客戶端的代理, 也就是上述動作都發生在”注冊服務的Server進程”中.
當驅動收到數據包之后, 會把數據包拆分: 首先得到handle == 0, 得知這是一次與ServiceManger通信的請求; 然后拆分出具體動作, 得知這是一次服務添加過程(在數據包中有特定的數據代表是添加還是獲取服務). 對於首次添加的服務, 會在驅動層創建一個binder_node結構體, 並把”Service BBinder指針”保存到這個結構體里面, 然后把這個結構體掛載到該服務隸屬的進程的binder_proc下. 此時的圖示類似於下面這樣:
然后, 驅動層會在ServiceManger進程對應的binder_proc下創建一個binder_ref, 這個binder_ref指向剛剛創建的binder_node, 同時驅動層會計算出對應的handle值(計算的規則詳見后文的代碼分析), 此時圖示變成了這樣:
這里有一個疑問, 上面計算出來的handle值有何意義? 前文說道客戶端想與服務端通信時, 首先會向ServiceManager查詢, 最終得到一個handle值, 難道這里創建的handle值就是以后給客戶端使用的? 答案是No. 這里的handle值只是給ServiceManager內部使用的(具體用途2.4節說明), 與客戶端拿到的handle沒有什么關系(客戶端如何獲取handle, 請查看2.4節: 獲取服務).
之后, 驅動層會把”Service name”(從數據包中拆解出來)和handle1(剛剛計算出來的)封裝成數據包, 然后傳遞給用戶空間的ServiceManager進程. ServiceManager進程在收到數據包之后, 會拆解出”Service name”和“handle1值”, 然后把它倆存儲起來. “Service name”和“handle1”在ServiceManager中是一一對應的關系, 並且都是唯一的. 圖示如下:
上述就是注冊服務的宏觀邏輯, 如果想配合源代碼仔細研究, 網上的這篇文章寫的很好, 可以參考 : Binder系列5—注冊服務(addService)
2.4 獲取服務(getService)
理解了注冊服務的過程, 再來看獲取服務相當就比較容易了.
獲取服務的起點是Client知道服務的Service name, 終點是拿到與這個Service相對應的handle值. 下面細看一下這個流程.
Client端首先也是獲取到ServiceManger的代理IServiceManager, 然后通過IServiceManager把”Service name”寫入驅動層, 驅動收到這個請求后, 會把”Service name”傳給用戶空間的ServiceManager進程.
在ServiceManager進程中, 由於已經存在着”Service name”和“handle”的一一對應關系, 因此ServiceManager進程可以通過“Service name”找到對應的”handle”, 然后把”handle”回傳給驅動層.
驅動層收到傳回的”handle”之后, 會從ServiceManager進程對應的binder_proc的紅黑樹中找到對應的binder_ref, 關鍵字就是“handle”.
找到binder_ref之后, 就能找到它所指向的binder_node. 注意這個binder_node其實就是Client端想要找的那個服務端, 也就是與“Service name”對應的那個服務端.
說道這里大家可能又有一個疑問, 難道這里描述的handle不是最終返回給Client端的那個嗎? 答案是不是的. 這里的handle是ServiceManager內部使用的, 它是在注冊服務的過程中創建的(前文已經介紹了是如何創建的), 這個handle的主要作用是為了通過”Service name”最終找到對應的binder_node.
那Client端需要的handle是怎么來的呢?
當我們找到與“Service name”對應的binder_node之后, 驅動層會創建一個binder_ref, 這個binder_ref指向剛剛那個binder_node, 在創建binder_ref的過程中會計算出一個handle值(圖中的handle2), 並最終會把這個handle2反饋給Client端. 圖示如下:
至此, Client端就完成了通過”Service name”獲取到handle的整個過程, 之后Client就可以通過這個handle2與服務端通信了.
這里同樣只是宏觀描述了整個過程, 如果對代碼細節有興趣, 可以參考這篇文章: Binder系列6—獲取服務(getService)
關於handle值的計算規則, 文中有一段總結比較經典:
handle值計算方法規律:
每個進程binder_proc所記錄的binder_ref的handle值是從1開始遞增的;
所有進程binder_proc所記錄的handle=0的binder_ref都指向service manager;
同一個服務的binder_node在不同進程的binder_ref的handle值可以不同;
3. Binder數據傳輸與解析
Binder通信一般是兩個進程交互的, 示意圖如下:
Client端會把請求寫入驅動層, Server端則會從驅動層讀取請求, 然后執行相應的服務. 下文分別闡述這兩種情況的細節.
3.1 Client端把數據寫入驅動層
數據傳輸涉及到兩部分 : 數據傳輸的路徑和數據包的封裝/解封過程.
下圖完整了顯示了一次從上至下的數據傳輸流程. 宏觀上來說: 在用戶空間, 數據是逐步封裝, 最終變成一個binder_write_read的數據包; 內核空間, 數據是逐步解封, 最終生成了一個binder_transaction事物.
BpxxxFunc是客戶端的實現代碼, 這里有兩種典型情況:
一種是普通的客戶端, 例如BpInputFlinger:: doSomething , 這種情況下會有3個主要的數據: code, 它一般是enum型, 意思是想調用服務端的哪個服務(也就是服務端的哪個函數); data , 它是一個parcel型的數據, 代表想傳給服務端的數據; reply, 它也是一個parcel型的, 代表想從服務端獲得的返回值.
另一種是ServiceManager的客戶端, 例如BpServiceManager:: addService, 這種情況與普通客戶端的唯一不同之處在於, 它會在data這個parcel里面多加入一個flat_binder_object數據. 如是添加服務, flat_binder_object里面存儲是服務的BBinder指針; 如果是獲取服務, flat_binder_object里面存儲的是驅動反饋上來的handle值.
BpBinder::transact, 它是binder框架層的實現. 它里面有一個mHandle, 也就是前文所說的通過ServiceManager獲取到的服務端的handle值. mHandle是在創建BpBinder對象時賦值的.
IPCThreadState.writeTransactionData, 同樣是binder框架層的實現. 它首先會創建一個binder_transaction_data, 把前面傳下來的數據都寫入到這個結構體里面. 然后它會在binder_transaction_data前加入BC_XXX, 封裝成mOut.
BX_XXX是與binder驅動交互的協議, 其中BC_XXX代表用戶空間到驅動層; BR_XXX代表驅動層到用戶空間.
BC_XXX最常見的值就是BC_TRANSACTION, 代表Client向Binder驅動發送請求數據. 其它取值參見BC_PROTOCOL.
IPCThreadState.talkWithDriver, 它會把前一環節創建的mOut和mIn(兩者是同一種數據類型, 只不過mOut里面填充了很多數據, mIn相當於一個空的存儲池. 驅動層最終會把反饋給用戶空間的數據寫入mIn)封裝成binder_write_read, 然后通過ioctl與驅動層交互. binder_write_read數據包會傳入驅動層.
驅動層收到收到用戶空間的binder_write_read數據包后會依次解析數據包的內容, 然后進行相應的處理, 這里不細看了.
3.2 Server端從驅動層讀取數據
Server端注冊完服務之后, 會block在驅動層, 等待客戶端向自己發送請求.
當客戶端發送完請求會, 最終會生產一個binder_transaction事物, 並將binder_transaction.work加入到對應的server的隊列中, 這個隊列可以是thread->todo, 也可以是proc->todo(第4章會詳述這幾個概念). 然后調用wake_up_interruptible喚醒服務端.
服務端被喚醒后, 就從調出block狀態, 然后從thread->todo或者proc->todo中取出一個binder_work.
binder_work是binder_transaction內部的一個數據, 因此使用container_of可以獲得binder_work對應的binder_transaction指針.
通過binder_transaction.buffer中的數據(這些數據是客戶端傳過來的), 可以構建一個binder_transaction_data數據結構.
binder_transaction_data最終會被封裝成binder_write_read, 然后返回給用戶空間.
用戶空間在收到數據后, 會調用IPCThreadState::executeCommand解析數據包, 拆分出BR_XXX, BR_XXX最常見的值是BR_TRANSACTION, 代表Binder驅動向Server端發送請求數據. 其它取值參見BR_PROTOCOL.
如果BR_XXX是BR_TRANSACTION, 則會從數據包binder_transaction_data中取出tr.cookie, 然后將其轉換為<BBinder*>(tr.cookie), 然后調用<BBinder*>(tr.cookie)->transact函數, 並傳入相應的參數(tr.code, buffer, &reply, tr.flags).
這些參數的具體意義是:
tr.code代表要調用服務端的哪個函數
buffer是一個parcel數據包, 是從客戶端傳過來的
reply也是一個parcel數據包, 代表需要回傳給客戶端的數據. 此時它是空的, 內容由服務端填充.
tr.flags是標志位, 它的定義參考transaction_flags, 最常見的取值是TF_ONE_WAY
Binder.cpp實現了一個默認的transact函數, 在函數里面會調用onTransact. onTransact是BBinder定義的一個接口類, 任何服務端都必須繼承並實現onTransact函數
服務端的BnXXX::onTransact被調用.
在BnXXX::onTransact函數中, 會根據tr.code的值, 調用不用的實現函數BnXXXFunc
4. Binder事物
每一次數據傳輸, 在Binder驅動層都會生成一個事物binder_transaction, 並把這個事物掛載到相應的鏈表todo上. 所謂相應的鏈表的意思是: 這個事物應該由誰來處理, 就掛載到誰的鏈表上. 比如當客戶端向服務端發送一個請求時, 客戶端會先把數據寫入驅動層, 驅動層會生產一個事物binder_transaction, 這個事物顯然應該由對應的服務端來處理, 因此這個事物會掛載到對應的服務端的todo下面.
本章主要介紹與binder事物相關的幾個數據結構, 它們是binder_transaction / binder_work / binder_thread / binder_proc->todo.
binder_transaction
定義文件: drivers/android/binder.c # L355
類型 |
成員變量 |
解釋 |
int |
debug_id |
用於調試 |
struct binder_work |
work |
binder工作類型 |
struct binder_thread * |
from |
發送端線程 |
struct binder_transaction * |
from_parent |
上一個事務 |
struct binder_proc * |
to_proc |
接收端進程 |
struct binder_thread * |
to_thread |
接收端線程 |
struct binder_transaction * |
to_parent |
下一個事務 |
unsigned |
need_reply |
是否需要回復 |
struct binder_buffer * |
buffer |
數據buffer |
unsigned int |
code |
通信方法,比如startService |
unsigned int |
flags |
標志,比如是否oneway |
long |
priority |
優先級 |
long |
saved_priority |
保存的優先級 |
kuid_t |
sender_euid |
發送端uid |
這個結構體是在binder_transaction()函數中創建的
binder_work代表此次事物的具體類型, 取值范圍后文詳述
debug_id:是一個全局靜態變量,每當創建一個binder_transaction或binder_node或binder_ref對象,則++debug_id
from與to_thread是一對,分別是發送端線程和接收端線程
from_parent與to_parent是一對,分別是上一個和下一個binder_transaction,組成一個鏈表
執行binder_transaction()方法過程,當非oneway的BC_TRANSACTION時,則設置當前事務t->from_parent等於當前線程的transaction_stack. transaction_stack代表當前線程正在處理的事物.
執行binder_thread_read()方法過程,當非oneway的BR_TRANSACTION時,則設置當前事務t->to_parent等於當前線程的transaction_stack
binder_buffer是用來存儲數據的, 它的細節在《Binder Buffer》一節中詳述
binder_work
定義文件 : drivers/android/binder.c # L214
struct binder_work {
struct list_head entry;
enum {
BINDER_WORK_TRANSACTION = 1,
BINDER_WORK_TRANSACTION_COMPLETE,
BINDER_WORK_NODE,
BINDER_WORK_DEAD_BINDER,
BINDER_WORK_DEAD_BINDER_AND_CLEAR,
BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
} type;
};
binder_work.type設置時機:
- binder_transaction()
- binder_thread_write()
- binder_new_node()
binder_thread
binder_thread結構體代表當前binder操作所在的線程.
類型 |
成員變量 |
解釋 |
struct binder_proc * |
proc |
線程所屬的進程 |
struct rb_node |
rb_node |
|
int |
pid |
線程pid |
int |
looper |
looper的狀態 |
struct binder_transaction * |
transaction_stack |
線程正在處理的事務 |
struct list_head |
todo |
將要處理的鏈表 |
uint32_t |
return_error |
write失敗后,返回的錯誤碼 |
uint32_t |
return_error2 |
write失敗后,返回的錯誤碼2 |
wait_queue_head_t |
wait |
等待隊列的隊頭 |
struct binder_stats |
stats |
binder線程的統計信息 |
transaction_stack代表當前線程正在處理的事物
todo代表屬於本線程的事物鏈表
wait是等待隊列頭, 假設當前線程沒有事物需要處理, 則線程會block在wait上, 直到有人喚醒它.
looper代表當前線程的狀態, 具體意義在《Binder Thread》一節中詳述.
你可能還對binder_thread的具體意義摸不着頭腦, 沒關系, 后面《Binder Thread》會專門講解它. 這里我們只關注與Binder事物相關的部分.
binder_proc.todo
binder_proc前文描述過, 每一個需要Binder通信的進程在驅動層都會有一個對應的binder_proc. 這個結構體里面有一個元素如下:
struct binder_proc {
......
struct list_head todo;
wait_queue_head_t wait;
......
};
todo代表屬於該進程的事物的鏈表
wait是等待隊列頭, 如果進程中沒有需要處理的事物, 則會block在wait上.
thread.todo or proc.todo
既然binder_thread.todo和binder_proc.todo下面都可以掛載事物, 那到底應該掛載到哪個下面?
- reply的過程會找到target_thread.todo;
- 非reply則一般找到target_proc.todo;
- 對特殊的嵌套binder call會根據transaction_stack來決定是插入事務到目標線程還是目標進程
5. Binder Thread
Note : 也可閱讀http://blog.csdn.net/kc58236582/article/details/51744601這篇文章, 寫得貌似比這里還清晰些.
5.1 簡介
Binder通信是C/S模式的, 服務端會等待客戶端向自己發起請求. 所謂等待, 就是指服務端會有一個循環(looper), 在循環里面不停的查看是否有請求.
為了不讓循環卡死Server進程, 一般會在進程里面創建一個線程, 然后把這個循環丟到線程里面去運行. 這個線程就是這里的binder_thread.
在Binder通信機制里, 客戶端與服務端之間的通信是在專門的IPC通信線程中進行的. 這些線程構成一個線程池. 線程的創建和銷毀是在用戶空間進行的, 而對線程的控制是在驅動層進行的, 即驅動控制線程池中線程的生命, 而線程本身則是運行在用戶空間的.
5.2 數據結構
與線程池相關的幾個變量設置在struct binder_proc結構體中:
struct binder_proc {
- ...
- int max_threads;//max thread
- int requested_threads;//
- int requested_threads_started;//requested thread seq No
- int ready_threads;//available for use
- ...
- };
max_threads代表當前進程最多可以創建多少個線程(不包括5.3節所說的那兩個默認線程), 用戶空間可以通過ioctl命令BINDER_SET_MAX_THREADS來設置這個值, 默認情況下是15.
ready_threads表示當前線程池中有多少可用的空閑線程.
requested_threads請求開啟線程的數量.
requested_threads_started表示當前已經接受請求開啟的線程數量
5.3 默認情況下會有幾個thread
Server端代碼中(例如main_mediaserver.cpp), 一般我們會看到如下兩句:
- ProcessState::self()->startThreadPool();
- IPCThread::self()->joinThreadPool();
第一句話會新創建一個線程, 並在線程里面放一個looper, 在looper里面循環讀取和解析binder驅動層傳上來的數據.
第二句話會在進程的主線程里放一個looper, 然后在looper里面循環讀取和解析binder驅動層傳上來的數據. 這個動作有兩個目的: 一是相當於在進程main函數的最后放了一個while(1), 防止進程退出. 第二個目的就是相當於多創建了一個looper.
總結來看, 默認情況下, 會有兩個thread處理驅動層上報的數據. 一般情況下這已經足夠了.
但如果通信量很大, 可能會出現thread處理不過來的情況, 就向超市排隊的人多了, 收銀的忙不過來, 這時候就需要多開幾個收銀窗口.
那么何時會創建新的thread呢? 請參考下節.
另外還有一個題外話, 網上有人說上面第二句代碼是多余的, 去掉也可以運行. 其本質在於, 如果有其它代碼可以block住進程不讓其退出, 那這里是可以去掉, 帶來的影響就是默認thread只有1個, 相當於通信管道變窄了.
5.4 何時會創建新的thread
當有多個並發的IPC請求時,可能會觸發內核增加更多的IPC通信線程來服務這些請求。具體的情境如下代碼所示:
- if (proc->requested_threads + proc->ready_threads == 0 &&
- proc->requested_threads_started < proc->max_threads &&
- (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
- BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
- /*spawn a new thread if we leave this out */) {
- proc->requested_threads++;
- binder_debug(BINDER_DEBUG_THREADS,
- "%d:%d BR_SPAWN_LOOPER\n",
- proc->pid, thread->pid);
- if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
- return -EFAULT;
- binder_stat_br(proc, thread, BR_SPAWN_LOOPER);
- }
在請求的線程數和空閑的線程數為零, 且已經請求並開啟的線程數小於線程池的最大允許線程數量時, 驅動會向用戶空間發送一個BR_SPAWN_LOOPER命令以開啟新的接收線程來處理請求。
我們再來看看用戶空間對這個命令的處理, IPCThreadState::executeCommand中相關代碼如下:
- case BR_SPAWN_LOOPER:
- mProcess->spawnPooledThread(false);
- break;
直接調用ProcessState的spawnPooledThread函數創建一個新的線程. 注意這里傳入的參數是false, 稱之為非binder主線程. 而5.3節中介紹的兩個線程, 它們都沒有顯示指定參數, 從而會使用參數的默認值true, 因此它倆稱為binder主線程.
那binder主線程和非主線程有何區別? 詳見下節.
5.5 biner主線程與非主線程區別
用戶空間創建線程的函數是ProcessState::spawnPooledThread, 代碼如下:
- void ProcessState::spawnPooledThread(bool isMain)
- {
- if (mThreadPoolStarted) {
- String8 name = makeBinderThreadName();
- ALOGV("Spawning new pooled thread, name=%s\n", name.string());
- sp<Thread> t = new PoolThread(isMain);
- t->run(name.string());
- }
- }
這里會借用Android的Thread機制創建一個線程PoolThread, 然后調用run運行此線程.
在線程的threadloop里面, 會調用joinThreadPool:
- protected:
- virtual bool threadLoop()
- {
- IPCThreadState::self()->joinThreadPool(mIsMain);
- return false;
- }
- const bool mIsMain;
- };
注意這個threadLoop里面return的是false, 意味着一旦joinThreadPool結束運行, 整個線程就會退出了. 因此joinThreadPool里面應該是一個while循環(也就是前文所說的looper), 一般情況下不會退出.
joinThreadPool的實現函數如下:
- void IPCThreadState::joinThreadPool(bool isMain)
- {
- LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());
- mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
- // This thread may have been spawned by a thread that was in the background
- // scheduling group, so first we will make sure it is in the foreground
- // one to avoid performing an initial transaction in the background.
- set_sched_policy(mMyThreadId, SP_FOREGROUND);
- status_t result;
- do {
- processPendingDerefs();
- // now get the next command to be processed, waiting if necessary
- result = getAndExecuteCommand();
- if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
- ALOGE("getAndExecuteCommand(fd=%d) returned unexpected error %d, aborting",
- mProcess->mDriverFD, result);
- abort();
- }
- // Let this thread exit the thread pool if it is no longer
- // needed and it is not the main process thread.
- if(result == TIMED_OUT && !isMain) {//當isMain是false,並且是timeout的時候線程退出
- break;
- }
- } while (result != -ECONNREFUSED && result != -EBADF);
- LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n",
- (void*)pthread_self(), getpid(), (void*)result);
- mOut.writeInt32(BC_EXIT_LOOPER);
- talkWithDriver(false);
- }
isMain為true, 代表是binder主線程; 為false則代表非主線程. 從代碼分析可知它們的區別如下:
26行, 說明主線程是不會退出的, 一直存在. 但是非主線程result == TIMED_OUT的時候會退出.
更進一步來說, 當驅動發現沒有足夠的線程處理數據時, 會發送BR_SPAWN_LOOPER到用戶空間申請創建線程. 當數據處理完畢之后, 這些新創建的非主線程就會退出了.
5行, 主線程會往驅動寫入BC_ENTER_LOOPER; 而非主線程寫入的是BC_REGISTER_LOOPER.
BC_ENTER_LOOPER會讓驅動層的looper進入BINDER_LOOPER_STATE_ENTERED狀態; BC_REGISTER_LOOPER會讓驅動層的looper進入BINDER_LOOPER_STATE_REGISTERED狀態.
另外, BC_REGISTER_LOOPER還會更新驅動層的幾個數據:
- case BC_REGISTER_LOOPER:
- …
- } else {
- proc->requested_threads--;
- proc->requested_threads_started++;
- }
- thread->looper |= BINDER_LOOPER_STATE_REGISTERED;
- break;
驅動會更新proc->requested_threads_started來統計當前已經請求開啟並成功開啟的線程數量,這個值將作為判斷線程池是否已經滿的依據
5.6 looper的狀態遷移
在驅動層, binder_thread中有一個元素用於表示該thread的looper的狀態:
- struct binder_thread {
- struct binder_proc *proc; //線程所屬的進程
- struct rb_node rb_node; //紅黑樹的結點,進程通過該結點將線程加入到紅黑樹中
- int pid; //線程的pid
- int looper; //線程所處的狀態
looper的取值范圍如下
- enum {
- BINDER_LOOPER_STATE_REGISTERED = 0x01, // 創建注冊線程BC_REGISTER_LOOPER
- BINDER_LOOPER_STATE_ENTERED = 0x02, // 創建主線程BC_ENTER_LOOPER
- BINDER_LOOPER_STATE_EXITED = 0x04, // 已退出
- BINDER_LOOPER_STATE_INVALID = 0x08, // 非法
- BINDER_LOOPER_STATE_WAITING = 0x10, // 等待中
- BINDER_LOOPER_STATE_NEED_RETURN = 0x20, // 需要返回
- };
looper狀態遷移的時機:
收到 BC_REGISTER_LOOPER,則線程狀態為BINDER_LOOPER_STATE_REGISTERED;
收到 BC_ENTER_LOOPER,則線程狀態為 BINDER_LOOPER_STATE_ENTERED;
收到 BC_EXIT_LOOPER, 則線程狀態為BINDER_LOOPER_STATE_EXITED;
其他3個狀態的時機:
BINDER_LOOPER_STATE_WAITING:
當停留在binder_thread_read()的wait_event_xxx過程, 則設置該狀態;
BINDER_LOOPER_STATE_NEED_RETURN:
binder_get_thread()過程, 根據binder_proc查詢不到當前線程所對應的binder_thread,會新建binder_thread對象;
binder_deferred_flush()過程;
BINDER_LOOPER_STATE_INVALID:
當binder_thread創建過程狀態不正確時會設置.
5.7 最多能創建多少個thread
線程池的大小可以設置, 如果沒有主動去設置這個大小,則默認大小為15,如下代碼所示:
- static int open_driver()
- {
- int fd = open("/dev/binder", O_RDWR);
- if (fd >= 0) {
- ...
- size_t maxThreads = 15;
- result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
- if (result == -1) {
- ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
- }
- } else {
- ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));
- }
- return fd;
- }
- ProcessState::self()->setThreadPoolMaxThreadCount(4);
setThreadPoolMaxThreadCount可以修改線程池的大小, 例如上述代碼把線程池的大小設置為4. 這意味着該進程最多只能創建4個非主線程. 注意線程池的大小不包括主線程, 也就是說5.3節介紹的2個主線程不占用線程池空間. 所以總體來說該進程最多可以有6個線程用於處理數據.
6. Binder Buffer
ProcessState的構造函數如下:
- ProcessState::ProcessState()
- : mDriverFD(open_driver())
- ......
- {
- if (mDriverFD >= 0) {
- // mmap the binder, providing a chunk of virtual address space to receive transactions.
- mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
- if (mVMStart == MAP_FAILED) {
- // *sigh*
- ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
- close(mDriverFD);
- mDriverFD = -1;
- }
- }
- LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened. Terminating.");
- }
主要有2件事情:
17行會open(/dev/binder), 這個好理解, 目的就是為了和binder驅動交互嘛
22行, 從驅動層mmap了BINDER_VM_SIZE大小的空間(#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)))。為何要mmap?
mmap意味着用戶空間和內核空間共享同一塊物理內存, 它們在交互數據時不用copy_to_user. 這個特性主要是用於驅動層向用戶空間傳遞數據的過程. 具體來講就是 : 當Client端向Server端發送請求時, 請求數據會先copy_from_user到內核, 然后內核把這個數據交給Server端時就會用到上面的共享內存, 不會copy_to_user了; 反過來, 當Server端處理完請求, 打算把結果返回給Client端時, Server端也會先通過copy_from_user的方式把數據交給內核, 然后內核用共享內存的方式把數據返回給Client端.
關於這塊的邏輯, 可以進一步看兩個代碼.
首先看看Server端是如何與內核共享的:
IPCThreadState::executeCommand是Server端負責處理請求的, 它里面有如下一段:
Server端的buffer是直接用的內核傳上來的tr.data.ptr.buffer作為地址的. freeBuffer表示上層用完這個數據后, 回調freeBuffer把占用的空間釋放掉, 否則共享的1M空間很快會被擠爆了.
其次看看Client端是如何與內核共享的:
IPCThreadState::waitForResponse代表Client端請求Server處理請求並等待反饋結果, 里面有如下一段:
邏輯同上, 不在贅述.
從上可知binder一次通信過程只發生一次用內存拷貝。這也是Binder的優勢之一,普通的IPC通信機制, 一般都會有2次數據拷貝(Client端進程空間拷貝到內核空間,再由內核空間拷貝到Server進程空間,會發生兩次拷貝)。
對於進程和內核虛擬地址映射到同一個物理內存的操作是發生在數據接收端,而數據發送端還是需要將用戶態的數據復制到內核態。到此,可能有讀者會好奇,為何不直接讓發送端和接收端直接映射到同一個物理空間,那樣就連一次復制的操作都不需要了,0次復制操作那就與Linux標准內核的共享內存的IPC機制沒有區別了,對於共享內存雖然效率高,但是對於多進程的同步問題比較復雜,而管道/消息隊列等IPC需要復制2兩次,效率較低。這里就不先展開討論Linux現有的各種IPC機制跟Binder的詳細對比,總之Android選擇Binder的基於速度和安全性的考慮。
下面這圖是從Binder在進程間數據通信的流程圖,從圖中更能明了Binder的內存轉移關系。
首先發送方把數據地址傳入內核; 然后在內核的binder_transaction函數中會調用binder_alloc_buf從共享的內存中分配一塊binder_buffer, 然后調用copy_from_user將發送放的data從用戶空間拷貝到內核的binder_buffer.data中; 然后這塊binder_buffer會傳遞給數據接收方, 由於接收方與內核共享同一塊物理內存, 因此接收方訪問data時就不需要在做copy動作了.
注意上述的一次拷貝只是針對data. 對於BINDER協議、target、code這些字段, 發送方會copy_from_user, 接收方會copy_to_user. 原因是這些字段數據量很少, 兩次拷貝不會有太大影響.
7. flat_binder_object的本質
7.1 第一印象 : flat_binder_object用於注冊服務和獲取服務
從前面的章節我們知道flat_binder_object主要是用於服務的注冊和服務的獲取:
當我們向ServiceManager注冊服務時, 會有一個writeStrongBinder的動作, 這個動作會把代表服務的BBinder(IBinder是基類, 指向BBinder)封裝成flat_binder_object, 並最終傳入驅動層. 代碼如下:
驅動層收到flat_binder_object后, 會創建這個服務對應的binder_node. 代碼如下:
當我們向ServiceManager獲取服務時, 驅動層會把handle值封裝成flat_binder_object對象反饋給用戶空間, 用戶空間通過readStrongBinder的動作即可拿到handle, 並創建基於handle的BpBinder, 這個BpBinder就是服務在客戶端的代理了. 用戶空間的代碼如下:
至此, flat_binder_object給人的第一個感覺是用於注冊服務和獲取服務: 注冊服務時它被傳入驅動層, 里面包含代表服務的BBinder; 獲取服務時它由驅動層創建, 然后返回給用戶層, 用戶層通過其中的handle值創建BpBinder, 之后就可以通過這個BpBinder與服務端通信了.
7.2 匿名binder通信
既然flat_binder_object給人的感覺是用於注冊服務和獲取服務, 那我們先思考一下服務的注冊與獲取是否一定要與ServiceManager掛鈎? 答案是否定的!
所謂注冊服務, 其實就是讓驅動層創建一個與服務對應的binder_node; 所謂獲取服務, 其實就是要拿到服務的handle值. 這兩個過程並不一定要通過ServiceManager, 它們只與writeStrongBinder和readStrongBinder有關: 任何進程, 只要它調用了writeStrongBinder, 並把自己服務的BBinder傳給writeStrongBinder, 驅動層就會為這個服務創建一個binder_node. 並且驅動層會立即創建一個flat_binder_object對象, 里面包含了handle值, 然后把這個對象返回給用戶空間, 此時用戶空間只要調用readStrongBinder就能拿到服務的handle值, 繼而就可以與服務端通信了.
驅動層的關鍵代碼如下: 第一個紅框創建與服務對應的binder_node; 第二個紅框立馬創建一個包含handle的flat_binder_object
這種不通過ServiceManager進行服務注冊與獲取的方式稱作匿名binder通信, 它的意義是服務只提供給某個特定的客戶端進程, 只有這個進程才能與本服務通信. 而通過ServiceManager注冊的服務則可以被任何進程獲取.
從代碼的角度來說, 其實匿名服務的注冊與ServiceManager服務注冊時同一套邏輯, 唯一的不同之處在於匿名服務注冊時, target_proc是那個特定的客戶端進程; 而ServiceManager服務注冊時, target_proc是servicemanger進程.
目前已知的匿名服務注冊主要用於APK進程與ActivityManagerService之間.
7.3 flat_binder_object只能用於服務注冊和獲取嗎
如題, flat_binder_object給人的感覺貌似就是用於服務注冊和獲取的, 它的使用模式也很固定: 對於注冊服務的進程, 它通過writeStrongBinder生成一個flat_binder_object並傳入驅動層; 驅動層創建服務對應的binder_node並生成一個包含handle值的flat_binder_object對象反饋給用戶空間; 對於獲取服務的進程, 則可通過readStrongBinder從內核傳上來的flat_binder_object中拿到handle值, 並用這個handle創建一個BpBinder.
flat_binder_object只能這樣用嗎? writeStrongBinder只能被服務端進程調用嗎? readStrongBinder是不是又只能被客戶端進程調用呢?
其實不然, flat_binder_object的本質是它代表着一個服務, 而writeStrongBinder和readStrongBinder只是在不同進程間傳遞服務的方法. 注意writeStrongBinder和readStrongBinder是成對出現的, 任何兩個進程可以通過writeStrongBinder->readStrongBinder的方式傳遞flat_binder_object這個服務. 即使是兩個客戶端進程! 例如某個提供服務的進程A把flat_binder_object傳遞給了客戶進程B, 客戶進程B可以直接通過writeStrongBinder->readStrongBinder把這個flat_binder_object傳遞給客戶進程C, 然后客戶進程C就可以與服務進程A通信了. 甚至客戶進程B也可以通過writeStrongBinder->readStrongBinder這個flat_binder_object傳遞給服務進程A, 然后客戶進程A就可以調用自己提供的服務了(只是這個調用不需要在通過binder了, 直接是進程內的函數調用)!
好像不是很好理解是嗎? 那我們在從代碼的角度來分析一下. 一個服務可以存在於不同的進程, 只是它存在的形式略有不同: 在服務進程中, 它是BBinder; 在客戶進程中, 它是BpBinder. BBinder和BpBinder都是繼承於IBinder, 因此不管在服務進程還是客戶進程的代碼中, 通常都用IBinder指針代表服務. 因此不管是服務進程還是客戶進程, 當你想把某個服務傳遞給另外一個進程時, 你只管以IBinder為參數調用writeStrongBinder就可以了, 在writeStrongBinder中會做如下處理:
只有你傳遞的不是非法指針(binder != NULL), writeStrongBinder就會根據情況自動生成的不同的flat_binder_object對象: 如果binder->localBinder()的返回值為空, 說明此時是在客戶端進程調用的writeStrongBinder, 這種情況下生成的flat_binder_object是BINDER_TYPE_HANDLE類型的, 而且既然是客戶端進程, 那肯定能拿到handle值(proxy->handle()); 如果binder->localBinder()的返回值不為空, 則說明此時實在服務端進程調用writeStrongBinder, 這種情況下生成的flat_binder_object是BINDER_TYPE_BINDER類型的, 而且既然是服務端進程, 那肯定能知道服務的BBinder指針, 把這個指針賦值給obj.binder和obj.cookie.
上面代碼中參數binder是IBinder類型的, 為什么通過binder->localBinder()就能知道此時到底處於服務端進程還是客戶端進程呢? localBinder是IBinder這個基類定義的一個接口函數, 基類中有一個默認實現:
BBinder重載了這個實現, 但是BpBinder沒有重載. BBinder重載的代碼如下:
前面也說過, 一個服務在服務端進程是以BBinder的形式存在的, 在客戶端進程是以BpBinder的形式存在的. 因此IBinder->localBinder()如果是在服務進程, 則代表的是調用BBinder->localBinder(); 如果是在客戶進程, 則代表的是調用BpBinder->localBinder(). 所以通過localBinder()的返回值, 就能知道IBinder對象到底是處於服務進程還是客戶進程.
當驅動層收到flat_binder_object后, 會根據它類型的不同區別對待:
如果fp->type是BINDER_TYPE_BINDER類型的, 毫無疑問, 此時是服務端進程想把這個的服務傳遞某個客戶端進程. 如果這個服務對應的binder_node還不存在, 驅動層會先創建它, 然后反饋給客戶端進程一個包含handle、類型為BINDER_TYPE_HANDLE的flat_binder_object. 客戶端通過readStrongBinder來獲取和解析這個object.
如果fp->type是BINDER_TYPE_HANDLE類型的, 那此時代表某個客戶端進程想把handle這個服務傳遞給另外一個進程, 另外的這個進程即可以是某個其它的客戶端進程, 也可以是handle這個服務所在的服務端進程:
如果ref->node->proc == target_proc, 則代表想把handle這個服務傳遞給這個服務所在的服務端進程. 此時驅動層會生產一個BINDER_TYPE_BINDER類型的flat_binder_object並反饋給用戶空間, 服務端進程通過readStrongBinder既可以獲取和解析這個object.
如果是else呢, 則代表想把handle這個服務傳遞某個其它的客戶端進程. 此時驅動層會生產一個BINDER_TYPE_HANDLE類型的flat_binder_object並反饋給用戶空間, “其它的客戶端進程”同樣通過readStrongBinder獲取和解析這個object.
OK, 既然用戶空間都是通過readStrongBinder獲取和解析object, 那我們看看這個readStrongBinder的細節:
代碼清楚的顯示, 根據flat->type的不同, 會生產不同的對象: 如果type是BINDER_TYPE_BINDER, 說明某人想把服務回傳給服務本身所在的進程, 此時根據flat->cookie生成一個IBinder對象即可, 注意此時IBinder其實是代表BBinder; 如果type是BINDER_TYPE_HANDLE, 說明某人想把服務傳遞給某個客戶端進程, 此時根據flat->handle生成一個IBinder對象(getStrongProxyForHandle中會創建一個BpBinder), 此時IBinder其實是代表BpBinder.
說了這么多, 不知道自己表達清楚沒有. 對於flat_binder_object的本質, 我們可以下結論了.
7.4 結論
對於flat_binder_object的本質, 我們的結論是:
一個flat_binder_object代表一個服務, 它的主要作用是在不同的進程之間傳遞服務, 以便不同的進程都可以訪問這個服務. 傳遞的方法是writeStrongBinder和readStrongBinder, 任何一個src進程如果想把服務傳遞給一個dst進程, src進程只需調用writeStrongBinder, 然后dst進程馬上通過readStrongBinder就可以拿到這個服務. 服務在服務端進程中是以BBinder的形式存在的, 在所有的客戶端進程中都是以BpBinder的形式存在的.
另外還需意識到, 傳遞服務的目的主要是讓其它進程訪問此服務, 但這不是唯一功能. 很多地方都會用IBinder作為key構建map, 此時傳遞服務的目的就是只是傳遞一個普通的數據(就像傳遞一個int型參數一樣).
8. Binder與sp的關系
sp指的是strongpoint.
問題源自全志的在BootAnimation里面做的一件事情: 用MediaPlayer類播放視頻, 代碼如下:
這段代碼播放視頻沒有問題, 出題出在視頻播放完畢后, 做了這樣一個賦值:
原意是想通過賦值為NULL, 執行MediaPlayer()的析構函數, 從而釋放MediaPlayer.
mPlayer是sp類型的, 給sp類型的賦NULL, 會decStrong, 也就是把引用計數減1. 當引用計數減到0時, 系統會做delete對象, 進而執行對象的析構函數. 所以單從上面的代碼來看, mPlayer = NULL貌似可以達到執行析構的效果.
但實際情況下卻沒有, 因此在代碼中加了兩個ALOGD, 把引用計數打印出來看看.
第一次new動作之后, 結果 : D/BootAnimation( 2133): startBootMedia, sc(1), wc(2)
第二次setDataSource后, 結果 : D/BootAnimation( 2133): startBootMedia : setDataSource, sc(2), wc(4)
看來是因為setDataSource把引用計數加1了(總數為2了), 但mPlayer = NULL只會decStrong一次, 所以無法執行析構函數.
為什么setDataSource會把引用計數加1?
因為MediaPlayer實現了一個Binder服務端 : BnMediaPlayerClient. 在setDataSource過程中, 會通過writeStrongBinder的方式把這個服務端傳遞給MediaPlayerService. MPS會通過readStrongBinder的方式拿到這個服務的客戶端代理, 后面MPS要通過這個代理向MediaPlayer進程反饋播放時的一些消息(例如是否暫停, 是否eos, 是否遇到錯誤), 這個過程陳作notify.
回到引用計數的問題上, 現在問題變為 : 為什么一個Binder服務端把服務傳遞給另外一個進程后, 這個服務的引用計數會加1?
8.1 從邏輯上看, Binder服務端的引用計數取決於什么?
引用計數的目的是為了保證只要資源還在被使用, 那它就不應該被釋放. Binder服務也可以理解為一種資源, 與普通資源的不同之處在於它可以被其它進程使用, 因此從正常邏輯來說, 只要有任何一個客戶端正在使用服務, 此服務在服務端進程中就不應該被釋放.
那怎么做到這一點呢? 還是借助引用計數的功能, 只要有任何一個客戶端獲取了本服務, 那么在服務端進程中就把本服務的引用計數加1.
客戶端獲取服務, 從本質上來說就是客戶端進程執行了readStrongBinder()操作, 所以在這個環節把服務端進程中的服務的引用計數加1是最合理的.
下面從Android代碼層面看下系統是如何保障這個機制的.
8.2 客戶端獲取服務 -> Binder服務引用計數加1
廢話少說, 先看一下客戶端獲取服務的代碼流程:
Parcel::readStrongBinder() -> unflatten_binder -> ProcessState::getStrongProxyForHandle.
getStrongProxyForHandle中會新建BpBinder對象, 如下:
注意最后一句話, 它把BpBinder賦值給了一個sp<IBinder>類型, 這個賦值動作會導致sp執行構造函數:
這個構造函數會執行decStrong動作, 也就是執行BpBinder->decStrong, 最終會導致BpBinder-> onFirstRef被執行:
在onFirstRef中執行了incStrongHandle:
這句話的意思是讓服務端進程執行BC_ACQUIRE請求. 注意是服務端進程哦.
我們在來看服務端進程是如何響應的:
看見沒, 這里把服務的引用計數加1了.
8.3 客戶端釋放服務 -> Binder服務引用計數減1
有加1的地方就要有減1的地方. 那客戶端何時會去做減1的動作呢?
這次我們倒推, 如果要讓Binder服務引用計數減1, 那BpBinder要執行onLastStrongRef, 在里面請求服務端進程執行BC_RELEASE動作.
服務端收到BR_RELEASE請求后, 會mPendingStrongDerefs.push(obj). 最終在processPendingDerefs函數里面針對每一個obj執行obj->decStrong(mProcess.get()).
所以現在的問題是客戶端何時才會執行onLastStrongRef? 答案是在BpBinder對象的引用計數減為0時:
那BpBinder對象何時才會減為0?
我們以MediaPlayerService為例來說明. 前面說過, MediaPlayer實現了Binder服務端, 然后在setDataSource把這個服務傳遞給MPS. 我們看下MPS是如何處理傳遞過來的服務的:
這里有兩個關鍵 : 第一個是readStrongBinder, 它最終會創建BpBinder並返回一個sp<IBinder>.
第二個是interface_cast. 它會經歷如下這串流程:
asInterface實現如下:
所有的BpXXX都會繼承至BpInterface, 因此里面的new動作會導致基類構造函數被執行:
構造函數里面調用了BpRefBase(remote), 注意這里的remote就是前面readStrongBinder返回的結果.
BpRefBase實現如下:
它的入參是o, 那就是上面的remote. 該函數的主要目的是初始化mRemote. 這里調用了一下mRemote->incStrong增加了一下BpBinder的引用計數.
大家可能會有疑問, 前面readStrongBinder的時候new BpBinder, 然后通過sp<IBinder>的形式返回, 此時已經讓BpBinder的引用計數加1了. 為什么又要做一次mRemote->incStrong?
因為當interface_cast執行完畢時, sp<IBinder>這個返回值的生命周期就結束了, 結束時會有一個減1動作, 因此mRemote要自己做incStrong.
對於MPS來說, 通過onTransact->create流程, 會把上面創建的這個客戶端存儲在MediaPlayerService:: Client:: mClient這個變量中:
當執行disconnect操作時, 會有這樣一句話:
這句話像多米洛骨牌一樣, 會觸發這樣一整串流程:
mClient.clear() -> BpRefBase::onLastStrongRef -> “mRemote->decStrong” -> BpBinder::onLastStrongRef -> ipc->decStrongHandle -> binder通信 -> 服務進程中服務引用計數減1.
9. Binder示例代碼
假設你想自己動手寫一個binder通信的程序, 可以參考Android官方代碼:
- http://androidxref.com/7.1.1_r6/xref/frameworks/native/include/input/IInputFlinger.h : 數據定義
- http://androidxref.com/7.1.1_r6/xref/frameworks/native/services/inputflinger/host/InputFlinger.cpp : 服務端代碼實現
- http://androidxref.com/7.1.1_r6/xref/frameworks/native/libs/input/IInputFlinger.cpp : 客戶端代碼實現
- http://androidxref.com/7.1.1_r6/xref/frameworks/native/services/inputflinger/host/main.cpp : 注冊服務
或者參考他人自己做的示例:
Binder系列8—如何使用Binder , 這里有Native層和JAVA層的示例.
另外, 如果想在JAVA里面簡單實現一個服務, 不一定要定義AIDL. 示例代碼如下:
//服務端代碼
private final IBinder mToken = new Binder() {
private static final int CMD_GET_TOKEN_NAME = 0;
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException
{
switch (code) {
case CMD_GET_TOKEN_NAME:
Log.e(TAG, "CMD_GET_TOKEN_NAME " + this.toString());
reply.writeString(this.toString());
return true;
default:
return super.onTransact(code, data, reply, flags);
}
}
};
//客戶端代碼
private String getTokenName(IBinder cb) {
if (cb == null)
return "ERROR cb == null";
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
String res;
try {
cb.transact(0, data, reply, 0);
res = reply.readString();
} catch (RemoteException e) {
res= "RemoteException";
} finally {
data.recycle();
reply.recycle();
}
return res;
}
10. Reflink
原理篇:
[November 1, 2015] Binder系列1—Binder Driver初探 |
|
[November 2, 2015] Binder系列2—Binder Driver再探 |
|
[November 7, 2015] Binder系列3—啟動ServiceManager |
init.rc如何啟動ServiceManager進程, 如何與驅動交互 |
[November 8, 2015] Binder系列4—獲取ServiceManager |
如果想與ServiceManager進程通信, 如何獲取它在客戶端的代理 |
[November 14, 2015] Binder系列5—注冊服務(addService) |
注冊服務 |
[November 15, 2015] Binder系列6—獲取服務(getService) |
獲取服務 |
[November 21, 2015] Binder系列7—framework層分析 |
Java層的binder機制 |
[November 22, 2015] Binder系列8—如何使用Binder |
Native層和Java層的Binder示例 |
[November 23, 2015] Binder系列9—如何使用AIDL |
AIDL的使用方法 |
[November 28, 2015] Binder系列10—總結 |
|
[October 2, 2016] binderDied()過程分析 |
Binder die機制 |
[October 3, 2016] Binder死亡通知機制之linkToDeath |
如何接收die消息 |
調試篇:
[August 27, 2016] Binder子系統之調試分析(一) |
Binder驅動層有很多開關, 它們的作用以及如何使用它們 |
[August 28, 2016] Binder子系統之調試分析(二) |
同上 |
[September 3, 2016] Binder子系統之調試分析(三) |
同上 |
[May 1, 2017] Binder異常解析 |
如何根據logcat中的異常報錯, 分析binder通信出錯的原因 |