1. 程式人生 > >Binder機制和共享記憶體 native

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