要看得懂android代碼,首先要了解binder機制。binder機制也是android裡面比較難以理解的一塊,這裡記錄一下binder的重要概念以及實現,作為備忘。部分內容來源於網上,如有侵權,請及時告知。
1.binder通信機制概述binder通信是一種client-server的通信結構,
1.從表面上來看,是client通過獲得一個server的代理接口,對server進行直接調用;
2.實際上,代理接口中定義的方法與server中定義的方法是一一對應的;
3.client調用某個代理接口中的方法時,代理接口的方法會將client傳遞的參數打包成為Parcel對象;
4.代理接口將該Parcel發送給內核中的binder driver.
5.server會讀取binder driver中的請求數據,如果是發送給自己的,解包Parcel對象,處理並將結果返回;
6.整個的調用過程是一個同步過程,在server處理的時候,client會block住。
linux中有管道,system V IPC,socket等進程間通信機制,那麼為什麼在android中使用了一個全新的binder通信機制呢?
一、可靠性。在行動裝置上,通常採用基於Client-Server的通信方式來實現網際網路與設備間的內部通信。目前linux支持IPC包括傳統的管道,System V IPC,即消息隊列/共享內存/信號量,以及socket中只有socket支持Client-Server的通信方式。Android系統為開發者提供了豐富進程間通信的功能接口,媒體播放,傳感器,無線傳輸。這些功能都由不同的server來管理。開發都只關心將自己應用程式的client與server的通信建立起來便可以使用這個服務。毫無疑問,如若在底層架設一套協議來實現Client-Server通信,增加了系統的複雜性。在資源有限的手機 上來實現這種複雜的環境,可靠性難以保證。
二、傳輸性能。socket主要用於跨網絡的進程間通信和本機上進程間的通信,但傳輸效率低,開銷大。消息隊列和管道採用存儲-轉發方式,即數據先從發送方緩存區拷貝到內核開闢的一塊緩存區中,然後從內核緩存區拷貝到接收方緩存區,其過程至少有兩次拷貝。雖然共享內存無需拷貝,但控制複雜。比較各種IPC方式的數據拷貝次數。共享內存:0次。Binder:1次。Socket/管道/消息隊列:2次。
IPC數據拷貝次數共享內存0Binder1Socket/管道/消息隊列2三、安全性。Android是一個開放式的平臺,所以確保應用程式安全是很重要的。Android對每一個安裝應用都分配了UID/PID,其中進程的UID是可用來鑑別進程身份。傳統的只能由用戶在數據包裡填寫UID/PID,這樣不可靠,容易被惡意程序利用。而我們要求由內核來添加可靠的UID。
基於以上原因,Android需要建立一套新的IPC機制來滿足系統對通信方式,傳輸性能和安全性的要求,這就是Binder。Binder基於Client-Server通信模式,傳輸過程只需一次拷貝,為發送發添加UID/PID身份,既支持實名Binder也支持匿名Binder,安全性高。
3.service manager顧名思義,service manager就是android下面管理service的一個進程,它本身也是一個service,這裡的service和init.rc裡面的service有一些差別,init.rc中的service都是一個進程,而這裡的service可能不是一個單獨的進程。每一個service在使用之前都必須向SM註冊,每一個client要使用service前都應該先向SM查詢是否存在這個service,如果存在,則給client返回這個service的handle。下面是sm中main函數的關鍵代碼:
int main(int argc, char **argv){ struct binder_state *bs; bs = binder_open(128*1024); if (!bs) { ALOGE("failed to open binder driver\n"); return -1; } if (binder_become_context_manager(bs)) { ALOGE("cannot become context manager (%s)\n", strerror(errno)); return -1; } . //svcmgr_handle的值為0 svcmgr_handle = BINDER_SERVICE_MANAGER; binder_loop(bs, svcmgr_handler); return 0;}
在SM中主要做了如下工作:
打開binder設備,映射128k的內存到應用空間。
指定 svcmgr_handle的值為0,當client與SM通信時,需要先創建一個handle為0的代理binder。
binder_become_context_manager通知binder driver使SM為context manager。
binder_loop是一個死循環,裡面不停的讀binder是否有數據,如果有數據,則解析,對於BR_TRANSACTION,會調用svcmgr_handler來處理。
SM維護了一個svclist來存儲service的信息。一個新的service需要向SM註冊add到這個列表,而client請求時會在svclist裡面查找請求的service。一個service包括兩個重要的信息,handle和name。add和get都會根據name來進行匹配。
下面一個圖片可以簡單說明SM與binder driver之間的關係:
由上可知,service在使用前會先作為client向SM註冊;應用若要使用某一個服務,需要先向SM獲取該服務的handle,然後通過handle來調用該服務提供的方法。
4.ProcessStateProcessState是以單例模式設計的。每個進程在使用binder機制通信時,均需要維護一個ProcessState實例來描述當前進程在binder通信時的binder狀態。
ProcessState有如下2個主要功能:
1.創建一個thread,該線程負責與內核中的binder模塊進行通信,稱該線程為Pool thread;
2.為指定的handle創建一個BpBinder對象,並管理該進程中所有的BpBinder對象。
在Binder IPC中,所有進程均會啟動一個thread來負責與BD(binder driver)來直接通信,也就是不停的讀寫BD,這個線程的實現主體是一個IPCThreadState對象,下面會介紹這個類型。
下面是 Pool thread的啟動方式:
ProcessState::self()->startThreadPool();
BpBinder主要功能是負責client向BD發送調用請求的數據。它是client端binder通信的核心對象,通過調用transact函數向BD發送調用請求的數據,它的構造函數如下:
BpBinder(int32_t handle);
通過BpBinder的構造函數發現,BpBinder會將當前通信中server的handle記錄下來,當有數據發送時,會通知BD數據的發送目標ProcessState通過如下方式來獲取BpBinder對象:
ProcessState::self()->getContextObject(handle);
在這個過程中,ProcessState會維護一個BpBinder的vector mHandleToObject,每當ProcessState創建一個BpBinder的實例時,回去查詢mHandleToObject,如果對應的handle已經有binder指針,那麼不再創建,否則創建binder並插入到mHandleToObject中。
ProcessState創建的BpBinder實例,一般情況下會作為參數構建一個client端的代理接口,這個代理接口的形式為BpINTERFACE,例如在與SM通信時,client會創建一個代理接口BpServiceManager。
IPCThreadState也是以單例模式設計的。由於每個進程只維護了一個ProcessState實例,同時ProcessState只啟動一個Pool thread,也就是說每一個進程只會啟動一個Pool thread,因此每個進程則只需要一個IPCThreadState即可。
Pool thread的實際內容則為:
IPCThreadState::self()->joinThreadPool();
ProcessState中有2個Parcel成員,mIn和mOut,Pool thread會不停的查詢BD中是否有數據可讀,如果有將其讀出並保存到mIn,同時不停的檢查mOut是否有數據需要向BD發送,如果有,則將其內容寫入到BD中,總而言之,從BD中讀出的數據保存到mIn,待寫入到BD中的數據保存在了mOut中。
ProcessState中生成的BpBinder實例通過調用IPCThreadState的transact函數來向mOut中寫入數據,這樣的話這個binder IPC過程的client端的調用請求的發送過程就明了了。
IPCThreadState有兩個重要的函數,talkWithDriver函數負責從BD讀寫數據,executeCommand函數負責解析並執行mIn中的數據。
為server端提供接口,它的子類聲明了service能夠實現的所有的方法;
6.2基類IBinderBBinder與BpBinder均為IBinder的子類,因此可以看出IBinder定義了binder IPC的通信協議,BBinder與BpBinder在這個協議框架內進行的收和發操作,構建了基本的binder IPC機制。
6.3基類BpRefBaseclient端在查詢SM獲得所需的的BpBinder後,BpRefBase負責管理當前獲得的BpBinder實例。
7.兩個接口類7.1 BpINTERFACE如果client想要使用binder IPC來通信,那麼首先會從SM出查詢並獲得server端service的BpBinder,在client端,這個對象被認為是server端的遠程代理。為了能夠使client能夠想本地調用一樣調用一個遠程server,server端需要向client提供一個接口,client在在這個接口的基礎上創建一個BpINTERFACE,使用這個對象,client的應用能夠想本地調用一樣直接調用server端的方法。而不用去關心具體的binder IPC實現。
下面看一下BpINTERFACE的原型:
class BpINTERFACE : public BpInterface<IINTERFACE>
順著繼承關係再往上看
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
BpINTERFACE分別繼承自INTERFACE,和BpRefBase;
● BpINTERFACE既實現了service中各方法的本地操作,將每個方法的參數以Parcel的形式發送給BD。例如BpServiceManager的
virtual status_t addService(const String16& name, const sp<IBinder>& service){ Parcel data, reply; data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); data.writeString16(name); data.writeStrongBinder(service); status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); return err == NO_ERROR ? reply.readExceptionCode() : err;}
● 同時又將BpBinder作為了自己的成員來管理,將BpBinder存儲在mRemote中,BpServiceManager通過調用BpRefBase的remote()來獲得BpBinder指針。
7.2 BnINTERFACE在定義android native端的service時,每個service均繼承自BnINTERFACE(INTERFACE為service name)。BnINTERFACE類型定義了一個onTransact函數,這個函數負責解包收到的Parcel並執行client端的請求的方法。
順著BnINTERFACE的繼承關係再往上看,
class BnINTERFACE: public BnInterface<IINTERFACE>
IINTERFACE為client端的代理接口BpINTERFACE和server端的BnINTERFACE的共同接口類,這個共同接口類的目的就是保證service方法在C-S兩端的一致性。
再往上看
class BnInterface : public INTERFACE, public BBinder
同時我們發現了BBinder類型,這個類型又是幹什麼用的呢?既然每個service均可視為一個binder,那麼真正的server端的binder的操作及狀態的維護就是通過繼承自BBinder來實現的。可見BBinder是service作為binder的本質所在。
那麼BBinder與BpBinder的區別又是什麼呢?
其實它們的區別很簡單,BpBinder是client端創建的用於消息發送的代理,而BBinder是server端用於接收消息的通道。查看各自的代碼就會發現,雖然兩個類型均有transact的方法,但是兩者的作用不同,BpBinder的transact方法是向IPCThreadState實例發送消息,通知其有消息要發送給BD;而BBinder則是當IPCThreadState實例收到BD消息時,通過BBinder的transact的方法將其傳遞給它的子類BnSERVICE的onTransact函數執行server端的操作。
8.ParcelParcel是binder IPC中的最基本的通信單元,它存儲C-S間函數調用的參數.但是Parcel只能存儲基本的數據類型,如果是複雜的數據類型的話,在存儲時,需要將其拆分為基本的數據類型來存儲。
簡單的Parcel讀寫不再介紹,下面著重介紹一下2個函數。
8.1 writeStrongBinder當一個service 調用add_service把自己加入到SM中時,就會遇到這種情況,如下(IServiceManager.cpp):
virtual status_t addService(const String16& name, const sp<IBinder>& service, bool allowIsolated) { Parcel data, reply; data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); data.writeString16(name); data.writeStrongBinder(service); data.writeInt32(allowIsolated ? 1 : 0); status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); return err == NO_ERROR ? reply.readExceptionCode() : err; }
其中writeStrongBinder(Parcel.cpp)如下:
status_t Parcel::writeStrongBinder(const sp<IBinder>& val){ return flatten_binder(ProcessState::self(), val, this);}
接著看flatten_binder:
status_t flatten_binder(const sp<ProcessState>& /*proc*/, const sp<IBinder>& binder, Parcel* out){ flat_binder_object obj; obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; if (binder != NULL) { IBinder *local = binder->localBinder(); if (!local) { BpBinder *proxy = binder->remoteBinder(); if (proxy == NULL) { ALOGE("null proxy"); } const int32_t handle = proxy ? proxy->handle() : 0; obj.type = BINDER_TYPE_HANDLE; obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */ obj.handle = handle; obj.cookie = 0; } else { obj.type = BINDER_TYPE_BINDER; obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs()); obj.cookie = reinterpret_cast<uintptr_t>(local); } } else { obj.type = BINDER_TYPE_BINDER; obj.binder = 0; obj.cookie = 0; } return finish_flatten_binder(binder, obj, out);}
addService的參數為一個BnINTERFACE類型指針,BnINTERFACE又繼承自BBinder:
BBinder* BBinder::localBinder() { return this; }
所以寫入到Parcel的binder類型為BINDER_TYPE_BINDER,在SM中,當service的binder類型不為BINDER_TYPE_HANDLE時,SM將不會將此service添加到svclist,但是很顯然每個service的添加都是成功的,addService在開始傳遞的binder類型為BINDER_TYPE_BINDER,SM收到的binder類型為BINDER_TYPE_HANDLE,那麼這個過程當中究竟發生了什麼?這個問題是這樣的,在binder driver中(Binder.c)由以下代碼:
static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply){.. if (fp->type == BINDER_TYPE_BINDER) fp->type = BINDER_TYPE_HANDLE; else fp->type = BINDER_TYPE_WEAK_HANDLE; fp->handle = ref->desc;..}
由之前我們已經知道,SM只是保存了server binder的handle和name,那麼當client需要和某個service通訊的時候,如何獲得service的binder呢?接著看readStrongBinder
8.2 readStrongBinder當server端收到client的調用請求之後,如果需要返回一個binder時,可以向BD發送這個binder,當IPCThreadState實例收到這個返回的Parcel時,client可以通過這個函數將這個被server返回的binder讀出。
sp<IBinder> Parcel::readStrongBinder() const{ sp<IBinder> val; unflatten_binder(ProcessState::self(), *this, &val); return val;}
再看unflatten_binder:
status_t unflatten_binder(const sp<ProcessState>& proc, const Parcel& in, sp<IBinder>* out){ const flat_binder_object* flat = in.readObject(false); if (flat) { switch (flat->type) { case BINDER_TYPE_BINDER: *out = reinterpret_cast<IBinder*>(flat->cookie); return finish_unflatten_binder(NULL, *flat, in); case BINDER_TYPE_HANDLE: *out = proc->getStrongProxyForHandle(flat->handle); return finish_unflatten_binder( static_cast<BpBinder*>(out->get()), *flat, in); } } return BAD_TYPE;}
發現如果server返回的binder類型為BINDER_TYPE_BINDER的話,也就是返回一個binder引用的話,直接獲取這個binder;如果server返回的binder類型為BINDER_TYPE_HANDLE時,也就是server返回的僅僅是binder的handle,那麼需要重新創建一個BpBinder返回給client。
有上面的代碼可以看出,SM保存的service的binder僅僅是一個handle,而client則是通過向SM獲得這個handle,從而重新構建代理binder與server通信。
這裡順帶提一下一種特殊的情況,binder通信的雙方即可作為client,也可以作為server.也就是說此時的binder通信是一個半雙工的通信。那麼在這種情況下,操作的過程會比單工的情況複雜,但是基本的原理是一樣的,有興趣可以分析一下MediaPlayer和MediaPlayerService的例子。
以上就是涉及到binder通訊的一些比較重要的點。關於binder的具體實現就需要查看binder driver的代碼了。
本文屬原創,轉載請註明出處,違者必糾。
關注微信公眾號:程式設計師互動聯盟(coder_online)
更有(java/C/C++/Linux/Android)高手幫你解決難題,和你互動,討論編程未來。
長按二維碼識別關注程式設計師互動聯盟