【Android 系統開發】_「核心技術」篇 -- Binder機制

Binder 概述

Binder 是一種進程間通訊機制,基於開源的 OpenBinder 實現;OpenBinder 起初由 Be Inc. 開發,後由 Plam Inc. 接手。從字面上來解釋 Binder 有膠水、粘合劑的意思,顧名思義就是粘和不一樣的進程,使之實現通訊。瀏覽器

爲何要理解 Binder?

通常Android應用開發不多直接用到跨進程信通訊(IPC),但若是你想知道:緩存

       🔰  App是如何啓動並初始化的?
       🔰  Activity的啓動過程是怎樣的?
       🔰  進程間是如何通訊的?
       🔰  AIDL的具體原理是什麼?
       🔰  衆多插件化框架的設計原理 等等安全

這些問題的背後都與 Binder 有莫大的關係,要弄懂上面這些問題理解 Bidner 通訊機制是必須的。服務器

咱們知道 Android 應用程序是由 Activity、Service、Broadcast Receiver 和 Content Provide 四大組件中的一個或者多個組成的。有時這些組件運行在同一進程,有時運行在不一樣的進程。這些進程間的通訊就依賴於 Binder IPC 機制。不只如此,Android 系統對應用層提供的各類服務如:ActivityManagerService、PackageManagerService 等都是基於 Binder IPC 機制來實現的。Binder 機制在 Android 中的位置很是重要,絕不誇張的說理解 Binder 是邁向 Android 高級工程的第一步。網絡

爲何是 Binder ?

Android 系統是基於 Linux 內核的,Linux 已經提供了 管道、消息隊列、共享內存Socket 等 IPC 機制。那爲何 Android 還要提供 Binder 來實現 IPC 呢?
主要是基於 性能穩定性安全性 幾方面的緣由!架構

性能

首先說說性能上的優點。框架

Socket:做爲一款通用接口,其傳輸效率低,開銷大,主要用在跨網絡的進程間通訊和本機上進程間的低速通訊。
消息隊列和管道:採用存儲-轉發方式,即數據先從發送方緩存區拷貝到內核開闢的緩存區中,而後再從內核緩存區拷貝到接收方緩存區,至少有兩次拷貝過程。
共享內存:雖然無需拷貝,但控制複雜,難以使用。
Binder:只須要一次數據拷貝,性能上僅次於共享內存。ide

IPC機制 數據拷貝次數
共享內存 0
Binder 1
管道、消息隊列、Socket 2

穩定性

再說說穩定性,Binder 基於 C/S 架構,客戶端(Client)有什麼需求就丟給服務端(Server)去完成,架構清晰、職責明確又相互獨立,天然穩定性更好。共享內存雖然無需拷貝,可是控制負責,難以使用。從穩定性的角度講,Binder 機制是優於共享內存的。函數

安全性

Android 做爲一個開放性的平臺,市場上有各種海量的應用供用戶選擇安裝,所以安全性對於 Android 平臺而言極其重要。做爲用戶固然不但願咱們下載的 APP 偷偷讀取個人通訊錄,上傳個人隱私數據,後臺偷跑流量、消耗手機電量。傳統的 IPC 沒有任何安全措施,徹底依賴上層協議來確保。首先傳統的 IPC 接收方沒法得到對方可靠的進程用戶ID/進程ID(UID/PID),從而沒法鑑別對方身份。Android 爲每一個安裝好的 APP 分配了本身的 UID,故而進程的 UID 是鑑別進程身份的重要標誌。傳統的 IPC 只能由用戶在數據包中填入 UID/PID,但這樣不可靠,容易被惡意程序利用。可靠的身份標識只有由 IPC 機制在內核中添加。其次傳統的 IPC 訪問接入點是開放的,只要知道這些接入點的程序均可以和對端創建鏈接,無論怎樣都沒法阻止惡意程序經過猜想接收方地址得到鏈接。同時 Binder 既支持實名 Binder,又支持匿名 Binder,安全性高。性能

傳統IPC通訊原理

瞭解 Linux IPC 相關的概念和原理有助於咱們理解 Binder 通訊原理。所以,在介紹 Binder 跨進程通訊原理以前,咱們先聊聊 Linux 系統下傳統的進程間通訊是如何實現。

基礎概念

進程隔離

簡單的說就是操做系統中,進程與進程間內存是不共享的。兩個進程就像兩個平行的世界,A 進程無法直接訪問 B 進程的數據,這就是進程隔離的通俗解釋。A 進程和 B 進程之間要進行數據交互就得采用特殊的通訊機制:進程間通訊(IPC)。

進程空間

如今操做系統都是採用的虛擬存儲器,對於 32 位系統而言,它的尋址空間(虛擬存儲空間)就是 2 的 32 次方,也就是 4GB。操做系統的核心是內核,獨立於普通的應用程序,能夠訪問受保護的內存空間,也能夠訪問底層硬件設備的權限。爲了保護用戶進程不能直接操做內核,保證內核的安全,操做系統從邏輯上將虛擬空間劃分爲用戶空間(User Space)和內核空間(Kernel Space)。針對 Linux 操做系統而言,將最高的 1GB 字節供內核使用,稱爲內核空間;較低的 3GB 字節供各進程使用,稱爲用戶空間。

系統調用

雖然從邏輯上進行了用戶空間和內核空間的劃分,但不可避免的用戶空間須要訪問內核資源,好比文件操做、訪問網絡等等。爲了突破隔離限制,就須要藉助系統調用來實現。系統調用是用戶空間訪問內核空間的惟一方式,保證了全部的資源訪問都是在內核的控制下進行的,避免了用戶程序對系統資源的越權訪問,提高了系統安全性和穩定性。

Linux 使用兩級保護機制:0 級供系統內核使用,3 級供用戶程序使用。

當一個任務(進程)執行系統調用而陷入內核代碼中執行時,稱進程處於內核運行態(內核態)。此時處理器處於特權級最高的(0級)內核代碼中執行。當進程處於內核態時,執行的內核代碼會使用當前進程的內核棧。每一個進程都有本身的內核棧。

當進程在執行用戶本身的代碼的時候,咱們稱其處於用戶運行態(用戶態)。此時處理器在特權級最低的(3級)用戶代碼中運行。

IPC通訊原理

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

一般的作法是消息發送方將要發送的數據存放在內存緩存區中,經過系統調用進入內核態。而後內核程序在內核空間分配內存,開闢一塊內核緩存區,調用 copyfromuser() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中。一樣的,接收方進程在接收數據時在本身的用戶空間開闢一塊內存緩存區,而後內核程序調用 copytouser() 函數將數據從內核緩存區拷貝到接收進程的內存緩存區。這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,咱們稱完成了一次進程間通訊

咱們來看下原理圖:

傳統IPC通訊方式.png

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

      ✨  一、性能低下,一次數據傳遞須要經歷:內存緩存區 --> 內核緩存區 --> 內存緩存區,須要 2 次數據拷貝;
      ✨  二、接收數據的緩存區由數據接收進程提供,可是接收進程並不知道須要多大的空間來存放將要傳遞過來的數據,所以只能開闢儘量大的內存空間或者先調用 API 接收消息頭來獲取消息體的大小,這兩種作法不是浪費空間就是浪費時間。

Binder跨進程通訊原理

理解了 Linux IPC 相關概念和通訊原理,接下來咱們正式介紹下 Binder IPC 的原理。

動態內核可加載模塊

正如前面所說,跨進程通訊是須要內核空間作支持的。傳統的 IPC 機制如管道、Socket 都是內核的一部分,所以經過內核支持來實現進程間通訊天然是沒問題的。可是 Binder 並非 Linux 系統內核的一部分,那怎麼辦呢?這就得益於 Linux 的 動態內核可加載模塊(Loadable Kernel Module,LKM)的機制;模塊是具備獨立功能的程序,它能夠被單獨編譯,可是不能獨立運行。它在運行時被連接到內核做爲內核的一部分運行。這樣,Android 系統就能夠經過動態添加一個內核模塊運行在內核空間,用戶進程之間經過這個內核模塊做爲橋樑來實現通訊。

在 Android 系統中,這個運行在內核空間,負責各個用戶進程經過 Binder 實現通訊的內核模塊就叫 Binder 驅動(Binder Dirver)。

那麼在 Android 系統中用戶進程之間是如何經過這個內核模塊(Binder 驅動)來實現通訊的呢?難道是和前面說的傳統 IPC 機制同樣,先將數據從發送方進程拷貝到內核緩存區,而後再將數據從內核緩存區拷貝到接收方進程,經過兩次拷貝來實現嗎?顯然不是,不然也不會有開篇所說的 Binder 在性能方面的優點了。這就涉及到 內存映射 的概念了。

內存映射

Binder IPC 機制中涉及到的內存映射經過 mmap() 來實現,mmap() 是操做系統中一種內存映射的方法。內存映射簡單的講就是將用戶空間的一塊內存區域映射到內核空間。映射關係創建後,用戶對這塊內存區域的修改能夠直接反應到內核空間;反以內核空間對這段區域的修改也能直接反應到用戶空間。

內存映射能減小數據拷貝次數,實現用戶空間和內核空間的高效互動。兩個空間各自的修改能直接反映在映射的內存區域,從而被對方空間及時感知。也正由於如此,內存映射可以提供對進程間通訊的支持。

Binder IPC 實現原理

Binder IPC 正是基於內存映射(mmap)來實現的,可是 mmap() 一般是用在有物理介質的文件系統上的。

好比進程中的用戶區域是不能直接和物理設備打交道的,若是想要把磁盤上的數據讀取到進程的用戶區域,須要兩次拷貝(磁盤-->內核空間-->用戶空間);一般在這種場景下 mmap() 就能發揮做用,經過在物理介質和用戶空間之間創建映射,減小數據的拷貝次數,用內存讀寫取代I/O讀寫,提升文件讀取效率。

而 Binder 並不存在物理介質,所以 Binder 驅動使用 mmap() 並非爲了在物理介質和用戶空間之間創建映射,而是用來在內核空間建立數據接收的緩存空間。

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

      ✨  一、首先 Binder 驅動在內核空間建立一個 數據接收緩存區
      ✨  二、接着在內核空間開闢一塊內核緩存區,創建 內核緩存區 內核中數據接收緩存區 之間的映射關係,以及 內核中數據接收緩存區 接收進程用戶空間地址 的映射關係;
      ✨  三、發送方進程經過系統調用 copyfromuser() 將數據 copy 到內核中的內核緩存區,因爲內核緩存區和接收進程的用戶空間存在內存映射,所以也就至關於把數據發送到了接收進程的用戶空間,這樣便完成了一次進程間的通訊。

咱們來看下原理圖:

Binder通訊原理.jpg

Binder 通訊模型

介紹完 Binder IPC 的底層通訊原理,接下來咱們看看實現層面是如何設計的。

一次完整的進程間通訊必然至少包含兩個進程,一般咱們稱通訊的雙方分別爲 客戶端進程 (Client)和 服務端進程 (Server),因爲進程隔離機制的存在,通訊雙方必然須要藉助 Binder 來實現。

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 驅動的交互來間接的實現跨進程通訊。

以下原理圖:

原理圖.jpg

Client、Server、ServiceManager、Binder 驅動這幾個組件在通訊過程當中扮演的角色就如同互聯網中服務器(Server)、客戶端(Client)、DNS域名服務器(ServiceManager)以及路由器(Binder 驅動)以前的關係。

一般咱們訪問一個網頁的步驟是這樣的:首先在瀏覽器輸入一個地址,如 https://www.google.com 而後按下回車鍵。可是並無辦法經過域名地址直接找到咱們要訪問的服務器,所以須要首先訪問 DNS 域名服務器,域名服務器中保存了 https://www.google.com 對應的 ip 地址 10.249.23.13,而後經過這個 ip 地址才能找到 https://www.google.com 對應的服務器。

以下圖所示:

google.jpg


Android Binder 設計與實現一文中對 Client、Server、ServiceManager、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 比較特殊,它沒有名字也不須要註冊。當一個進程使用 BINDERSETCONTEXT_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 通訊過程:

      ✨ 一、首先,一個進程使用 BINDERSETCONTEXT_MGR 命令經過 Binder 驅動將本身註冊成爲 ServiceManager;
      ✨ 二、Server 經過驅動向 ServiceManager 中註冊 Binder(Server 中的 Binder 實體),代表能夠對外提供服務。驅動爲這個 Binder 建立位於內核中的實體節點以及 ServiceManager 對實體的引用,將名字以及新建的引用打包傳給 ServiceManager,ServiceManger 將其填入查找表。
      ✨ 三、Client 經過名字,在 Binder 驅動的幫助下從 ServiceManager 中獲取到對 Binder 實體的引用,經過這個引用就能實現和 Server 進程的通訊。
咱們看到整個通訊過程都須要 Binder 驅動的接入。下圖能更加直觀的展示整個通訊過程(爲了進一步抽象通訊過程以及呈現上的方便,下圖咱們忽略了 Binder 實體及其引用的概念):

原理圖:

原理圖.jpg

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 進程,一次通訊就完成了。

原理圖:

原理圖.jpg

Binder 的完整定義

如今咱們能夠對 Binder 作個更加全面的定義了:

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

參考Blog

  01. https://www.jianshu.com/p/062...
  02. https://zhuanlan.zhihu.com/p/...
  03. https://blog.csdn.net/univers...
  04. https://blog.csdn.net/freekit...

相關文章
相關標籤/搜索