Binder機制和共享記憶體 native
匿名Binder:
即沒有向ServiceManager註冊的Binder。
Binder通訊並不絕對依賴ServiceManager,它只是一個域名解析器。可有可無,有更方便。
所以可以看到ContextImpl$ApplicationThread,ContentProvider$Transport都是沒有向ServiceManager addService,只要client程序能獲取proxy即可。如果本來已經建立Binder通訊的,就可以直接獲取到proxy而不用通過ServiceManager。
實名Binder:
即通過ServiceManager#addService的Binder子類。
client獲取proxy:
ServiceManagerNative#getService
public IBinder getService(String name) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IServiceManager.descriptor); data.writeString(name); mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0); IBinder binder = reply.readStrongBinder(); reply.recycle(); data.recycle(); return binder; }
android_os_parcel.cpp#android_os_Parcel_readStrongBinder
static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr) { Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); if (parcel != NULL) { return javaObjectForIBinder(env, parcel->readStrongBinder()); } return NULL; }
上面的javaObjectForIBinder方法會把native的Ibinder物件變成一個java層的Ibinder,native層的IBinder應該是被封裝在so庫裡,在原始碼中搜索不到。
Parcel.cpp#readStrongBinder()
sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}
Parcel.cpp#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;
}
ProccessState.cpp#getStrongProxyForHandle
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;
AutoMutex _l(mLock);
handle_entry* e = lookupHandleLocked(handle);
if (e != NULL) {
// We need to create a new BpBinder if there isn't currently one, OR we
// are unable to acquire a weak reference on this current one. See comment
// in getWeakProxyForHandle() for more info about this.
IBinder* b = e->binder;
if (b == NULL || !e->refs->attemptIncWeak(this)) {
if (handle == 0) {
// Special case for context manager...
// The context manager is the only object for which we create
// a BpBinder proxy without already holding a reference.
// Perform a dummy transaction to ensure the context manager
// is registered before we create the first local reference
// to it (which will occur when creating the BpBinder).
// If a local reference is created for the BpBinder when the
// context manager is not present, the driver will fail to
// provide a reference to the context manager, but the
// driver API does not return status.
//
// Note that this is not race-free if the context manager
// dies while this code runs.
//
// TODO: add a driver API to wait for context manager, or
// stop special casing handle 0 for context manager and add
// a driver API to get a handle to the context manager with
// proper reference counting.
Parcel data;
status_t status = IPCThreadState::self()->transact(
0, IBinder::PING_TRANSACTION, data, NULL, 0);
if (status == DEAD_OBJECT)
return NULL;
}
b = new BpBinder(handle);
e->binder = b;
if (b) e->refs = b->getWeakRefs();
result = b;
} else {
// This little bit of nastyness is to allow us to add a primary
// reference to the remote proxy when this team doesn't have one
// but another team is sending the handle to us.
result.force_set(b);
e->refs->decWeak(this);
}
}
return result;
}
上面的方法就根據handle產生了一個BpBinder
可以看到從ServiceManager得到的proxy是一個BpBinder,而後面是怎麼從java層呼叫到這個BpBinder的,在java層的Binder的程式碼中沒有體現出來。在proxy的程式碼中,最終會呼叫mRemote.transact(...),然而在java層的Binder程式碼中transact方法程式碼如下:
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
Proxy中mRemote的由來:
mRemote是一個BinderProxy物件,BinderProxy在Binder.java中。
mRemote是怎麼來的呢,看下面:
在傳遞IBinder物件時(比如:Proxy物件中的mRemote,即服務端),會使用Parcel的方法把IBinder寫到Binder驅動中。
/**
* Write an object into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public final void writeStrongBinder(IBinder val) {
nativeWriteStrongBinder(mNativePtr, val);
}
jni如下:
static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
}
}
native原始碼如下:
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}
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);
}
上面的方法就是將IBinder扁平化,有點像序列化,就是將IBinder子類物件(即服務端物件)作類似序列化,但是不是完全按照原來的樣子序列化,還會做其他建立物件或者修改的操作。
然後client端,拿到這個從服務端程序傳過來的服務端物件,需要通過Parcel#readStrongBinder()來獲取可以跟驅動打交道,還可以讓驅動知道要找哪個服務端的IBinder物件。這個獲取到IBinder就是Proxy中的mRemote,這是一個BinderProxy物件。看Parcel#readStrongBinder()
/**
* Read an object from the parcel at the current dataPosition().
*/
public final IBinder readStrongBinder() {
return nativeReadStrongBinder(mNativePtr);
}
jni方法如下(在parcel.cpp):
static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
return javaObjectForIBinder(env, parcel->readStrongBinder());
}
return NULL;
}
native方法如下:
sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}
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;
}
現在看看
ibinderForJavaObject(env, object) //把java層 IBinder變成native層的IBinder物件
javaObjectForIBinder(env, parcel->readStrongBinder())//把native層的IBinder物件變成java層的IBinder物件
就是ibinderForJavaObject方法把服務端IBinder扁平化寫到Binder驅動,而javaObjectForIBinder根據在Binder驅動中扁平化的服務端IBinder,建立一個BinderProxy物件。
最終BinderProxy使用transact方法和Binder驅動互動,並最終將資訊傳到對應的服務端Binder,會呼叫Binder#execTransact()->Binder子類#onTransact()
下面是BinderProxy#transact
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
return transactNative(code, data, reply, flags);
}
jni方法(android_util_binder.cpp):
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
if (dataObj == NULL) {
jniThrowNullPointerException(env, NULL);
return JNI_FALSE;
}
Parcel* data = parcelForJavaObject(env, dataObj);
if (data == NULL) {
return JNI_FALSE;
}
Parcel* reply = parcelForJavaObject(env, replyObj);
if (reply == NULL && replyObj != NULL) {
return JNI_FALSE;
}
IBinder* target = (IBinder*)
env->GetLongField(obj, gBinderProxyOffsets.mObject);
if (target == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!");
return JNI_FALSE;
}
ALOGV("Java code calling transact on %p in Java object %p with code %" PRId32 "\n",
target, obj, code);
bool time_binder_calls;
int64_t start_millis;
if (kEnableBinderSample) {
// Only log the binder call duration for things on the Java-level main thread.
// But if we don't
time_binder_calls = should_time_binder_calls();
if (time_binder_calls) {
start_millis = uptimeMillis();
}
}
//printf("Transact from Java code to %p sending: ", target); data->print();
status_t err = target->transact(code, *data, reply, flags);
//if (reply) printf("Transact from Java code to %p received: ", target); reply->print();
if (kEnableBinderSample) {
if (time_binder_calls) {
conditionally_log_binder_call(start_millis, target, code);
}
}
if (err == NO_ERROR) {
return JNI_TRUE;
} else if (err == UNKNOWN_TRANSACTION) {
return JNI_FALSE;
}
signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
return JNI_FALSE;
}
Parcel類
這個類和Parcelable類是完全不同的,這個是為Binder通訊定製的類,native層對應了Parcel.cpp。這個類可以產生BpBinder
關於ServiceManager與Binder的關係看下面的優秀博文即可,不重複寫了。
BinderInternal.getContextObject()
Binder IPC通訊:形式上物件只能傳遞parcel和IBinder型別的引數,但是parcel中可以傳遞object型別,也就是可以傳遞任何型別的變數。
binder通訊和共享記憶體的區別和聯絡
下圖是binder通訊,首先是應用程式把資料通過parcel,應該是通過malloc分配一個空間,雖然是在使用jni呼叫,並最終在native實現的程式中去把一個object進行扁平化儲存在上面分配的空間的,但是無論是java層還是native,只要是是非核心程式(核心程式包括作業系統的核心程序和驅動程式),而在parcel進行的分配空間和對object扁平化時,程式是執行在使用者空間的(cpu處於使用者態),因為這個應用程式主動呼叫的程式,而這個此時還不涉及到系統呼叫,所以沒有主動進入核心態執行。所以parcel的write方法寫入的東西都是寫入了應用程序的使用者態空間,最終會把把這些東西通過系統呼叫去訪問binder驅動裝置,並寫入屬於binder驅動的核心空間中,而具體是binder驅動的核心空間的哪一塊,就得看binder怎麼分了。binder會為每個binder通訊的程序分一塊空間,而應用程序訪問binder驅動並且把東西寫入驅動的核心空間時,是寫入到binder驅動核心空間中分給接收應用程序的那一塊記憶體。這就是通過把使用者空間中的資料複製到核心空間中,然後把binder驅動作為一箇中介儲存空間;即傳送程序把資料寫到binder驅動的內和空間中,然後接收程序從核心空間中把內容複製到屬於自己的使用者空間。
共享記憶體:有兩種方式,一種是shmget,這種方式是直接把多個程序各自的一個邏輯地址(各個程序的邏輯地址都不同)對映到同一個實體記憶體空間,這種方式是讀寫是最快,而且各程序無需去複製到自己的程序空間中,多程序通訊的效率比較高,但是使用起來比較複雜,shmget方式和mmap記憶體對映方式就類似低階語言和高階語言的區別,效能和程式碼讀寫維護的便利的權衡。mmap記憶體對映,使用相對於shmget方式比較簡便,而且雖然最終對映的檔案還是說佔用實體記憶體空間,但是一個檔案可以分塊載入到記憶體中,那麼mmap就可以使用更大的共享記憶體區了,而shmget是直接對映實體記憶體,所以共享區大小受限。而且mmap因為是對映磁碟的檔案,所以在關閉或者關機時載入到記憶體中的檔案會儲存到磁碟中,就是mmap實現的共享記憶體中的內容可以斷電永久儲存。
shmget的api:
(1)通過int shmget(key_t key, size_t size, int shmflg);在實體記憶體建立一個共享記憶體,返回共享記憶體的編號。
(2)通過void *shmat(int shmid, constvoid shmaddr,int shmflg);連線成功後把共享記憶體區物件對映到呼叫程序的地址空間
(3)通過void *shmdt(constvoid* shmaddr);斷開使用者級頁表到共享記憶體的那根箭頭。
(4)通過int shmctl(int shmid, int cmd, struct shmid_ds* buf);釋放實體記憶體中的那塊共享記憶體。
總結mmap和shm:
1、mmap是在磁碟上建立一個檔案,每個程序地址空間中開闢出一塊空間進行對映。
而對於shm而言,shm每個程序最終會對映到同一塊實體記憶體。shm儲存在實體記憶體,這樣讀寫的速度要比磁碟要快,但是儲存量不是特別大。
2、相對於shm來說,mmap更加簡單,呼叫更加方便,所以這也是大家都喜歡用的原因。
3、另外mmap有一個好處是當機器重啟,因為mmap把檔案儲存在磁碟上,這個檔案還儲存了作業系統同步的映像,所以mmap不會丟失,但是shmget就會丟失。
Binder相關的jni程式碼和c程式碼及標頭檔案路徑:
frameworks/base/core/jni
frameworks/native/include
frameworks/native/libs/binder