1. 程式人生 > >Android : 跟我學Binder --- (1) 什麼是Binder IPC?為何要使用Binder機制?

Android : 跟我學Binder --- (1) 什麼是Binder IPC?為何要使用Binder機制?

一、引言

  如果把Android系統比作一幅精美絕倫的畫,那Binder則是其濃墨重彩的獨特一筆。初步瞭解過的人應該知道Binder是Android核心程序間通訊(IPC:Internet Process Connection)手段之一,它是基於開源的 OpenBinder 實現,OpenBinder 起初由 Be Inc. 開發,後由 Plam Inc. 接手。從字面上來解釋 Binder 有膠水、粘合劑的意思,顧名思義就是粘和不同的程序,使之實現通訊。而日常開發中涉及到的如:AIDL、外掛化程式設計技術 等等,底層通訊實現都離不開Binder,所以如果想在“Android系統開發工程師”的頭銜前面加上“高階”兩個字,那理解、掌握Binder機制是必經之路。

 

二、Linux 下傳統的程序間通訊原理

  首先了解一下 Linux IPC 相關的概念和原理有助於理解 Binder 通訊原理。

  1.Liunx 中跨程序通訊涉及到的一些基本概念:

    程序隔離程序間不可直接相互訪問資源

      簡單的說就是作業系統中,程序與程序間記憶體是不共享的,兩個程序就像兩個平行的世界,A 程序沒法直接訪問 B 程序的資料,這就是程序隔離的通俗解釋。A 程序和 B 程序之間要進行資料互動就得采用特殊的通訊機制:程序間通訊(IPC)。

 

    ②程序空間劃分:使用者空間(User Space)/核心空間(Kernel Space)

      現在作業系統都是採用的虛擬儲存器,對於 32 位系統而言,它的定址空間(虛擬儲存空間)就是 2 的 32 次方,也就是 4GB。作業系統的核心是核心,獨立於普通的應用程式,可以訪問受保護的記憶體空間,也可以訪問底層硬體裝置的許可權。為了保護使用者程序不能直接操作核心,保證核心的安全,作業系統從邏輯上將虛擬空間劃分為使用者空間(User Space)和核心空間(Kernel Space)。針對 Linux 作業系統而言,將最高的 1GB 位元組供核心使用,稱為核心空間;較低的 3GB 位元組供各程序使用,稱為使用者空間,它們在自己的空間執行,相互隔離。

 

    ③系統呼叫:

使用者態/核心態

      雖然從邏輯上進行了使用者空間和核心空間的劃分,但不可避免的使用者空間需要訪問核心資源,比如檔案操作、訪問網路等等。為了突破隔離限制,就需要藉助系統呼叫來實現。系統呼叫是使用者空間訪問核心空間的唯一方式,保證了所有的資源訪問都是在核心的控制下進行的,避免了使用者程式對系統資源的越權訪問,提升了系統安全性和穩定性。當一個任務(程序)執行系統呼叫而陷入核心程式碼中執行時,稱程序處於核心執行態(核心態),執行的核心程式碼會使用當前程序的核心棧,每個程序都有自己的核心棧。當程序在執行使用者自己的程式碼的時候,則稱其處於使用者執行態(使用者態)。系統呼叫主要通過如下兩個函式來實現,在編寫Linux裝置驅動程式時經常用到:

      copy_from_user() //將資料從使用者空間拷貝到核心空間
      copy_to_user() //將資料從核心空間拷貝到使用者空間

  2.Linux 下的傳統 IPC 通訊原理:

 

    Linux的IPC通常做法是訊息傳送方將要傳送的資料存放在記憶體快取區中,通過系統呼叫進入核心態。然後核心程式在核心空間分配記憶體,開闢一塊核心快取區,呼叫 copy_from_user() 函式將資料從使用者空間的記憶體快取區拷貝到核心空間的核心快取區中。同樣的,接收方程序在接收資料時在自己的使用者空間開闢一塊記憶體快取區,然後核心程式呼叫 copy_to_user() 函式將資料從核心快取區拷貝到接收程序的記憶體快取區。這樣資料傳送方程序和資料接收方程序就完成了一次資料傳輸。

 

  但是這種傳統的 IPC 通訊方式有兩個顯著缺點:

 

  1. 一次資料傳遞需要經歷:記憶體快取區 --> 核心快取區 --> 記憶體快取區,需要 2 次資料拷貝,效率低,可能此時會想到共享記憶體方式,但是其難於控制不穩定;
  2. 接收資料的快取區由資料接收程序提供,但是接收程序並不知道需要多大的空間來存放將要傳遞過來的資料,因此只能開闢儘可能大的記憶體空間或者先呼叫 API 接收訊息頭來獲取訊息體的大小,對於手機這種資源緊張的嵌入式移動裝置來說無疑是巨大負擔;

 

  可見傳統IPC效率低,佔資源,除此之外還有安全、穩定性等缺點不足以勝任Android的核心程序間通訊方式,下面正式介紹Binder機制,看其有何過人之處。

 

三、為何要用Binder通訊機制?

  Android系統的核心Linux已經有很多程序間通訊的方式,比如:管道(Pipe)、訊號(Signal)和跟蹤(Trace)、插口(Socket)、報文佇列(Message)、共享記憶體(Share Memory)和訊號量(Semaphore)等 IPC 機制,那Android為何還要再實現一個Binder IPC 呢?主要是基於高效性穩定性安全性幾方面原因。

  1.高效對於手機行動通訊裝置來說,是基於Client-Server即C/S架構的通訊方式,而上面提到的Linux傳統IPC中只有socket支援Client-Server的通訊方式,但是socket作為一款通用介面,其傳輸效率低,開銷大,主要用在跨網路的程序間通訊和本機上程序間的低速通訊。訊息佇列和管道採用儲存-轉發方式,即資料先從傳送方快取區拷貝到核心開闢的快取區中,然後再從核心快取區拷貝到接收方快取區,至少有兩次拷貝過程。共享記憶體雖然無需拷貝,但控制複雜,難以使用。

IPC方式 資料拷貝次數
共享記憶體 0
Binder 1
Socket/管道/訊息佇列 2

  2.穩定性Binder 基於 C/S 架構,客戶端(Client)有什麼需求就丟給服務端(Server)去完成,架構清晰、職責明確又相互獨立,自然穩定性更好。所以從穩定性的角度講,Binder 機制是也優於記憶體共享的複雜控制缺點。

  3.安全Android 作為一個開放性的平臺,市場上有各類海量的應用供使用者選擇安裝,因此安全性對於 Android 平臺而言極其重要。作為使用者當然不希望我們下載的 APP 偷偷讀取我的通訊錄,上傳我的隱私資料,後臺偷跑流量、消耗手機電量。傳統的 IPC 沒有任何安全措施,完全依賴上層協議來確保。首先傳統的 IPC 接收方無法獲得對方可靠的程序使用者ID/程序ID(UID/PID),從而無法鑑別對方身份。Android 為每個安裝好的 APP 分配了自己的 UID,故而程序的 UID 是鑑別程序身份的重要標誌。傳統的 IPC 只能由使用者在資料包中填入 UID/PID,但這樣不可靠,容易被惡意程式利用。可靠的身份標識只有由 IPC 機制在核心中新增。其次傳統的 IPC 訪問接入點是開放的,只要知道這些接入點的程式都可以和對端建立連線,不管怎樣都無法阻止惡意程式通過猜測接收方地址獲得連線。同時 Binder 既支援實名 Binder,又支援匿名 Binder,安全性高。

    

  Binder優勢總結:

優勢 描述
效能 只需要一次資料拷貝,效能上僅次於共享記憶體
穩定性 基於 C/S 架構,職責明確、架構清晰,因此穩定性好
安全性 為每個 APP 分配 UID,程序的 UID 是鑑別程序身份的重要標誌

 

 

  另外上面講過Linux IPC的通訊原理,現在介紹一下Binder IPC 的通訊原理

  正如前面所說,跨程序通訊是需要核心空間做支援的。傳統的 IPC 機制如管道、Socket 都是核心的一部分,因此通過核心支援來實現程序間通訊自然是沒問題的。但是 Binder 並不是 Linux 系統核心的一部分,那怎麼辦呢?這就得益於 Linux 的動態核心可載入模組(Loadable Kernel Module,LKM)的機制;模組是具有獨立功能的程式,它可以被單獨編譯,但是不能獨立執行。它在執行時被連結到核心作為核心的一部分執行。這樣,Android 系統就可以通過動態新增一個核心模組執行在核心空間,使用者程序之間通過這個核心模組作為橋樑來實現通訊,此核心模組就叫 Binder 驅動(Binder Dirver)。

  那麼在 Android 系統中使用者程序之間是如何通過這個核心模組(Binder 驅動)來實現通訊的呢?難道是和前面說的傳統 IPC 機制一樣,先將資料從傳送方程序拷貝到核心快取區,然後再將資料從核心快取區拷貝到接收方程序,通過兩次拷貝來實現嗎?顯然不是,否則也不會有開篇所說的 Binder 在效能方面的優勢了。這就不得說到 Linux 下的另一個概念:記憶體對映(mmap):將使用者空間的一塊記憶體區域對映到核心空間。對映關係建立後,使用者對這塊記憶體區域的修改可以直接反應到核心空間;反之核心空間對這段區域的修改也能直接反應到使用者空間,記憶體對映能減少資料拷貝次數,實現使用者空間和核心空間的高效互動。兩個空間各自的修改能直接反映在對映的記憶體區域,從而被對方空間及時感知。

  但是 mmap() 通常是用在有物理介質的檔案系統上的,比如對flash的操作,因為程序中的使用者區域是不能直接和物理裝置打交道的,如果想要把flash上的資料讀取到程序的使用者區域,需要兩次拷貝(flash-->核心空間-->使用者空間),通常在這種場景下 mmap() 就能發揮作用,通過在物理介質和使用者空間之間建立對映,減少資料的拷貝次數,用記憶體讀寫取代I/O讀寫,提高檔案讀取效率。而 Binder 並不存在物理介質,因此 Binder 驅動使用 mmap() 並不是為了在物理介質和使用者空間之間建立對映,而是用來在核心空間建立資料接收的快取空間。

  一次完整的 Binder IPC 通訊過程通常是這樣:

  1. 首先 Binder 驅動在核心空間建立一個數據接收快取區;
  2. 接著在核心空間開闢一塊核心快取區,建立核心快取區核心中資料接收快取區之間的對映關係,以及核心中資料接收快取區接收程序使用者空間地址的對映關係;
  3. 傳送方程序通過系統呼叫 copy_from_user() 將資料 copy 到核心中的核心快取區,由於核心快取區和接收程序的使用者空間存在記憶體對映,因此也就相當於把資料傳送到了接收程序的使用者空間;

 

四、Binder通訊模型

  通過介紹、對比Android Binder IPC和Linux 傳統IPC之間的差異,Binder IPC的各優點已經凸顯出來。一次完整的程序間通訊必然至少包含兩個程序,通常我們稱通訊的雙方分別為客戶端程序(Client)和服務端程序(Server),由於程序隔離機制的存在,通訊雙方必然需要藉助 Binder 來實現。接下來看看實現層面是如何設計的?

 1. Client/Server/ServiceManager/驅動

  前面介紹過,Binder 是基於 C/S 架構的。由一系列的元件組成,包括 Client、Server、ServiceManager、Binder 驅動。其中 Client、Server、Service Manager 執行在使用者空間,Binder 驅動執行在核心空間。其中 Service Manager 和 Binder 驅動由系統提供,而 Client、Server 由應用程式來實現。Client、Server 和 ServiceManager 均是通過系統呼叫 open、mmap 和 ioctl 來訪問裝置檔案 /dev/binder,從而實現與 Binder 驅動的互動來間接的實現跨程序通訊。

    Client、Server、ServiceManager、Binder 驅動這幾個元件在通訊過程中扮演的角色就如同網際網路中伺服器(Server)、客戶端(Client)、DNS域名伺服器(ServiceManager)以及路由器(Binder 驅動)之前的關係。通常我們訪問一個網頁的步驟是這樣的:首先在瀏覽器輸入一個地址,如 www.google.com 然後按下回車鍵。但是並沒有辦法通過域名地址直接找到我們要訪問的伺服器,因此需要首先訪問 DNS 域名伺服器,域名伺服器中儲存了 www.google.com 對應的 ip 地址 10.249.23.13,然後通過這個 ip 地址才能放到到 www.google.com 對應的伺服器。下面是Binder類比介紹:

Binder 驅動
  Binder 驅動就如同路由器一樣,是整個通訊的核心;驅動負責程序之間 Binder 通訊的建立,Binder 在程序之間的傳遞,Binder 引用計數管理,資料包在程序之間的傳遞和互動等一系列底層支援。 

ServiceManager 與實名 Binder
  ServiceManager 和 DNS 類似,作用是將字元形式的 Binder 名字轉化成 Client 中對該 Binder 的引用,使得 Client 能夠通過 Binder 的名字獲得對 Binder 實體的引用。註冊了名字的 Binder 叫實名 Binder,就像網站一樣除了有 IP 地址外還有自己的網址。Server 建立了 Binder,併為它起一個字元形式,可讀易記得名字,將這個 Binder 實體連同名字一起以資料包的形式通過 Binder 驅動傳送給 ServiceManager ,通知 ServiceManager 註冊一個名為“張三”的 Binder,它位於某個 Server 中。驅動為這個穿越程序邊界的 Binder 建立位於核心中的實體節點以及 ServiceManager 對實體的引用,將名字以及新建的引用打包傳給 ServiceManager。ServiceManger 收到資料後從中取出名字和引用填入查詢表。
  細心的讀者可能會發現,ServierManager 是一個程序,Server 是另一個程序,Server 向 ServiceManager 中註冊 Binder 必然涉及到程序間通訊。當前實現程序間通訊又要用到程序間通訊,這就好像蛋可以孵出雞的前提卻是要先找只雞下蛋!Binder 的實現比較巧妙,就是預先創造一隻雞來下蛋,ServiceManager 和其他程序同樣採用 Bidner 通訊,ServiceManager 是 Server 端,有自己的 Binder 實體,其他程序都是 Client,需要通過這個 Binder 的引用來實現 Binder 的註冊,查詢和獲取。ServiceManager 提供的 Binder 比較特殊,它沒有名字也不需要註冊。當一個程序使用 BINDER_SET_CONTEXT_MGR 命令將自己註冊成 ServiceManager 時 Binder 驅動會自動為它建立 Binder 實體(這就是那隻預先造好的那隻雞)。其次這個 Binder 實體的引用在所有 Client 中都固定為 0 而無需通過其它手段獲得。也就是說,一個 Server 想要向 ServiceManager 註冊自己的 Binder 就必須通過這個 0 號引用和 ServiceManager 的 Binder 通訊。類比網際網路,0 號引用就好比是域名伺服器的地址,你必須預先動態或者手工配置好。要注意的是,這裡說的 Client 是相對於 ServiceManager 而言的,一個程序或者應用程式可能是提供服務的 Server,但對於 ServiceManager 來說它仍然是個 Client。

Client 獲得實名 Binder 的引用
  Server 向 ServiceManager 中註冊了 Binder 以後, Client 就能通過名字獲得 Binder 的引用了。Client 也利用保留的 0 號引用向 ServiceManager 請求訪問某個 Binder: 我申請訪問名字叫張三的 Binder 引用。ServiceManager 收到這個請求後從請求資料包中取出 Binder 名稱,在查詢表裡找到對應的條目,取出對應的 Binder 引用作為回覆傳送給發起請求的 Client。從面向物件的角度看,Server 中的 Binder 實體現在有兩個引用:一個位於 ServiceManager 中,一個位於發起請求的 Client 中。如果接下來有更多的 Client 請求該 Binder,系統中就會有更多的引用指向該 Binder ,就像 Java 中一個物件有多個引用一樣。

至此,大致能總結出 Binder 通訊過程:

  ①首先,一個程序使用 BINDER_SET_CONTEXT_MGR 命令通過 Binder 驅動將自己註冊成為 ServiceManager;

  ②Server 通過驅動向 ServiceManager 中註冊 Binder(Server 中的 Binder 實體),表明可以對外提供服務。驅動為這個 Binder 建立位於核心中的實體節點以及 ServiceManager 對實體的引用,將名字以及新建的引用打包傳給 ServiceManager,ServiceManger 將其填入查詢表。

  ③Client 通過名字,在 Binder 驅動的幫助下從 ServiceManager 中獲取到對 Binder 實體的引用,通過這個引用就能實現和 Server 程序的通訊。

 

 2. Binder 通訊中的代理模式

  目前已經解釋清楚 Client、Server 藉助 Binder 驅動完成跨程序通訊的實現機制了,但是還有個問題比較困惑:A 程序想要 B 程序中某個物件(object)是如何實現的呢?畢竟它們分屬不同的程序,A 程序 沒法直接使用 B 程序中的 object。

  前面介紹跨程序通訊的過程都有 Binder 驅動的參與,因此在資料流經 Binder 驅動的時候驅動會對資料做一層轉換。當 A 程序想要獲取 B 程序中的 object 時,驅動並不會真的把 object 返回給 A,而是返回了一個跟 object 看起來一模一樣的代理物件 objectProxy,這個 objectProxy 具有和 object 一摸一樣的方法,但是這些方法並沒有 B 程序中 object 物件那些方法的能力,這些方法只需要把把請求引數交給驅動即可。對於 A 程序來說和直接呼叫 object 中的方法是一樣的。當 Binder 驅動接收到 A 程序的訊息後,發現這是個 objectProxy 就去查詢自己維護的表單,一查發現這是 B 程序 object 的代理物件。於是就會去通知 B 程序呼叫 object 的方法,並要求 B 程序把返回結果發給自己,當驅動拿到 B 程序的返回結果後就會轉發給 A 程序,一次通訊就完成了。

   到此可以對 Binder 做個更加全面的定義了:

  • 從程序間通訊的角度看,Binder 是一種程序間通訊的機制;
  • 從 Server 程序的角度看,Binder 指的是 Server 中的 Binder 實體物件;
  • 從 Client 程序的角度看,Binder 指的是對 Binder 代理物件,是 Binder 實體物件的一個遠端代理;
  • 從傳輸過程的角度看,Binder 是一個可以跨程序傳輸的物件,Binder 驅動會對這個跨越程序邊界的物件做一點點特殊處理,自動完成代理物件和本地物件之間的轉換。

 

-end-

 

下一篇:

參考:http://www.cnblogs.com/baronzhang/p/8784458.html