瞭解 Binder 看這一篇就夠了

Binder

一. 前言

筆者一直堅信,在不介紹上下文的情況下直入主題就是在耍流氓,只有建立好完善的知識體系後才能更好的理解和記憶相關內容。因此在介紹Binder之前,先來梳理一下大致的脈絡。

二. 進程和IPC

2.1 進程

在 Android 中,可以簡單的把進程看作是 JVM 虛擬機,每個進程就是一個獨立的虛擬機,它擁有自己特定的資源,例如內存空間。多個進程之間的內存是不共享的,就像平行世界,互相無法直接訪問彼此的數據,這就是常說的進程隔離。由於這個機制,進程間的數據交流就需要採用特殊的通信機制:進程間通信(IPC)。

2.2 IPC

我們都知道 Binder 是一種 IPC方式,但Linux本身也有其他的跨進程交互方式的, 那 Android 幹嘛要自己重新定義了一個 Binder 機制,並作爲其服務及組件的主要交互方式呢?原因有兩個:提高效率 & 符合C/S架構。

三. 傳統 IPC

理解了上面的幾個概念,我們再來看看傳統的 IPC 方式中,進程之間是如何實現通信的。

傳統 IPC 方式使用到了一個非常重要的概念,那就是**進程的內存空間組成。**一個進程的內存可以分爲兩類:**用戶空間 & 內核空間,其中內核空間是所有進程共用的。**以下爲多進程中不同內存空間之間的關係:

  • 用戶空間 & 用戶控件

    數據不可相互獨立,不可直接交互

  • 內核空間 & 內核空間

    數據可共享

  • 用戶空間 & 內核空間

    用戶控件和內核空間之間可以通過系統調用進行交互,主要執行函數如下:

    // 將用戶空間的數據拷貝到內核空間
    copy_from_user();
    // 將內核空間的數據拷貝到用戶空間
    copy_to_user();
    

由此可知傳統 IPC 實現的大致思路如下:
在這裏插入圖片描述
可以看出通過這種方式執行一次 IPC 操作需要進行2次讀寫操作。

四. Binder 原理

理解了上面的傳統 IPC 實現原理,通過讀寫內核空間的方式實現跨進程通信至少需要2次讀寫操作。那麼Binder又是如何做到更高的通信效率呢,這裏就需要先了解幾個概念:虛擬內存 和 內存映射(mmap)。

4.1 虛擬內存

我們知道程序代碼和數據需要被解釋在內存中才能得以運行,隨着互聯網的飛速發展,計算機中安裝的軟件越來越多,軟件所需要的內存也越來越大,如果將軟件全部加載到內存中很可能導致內存不足,虛擬內存的概念也就被提出了。

虛擬內存是將系統硬盤空間(ROM)與系統內存空間(RAM)聯合在一起供程序使用的技術,**在程序運行時,只把虛擬內存的一小部分存儲到內存,其餘都存儲在硬盤上(也就是說程序虛擬空間就等於實際物理內存加部分硬盤空間)。當被訪問的虛擬地址不在內存時,將需要的虛擬地址隨即被調入到內存;同時當系統內存緊張時,也可以把當前不用的虛擬內存換出到硬盤,來騰出物理內存空間。**系統如此周而復始地運轉——換入、換出,而用戶幾乎無法查覺,這都是拜虛擬內存機制所賜。

4.2 內存映射

說到內存映射可能第一反應會比較陌生,但是如果說mmap估計好多人就會一拍腦袋,原來是這個。近幾年各大廠商在技術演技及性能優化中頻繁應用到了mmap的特性,其優勢可以簡單總結爲兩個:快 & 安全。

映射,顧名思義是將兩個事物建立一種一一對應的關係,當改動其中任意一個時會對兩者造成同樣的影響。舉個栗子就像人和影子,無論人做出什麼樣的動作影子都會跟着做出同樣的動作。

內存映射就是將進程中部分虛擬內存區域映射到物理內存上,操作程序的虛擬內存就是操作這塊物理內存,這樣就不用經過內核轉換,在整個內存映射過程中,並沒有實際的數據拷貝,只是邏輯上放入了內存,其餘的操作將又系統調用mmap()來實現,所以映射的整體效率非常高。

  • 文件爲例:把硬盤文件內容映射到程序的虛擬內存中,通過對虛擬內存的讀取修改就是對文件的讀取修改,這樣進程就可以像訪問普通內存一樣訪問文件,而不用調用read()、write()等操作了

  • 進程爲例:分別把進程A和進程B的虛擬內存區域的一塊與共享內存C建立映射關係,假如進程A對自己的這塊虛擬內存區域進行修改,那麼也會映射到進程B的虛擬內存塊;因爲這兩個進程都映射到了同一個共享內存C,所以A的修改對於B是可見的

4.3 Binder 組成

在理解了 Binder 底層通信原理後,再來看看 Binder 到底是什麼,首先得弄清楚一個概念,這裏說的 Binder 並不僅僅是平常所說的某一個 Binder 類,而是基於 Binder 進行 IPC 的整套框架結構。

一次完整的進程間通信必然至少包含兩個進程,在 Binder 中,通常我們稱通信的雙方分別爲客戶端進程(Client)和服務端進程(Server),由此也可以看出 BInder 的設計理念其實是基於C/S架構。

以下爲 Binder 的主要組成組件:

  • Binder 驅動

    一種虛擬的設備驅動,和硬件沒有關係,只是實現方式和設備驅動是一樣的,在設備目錄 /dev 下注冊一個 Binder 節點,提供標準文件操作,它是連接 Client 、Server、ServerManager的橋樑。

  • Service Manager

    管理進程註冊與查詢,保存進程信息。每個對外公開服務的進程都需要到這裏註冊,註冊成功會被分配一個唯一id。

  • Client

    調用 Server 的進程。

  • Server

    提供 Server 的進程。

爲了便於理解這裏可以將每個組件根據功能性類比爲我們常見的一次網絡請求:

Server —— 後臺服務器

Client —— 請求客戶端

ServiceManager —— DNS域名服務器

Binder 驅動 —— 路由器

通常我們訪問一個網頁的步驟是這樣的:

  1. 首先後臺服務器上搭建好我們的 API 接口,定義 ip 地址 10.xx.xx.xx,將 ip 地址綁定一個對外的穩定域名,如:https://blog.csdn.net/ccw0054,並且在DNS域名服務器中進行註冊。
  2. 在客戶端上打開瀏覽器,輸入我們的接口域名,按下回車開始請求。
  3. 請求被髮送到路由器,但是並沒有辦法通過域名地址直接找到我們要訪問的服務器。
  4. 因此路由器需要首先訪問 DNS 域名服務器,域名服務器中保存了對應的 ip 地址,然後通過這個 ip 地址最終訪問到對應的後臺服務器上搭建好的接口。

除此之外,由於 ServiceManager 本質上也是一個進程,Server 是另一個進程,而 Server需要向 ServiceManager 中註冊 Binder 的操作自然也屬於 IPC。ServiceManager 和其他進程同樣採用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 實體,其他進程都是 Client,需要通過這個 Binder 的引用來實現 Binder 的註冊,查詢和獲取。ServiceManager 提供的 Binder 比較特殊,它沒有名字也不需要註冊。當一個進程使用 BINDER_SET_CONTEXT_MGR 命令將自己註冊成 ServiceManager 時 Binder 驅動會自動爲它創建 Binder 實體。

將 Binder 代入翻譯過來是這樣的:

  1. 首先,一個進程使用 BINDER_SET_CONTEXT_MGR 命令通過 Binder 驅動將自己註冊成爲 ServiceManager。
  2. Server 通過驅動向 ServiceManager 中註冊 Binder(Server 中的 Binder 實體),表明可以對外提供服務。驅動爲這個 Binder 創建位於內核中的實體節點以及 ServiceManager 對實體的引用,將名字以及新建的引用打包傳給 ServiceManager,ServiceManger 將其填入查找表。
  3. Client 通過名字,在 Binder 驅動的幫助下從 ServiceManager 中獲取到對 Binder 實體的引用,通過這個引用就能實現和 Server 進程的通信。

4.4 Binder 中的代理模式

Client、Server 藉助 Binder 驅動完成跨進程通信了。但是有個問題會讓我們困惑,A 進程是如何將自己的數據(如某個對象)傳遞給 B 進程的呢。

跨進程通信的過程都有 Binder 驅動的參與,因此在數據流經 Binder 驅動的時候驅動會對數據做一層轉換。當 A 進程想要獲取 B 進程中的 object 時,驅動並不會真的把 object 返回給 A,而是返回了一個跟 object 看起來一模一樣的代理對象 objectProxy,這個 objectProxy 具有和 object 一摸一樣的方法,但是這些方法並沒有 B 進程中 object 對象那些方法的能力,這些方法只需要把請求參數交給驅動,由驅動再去調用真正的 object。

4.5 Binder Java 類

  • IBinder: IBinder 是一個接口,代表了一種跨進程通信的能力。只要實現了這個藉口,這個對象就能跨進程傳輸。
  • IInterface: IInterface 代表的就是 Server 進程對象具備什麼樣的能力(能提供哪些方法,其實對應的就是 AIDL 文件中定義的接口)
  • Binder: Java 層的 Binder 類,代表的其實就是 Binder 本地對象。BinderProxy 類是 Binder 類的一個內部類,它代表遠程進程的 Binder 對象的本地代理;這兩個類都繼承自 IBinder, 因而都具有跨進程傳輸的能力;實際上,在跨越進程的時候,Binder 驅動會自動完成這兩個對象的轉換。
  • Stub: AIDL 的時候,編譯工具會給我們生成一個名爲 Stub 的靜態內部類;這個類繼承了 Binder, 說明它是一個 Binder 本地對象,它實現了 IInterface 接口,表明它具有 Server 承諾給 Client 的能力;Stub 是一個抽象類,具體的 IInterface 的相關實現需要開發者自己實現。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章