Binder筆記

  • 進程隔離:一個進程佔一個內存空間,各個進程的內存空間完全隔離,數據不共享。
  • 內核空間(Kernel)是系統內核運行的空間,用戶空間(User Space)是用戶程序運行的空間。爲了保證安全性,它們之間是隔離的。
  • Linux Kernel獨立於普通的應用程序,可以訪問任何內存空間。
  • 應用程序訪問內核的唯一方式就是系統調用,當應用程序發起系統調用時,內核會檢查應用程序是否越權訪問,若否,則執行內核代碼。當應用程序成功調用起內核代碼時則稱應用程序的進程處於內核態,否則處於用戶態。
  • 傳統的 IPC 機制如管道、Socket,它們本身就是內核的一部分,所以通過內核來支持進程間通信自然是沒問題的。但是 Binder 並不是 Linux 系統內核的一部分,那怎麼辦呢?這時候就通過動態添加一個內核模塊,並由該模塊支持Binder IPC,我們把這個模塊稱爲Binder驅動。
  • Client進程和Server進程通信時ServiceManager相當於通訊錄,Binder驅動相當於基站
  • Binder的通信模型如下圖:
    這裏寫圖片描述

    整個通信步驟如下:

    1、SM建立(建立通信錄);首先有一個進程向驅動提出申請爲SM;驅動同意之後,SM進程負責管理Service(注意這裏是Service而不是Server,因爲如果通信過程反過來的話,那麼原來的客戶端Client也會成爲服務端Server)不過這時候通信錄還是空的,一個號碼都沒有。

    2、各個Server向SM註冊(完善通信錄);每個Server端進程啓動之後,向SM報告,我是zhangsan, 要找我請返回0x1234(這個地址沒有實際意義,類比);其他Server進程依次如此;這樣SM就建立了一張表,對應着各個Server的名字和地址;就好比B與A見面了,說存個我的號碼吧,以後找我撥打10086;

    3、Client想要與Server通信,首先詢問SM;請告訴我如何聯繫zhangsan,SM收到後給他一個號碼0x1234;Client收到之後,開心滴用這個號碼撥通了Server的電話,於是就開始通信了。

  • 從面向對象的角度來說,Client進程獲得了Server進程的Binder的遠程引用後才能進行IPC通信的。可是每個Server進程生成的Binder對象的內存地址都是隨機的,Client是無法知道這個Binder地址的,怎麼辦?所以才需要一個SM進程,Server啓動時將自己Binder引用註冊到SM中,Client進程再從SM中查詢獲取。但是這樣又出現了一個問題:不論是Client進程向SM查詢,還是Server進程向SM註冊,也都要先獲得SM進程的Binder的遠程引用才行。那麼SM進程的Binder的遠程引用從哪裏來的呢?原來當一個進程使用BINDER_SET_CONTEXT_MGR命令將自己註冊成SM時Binder驅動會自動爲它創建Binder實體,其次這個Binder的引用在其它進程中都固定爲0而無須通過其它手段獲得。也就是說,一個Server若要向SM註冊自己Binder就必需通過0這個引用號和SM的Binder通信,Client也只能利用保留的0號引用向SM請求訪問某個Binder。

  • 上述提到的0引用號,就是指binder_ref中的handle(即:binder_ref.desc)
  • Binder實體只存在於Server進程,Client進程只不過是持有了Server端的代理而已。對於同一個Binder實體,一個Client進程只會持有它的一個Binder代理。但是一個Binder實體,可以存在多個Binder代理,即多個Client進程都持有它的Binder代理。
  • 在Binder驅動中保存了Binder實體和Binder代理的映射關係。在驅動中,Binder本地對象的代表是一個叫做binder_node的數據結構,Binder代理對象是用binder_ref代表的;有的地方把Binder本地對象直接稱作Binder實體,把Binder代理對象直接稱作Binder引用(句柄),其實指的是Binder對象在驅動裏面的表現形式
  • IBinder是一個接口,只要實現了這個接口,就表示該對象可以被跨進程引用。Binder代理就是Binder實體的遠程引用。
  • 這裏寫圖片描述
    我們編寫的ICompute.aidl文件經過編譯後會生成接口文件ICompute.java。
public interface ICompute extends android.os.IInterface {
    ...
    public void add(int a, int b) throws android.os.RemoteException;
}

這裏Stub和Stub.Proxy均實現了接口ICompute,但目的不同。Stub實現ICompute是爲了提供具體的實現步驟,而Stub.Proxy實現ICompute則爲了封裝數據並傳遞給Binder驅動。

客戶端調用代理IBinder.transact(),Binder驅動會轉換成實體IBinder.transact()。實體IBinder接口的實現類是Binder,在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;
    }

Stub會重寫onTransact(),並且在該方法中調用ICompute.java中的接口方法。返回值最終會通過Binder驅動傳遞到客戶端的代理IBinder,作爲代理IBinder的返回值返回。

  • 這種傳統的 IPC 通信方式有兩個問題:

性能低下,一次數據傳遞需要經歷:內存緩存區 –> 內核緩存區 –> 內存緩存區,需要 2 次數據拷貝;

接收數據的緩存區由數據接收進程提供,但是接收進程並不知道需要多大的空間來存放將要傳遞過來的數據,因此只能開闢儘可能大的內存空間或者先調用 API 接收消息頭來獲取消息體的大小,這兩種做法不是浪費空間就是浪費時間。

  • 內存映射:Binder IPC 機制中涉及到的內存映射通過 mmap() 來實現,mmap() 是操作系統中一種內存映射的方法。內存映射簡單的講就是將用戶空間與內核空間的內存區域建立起映射。映射關係建立後,用戶對這塊內存區域的修改可以直接反應到內核空間;反之內核空間對這段區域的修改也能直接反應到用戶空間。
  • Binder IPC通過內存映射減少了一次的拷貝,且內核程序可以訪問接收進程的內存空間,並直接在接收進程開闢出所需大小的空間,最後將該空間與內核中的數據接收緩存區建立映射。從而解決了傳統IPC機制的兩個痛點。簡而言之,傳統的IPC是接收進程訪問內核獲取所需的內存大小,然後自己再開闢出空間。現在是,內核直接在接收進程開闢出了大小合適的內存空間。
  • 在Binder IPC通信時,之所以要在內核空間中開闢出內核緩存區數據接收緩存區,是因爲在進程間通信時爲了防止讀寫錯亂,而給每個進程分別開闢兩個緩存區,一個專門“讀”,一個專門“寫”。內核緩存區開闢出來專門給數據發送進程寫數據,數據接收緩存區開闢出來專門給數據接收進程讀數據。
  • Binder框架定義了四個角色:Server,Client,ServiceManager(以後簡稱SMgr)以及Binder驅動。其中 Server,Client,SMgr 運行於用戶空間,驅動運行於內核空間。這四個角色的關係和互聯網類似:Server是服務器,Client是客戶終端,SMgr是域名服務器(DNS),驅動是路由器。
    這裏寫圖片描述
    這裏寫圖片描述
  • SM是開機的時候通過init.rc文件啓動的,這就保證了它是系統中第一個註冊成服務大管家的Server,而WMS,AMS這些則是在SystemServer中啓動的

  • 獲取SM服務,調用ServiceManager.getService(name),比如ServiceManager.getService("activity");

public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        //其中BinderInternal.getContextObject()是native方法:
        //public static final native IBinder getContextObject();
        return sServiceManager;
    }

static public IServiceManager asInterface(IBinder obj)
    {
        if (obj == null) {
            return null;
        }
        IServiceManager in =
            (IServiceManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }

        return new ServiceManagerProxy(obj);
    }

public IBinder getService(String var1) throws RemoteException {
        Parcel var2 = Parcel.obtain();
        Parcel var3 = Parcel.obtain();
        var2.writeInterfaceToken("android.os.IServiceManager");
        var2.writeString(var1);
        this.mRemote.transact(1, var2, var3, 0);
        IBinder var5 = var3.readStrongBinder();
        var3.recycle();
        var2.recycle();
        return var5;
    }
  • BinderInternal.getContextObject()
public static final native IBinder getContextObject();
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
 return getStrongProxyForHandle(0);  
}

因爲和Binder驅動打交道,最終都得通過JNI調用本地代碼來實現。
而在native層中getContextObject方法通過ProcessState把 創建的對象BpBinder轉換爲Java層的IBinder對象。

IBinder只是一個接口類,
在native層,IBinder實現類爲BpBinder(由ProcessState創建)
而在Java層,IBinder實現類則是BinderProxy。

  • Binder驅動爲每個進程分配一個數據結構binder_proc,進程中每個用到了Binder通信的線程,Binder驅動也會爲該線程分配一個數據結構binder_thread
  • Binder 服務在調用期間拋出了RuntimeException異常,服務端會Crash麼?
    服務端並不會Crash,RuntimeException被Binder服務端線程捕捉,隨後將異常信息寫入到reply中,發回Binder客戶端進程,最後會客戶端binder線程拋出這個異常,如果沒有捕捉這個RuntimeException,那麼Binder客戶端進程會Crash。

  • system_server與zygote的通信使用的是socket

  • 在應用調用Application.onCreate之前,Binder驅動的初始化就已經完成了,所以直接就可以使用Binder來通信。
  • -
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章