1. 程式人生 > >Android IPC 機制詳解:IBinder

Android IPC 機制詳解:IBinder

http://www.linuxgraphics.cn/android/ipc_ibinder.html

IBinder 介面

IBinder介面是對跨程序的物件的抽象。普通物件在當前程序可以訪問,如果希望物件能被其它程序訪問,那就必須實現IBinder介面。IBinder介面可以指向本地物件,也可以指向遠端物件,呼叫者不需要關心指向的物件是本地的還是遠端。

transact是IBinder介面中一個比較重要的函式,它的函式原型如下:

 

android中的IPC的基本模型是基於客戶/伺服器(C/S)架構的。

客戶端 請求通過核心模組中轉 服務端

如果IBinder指向的是一個客戶端代理,那transact只是把請求傳送給伺服器。服務端的IBinder的transact則提供了實際的服務。

客戶端

BpBinder是遠端物件在當前程序的代理,它實現了IBinder介面。它的transact 函式實現如下:

 

引數說明:

  • code 是請求的ID號;
  • data 是請求的引數;
  • reply 是返回的結果;
  • flags 一些額外的標識,如FLAG_ONEWAY。通常為0。

transact只是簡單的呼叫了IPCThreadState::self()的transact,在 IPCThreadState::transact中:

 

這裡transact把請求經核心模組傳送了給服務端,服務端處理完請求之後,沿原路返回結果給呼叫者。這裡也可以看出請求是同步操作,它會等待直到結果返回為止。

在BpBinder之上進行簡單包裝,我們可以得到與服務物件相同的介面,呼叫者無需要關心呼叫的物件是遠端的還是本地的。拿ServiceManager來說: (frameworks/base/libs/utils/IServiceManager.cpp)

 

BpServiceManager實現了 IServiceManager和IBinder兩個介面,呼叫者可以把BpServiceManager的物件看作是一個 IServiceManager物件或者IBinder物件。當呼叫者把BpServiceManager物件當作IServiceManager物件使用時,所有的請求只是對BpBinder::transact的封裝。這樣的封裝使得呼叫者不需要關心IServiceManager物件是本地的還是遠端的了。

客戶通過defaultServiceManager函式來建立BpServiceManager物件: (frameworks/base/libs/utils/IServiceManager.cpp)

 

先通過ProcessState::self()->getContextObject(NULL)建立一個Binder物件,然後通過 interface_cast和IMPLEMENT_META_INTERFACE(ServiceManager, “android.os.IServiceManager”)把Binder物件包裝成 IServiceManager物件。原理上等同於建立了一個BpServiceManager物件。

ProcessState::self()->getContextObject呼叫ProcessState::getStrongProxyForHandle建立代理物件:

 

如果handle為空,預設為context_manager物件,context_manager實際上就是 ServiceManager。

服務端

服務端也要實現IBinder介面,BBinder類對IBinder介面提供了部分預設實現,其中transact的實現如下:

 

PING_TRANSACTION請求用來檢查物件是否還存在,這裡簡單的把 pingBinder的返回值返回給呼叫者。其它的請求交給onTransact處理。onTransact是BBinder裡宣告的一個 protected型別的虛擬函式,這個要求它的子類去實現。比如CameraService裡的實現如下:

 

由此可見,服務端的onTransact是一個請求分發函式,它根據請求碼(code)做相應的處理。

訊息迴圈

服務端(任何程序都可以作為服務端)有一個執行緒監聽來自客戶端的請求,並迴圈處理這些請求。

如果在主執行緒中處理請求,可以直接呼叫下面的函式:

 

如果想在非主執行緒中處理請求,可以按下列方式:

 

startThreadPool的實現原理:

 

這裡建立了PoolThread的物件,實現上就是建立了一個執行緒。所有的執行緒類都要實現threadLoop虛擬函式。PoolThread的threadLoop的實現如下:

 

上述程式碼,簡而言之就是建立了一個執行緒,然後線上程裡呼叫 IPCThreadState::self()->joinThreadPool函式。

下面再看joinThreadPool的實現:

 

這個函式在迴圈中重複執行下列動作:

  1. talkWithDriver 通過ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)讀取請求和寫回結果。
  2. executeCommand 執行相應的請求

在IPCThreadState::executeCommand(int32_t cmd)函式中:

  1. 對於控制物件生命週期的請求,像BR_ACQUIRE/BR_RELEASE直接做了處理。
  2. 對於BR_TRANSACTION請求,它呼叫被請求物件的transact函式。

按下列方式呼叫實際的物件:

 

如果tr.target.ptr不為空,就把tr.cookie轉換成一個Binder物件,並呼叫它的 transact函式。如果沒有目標物件,就呼叫 the_context_object物件的 transact函式。奇怪的是,根本沒有誰對the_context_object進行初始化, the_context_object是空指標。原因是context_mgr的請求發給了 ServiceManager,所以根本不會走到else 語句裡來。

核心模組

android使用了一個核心模組binder來中轉各個程序之間的訊息。模組原始碼放在binder.c裡,它是一個字元驅動程式,主要通過 binder_ioctl與使用者空間的程序交換資料。其中BINDER_WRITE_READ用來讀寫資料,資料包中有一個cmd域用於區分不同的請求:

  1. binder_thread_write用於傳送請求或返回結果。
  2. binder_thread_read用於讀取結果。

從binder_thread_write中呼叫binder_transaction中轉請求和返回結果,binder_transaction的實現如下:

對請求的處理:

  1. 通過物件的handle找到物件所在的程序,如果handle為空就認為物件是context_mgr,把請求發給context_mgr所在的程序。
  2. 把請求中所有的binder物件全部放到一個RB樹中。
  3. 把請求放到目標程序的佇列中,等待目標程序讀取。

如何成為context_mgr呢?核心模組提供了BINDER_SET_CONTEXT_MGR呼叫:

 

ServiceManager(frameworks/base/cmds/servicemanager)通過下列方式成為了 context_mgr程序:

 

如何得到服務物件的handle

  1. 服務提供者通過defaultServiceManager得到ServiceManager物件,然後呼叫addService向服務管理器註冊。
  2. 服務使用者通過defaultServiceManager得到ServiceManager物件,然後呼叫getService通過服務名稱查詢到服務物件的handle。

如何通過服務物件的handle找到服務所在的程序

表示服務管理器的handle,getService可以查詢到系統服務的handle。這個handle只是代表了服務物件,核心模組是如何通過handle找到服務所在的程序的呢?

  1. 對於ServiceManager: ServiceManager呼叫了binder_become_context_manager使用自己成為context_mgr,所有handle為0的請求都會被轉發給ServiceManager。
  2. 對於系統服務和應用程式的Listener,在第一次請求核心模組時(比如呼叫 addService),核心模組在一個RB樹中建立了服務物件和程序的對應關係。
  3. 請求服務時,核心先通過handle找到對應的程序,然後把請求放到服務程序的佇列中。 

 

C呼叫JAVA

前面我們分析的是C程式碼的處理。對於JAVA程式碼,JAVA呼叫C的函式通過JNI呼叫即可。從核心時讀取請求是在C程式碼(executeCommand)裡進行了,那如何在C程式碼中呼叫那些用JAVA實現的服務呢?

android_os_Binder_init裡的JavaBBinder對Java裡的Binder物件進行包裝。

JavaBBinder::onTransact呼叫Java裡的execTransact函式:

 

廣播訊息

binder不提供廣播訊息,不過可以ActivityManagerService服務來實現廣播。 (frameworks/base/core/java/android/app/ActivityManagerNative.java)

接收廣播訊息需要實現介面BroadcastReceiver,然後呼叫ActivityManagerProxy::registerReceiver註冊。

觸發廣播呼叫ActivityManagerProxy::broadcastIntent。(應用程式並不直接呼叫它,而是呼叫Context對它的包裝)

Reference

作者聯絡方式:李先靜 <xianjimli at hotmail dot com>