Binder機制原理簡述

本文只作爲了解 Binder機制的私人筆記,拜讀了很多大神的博客,從中提煉總結如下文:

參考自大神 https://zhuanlan.zhihu.com/p/35519585
參考自大神 https://blog.csdn.net/carson_ho/article/details/73560642

一 前言

二 Linux傳統的進程間通信原理簡述
  • 2.1 Liunx 中跨進程通信主要有三個關鍵信息
  • 2.2 Linux 下的傳統 IPC 通信原理
三 Binder 跨進程通信原理
四 Binder 跨進程通信實現
  • 4.1 Binder 跨進程通信實現簡述
  • 4.2 Binder 跨進程通信實現步驟

一 前言

因爲很多人說 Binder就是跨進程通信方式,但是 Android 又是基於Linux的操作系統,爲什麼捨棄了Linux已經很成熟的管道、消息隊列、共享內存和 Socket 等IPC 機制 而使用所謂的Binder機制呢? 所以在瞭解 Binder機制之前,我簡單瞭解了一下Linux 的 IPC 機制(Linux傳統的進程間通信原理 )作爲對比。

二 Linux傳統的進程間通信原理簡述

在瞭解 Linux傳統的進程間通信原理 之前先了解一下Liunx 中跨進程通信主要有三個關鍵信息

2.1 Liunx 中跨進程通信主要有三個關鍵信息:

關鍵信息1 : 進程隔離
關鍵信息2 : 進程空間劃分:用戶空間(User Space)/內核空間(Kernel Space)
關鍵信息3 : 系統調用:用戶態/內核態

關鍵信息1- 進程隔離 簡述:

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

關鍵信息2-進程空間劃分 簡述:

  • 現在操作系統都是採用的虛擬存儲器,對於 32 位系統而言,它的尋址空間(虛擬存儲空間)就是 2 的 32 次方,也就是 4GB。操作系統的核心是內核,獨立於普通的應用程序,可以訪問受保護的內存空間,也可以訪問底層硬件設備的權限。爲了保護用戶進程不能直接操作內核,保證內核的安全,操作系統從邏輯上將虛擬空間劃分爲用戶空間(User Space)和內核空間(Kernel Space)。針對 Linux 操作系統而言,將最高的 1GB 字節供內核使用,稱爲內核空間;較低的 3GB 字節供各進程使用,稱爲用戶空間。簡單的說就是,內核空間(Kernel)是系統內核運行的空間,用戶空間(User Space)是用戶程序運行的空間。爲了保證安全性,它們之間是隔離的。如下圖:
    在這裏插入圖片描述

關鍵信息3-系統調用

  • 雖然從邏輯上進行了用戶空間和內核空間的劃分,但不可避免的用戶空間需要訪問內核資源,比如文件操作、訪問網絡等等。爲了突破隔離限制,就需要藉助系統調用來實現。系統調用是用戶空間訪問內核空間的唯一方式,保證了所有的資源訪問都是在內核的控制下進行的,避免了用戶程序對系統資源的越權訪問,提升了系統安全性和穩定性。Linux 使用級別保護機制:0 級供系統內核使用,3 級供用戶程序使用。當一個任務(進程)執行系統調用而陷入內核代碼中執行時,稱進程處於內核運行態(內核態)。此時處理器處於特權級最高的(0級)內核代碼中執行。當進程處於內核態時,執行的內核代碼會使用當前進程的內核棧。每個進程都有自己的內核棧。當進程在執行用戶自己的代碼的時候,我們稱其處於用戶運行態(用戶態)。此時處理器在特權級最低的(3級)用戶代碼中運行。系統調用主要通過如下兩個函數來實現:

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

三個關鍵信息彙總如下圖:

在這裏插入圖片描述

2.2 Linux 下的傳統 IPC 通信原理

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

通常的做法是消息發送方將要發送的數據存放在內存緩存區中,通過系統調用進入內核態。然後內核程序在內核空間分配內存,開闢一塊內核緩存區,調用 copyfromuser() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中。同樣的,接收方進程在接收數據時在自己的用戶空間開闢一塊內存緩存區,然後內核程序調用 copytouser() 函數將數據從內核緩存區拷貝到接收進程的內存緩存區。這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,我們稱完成了一次進程間通信。如下圖:
在這裏插入圖片描述這種傳統的 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 機制一樣,先將數據從發送方進程拷貝到內核緩存區,然後再將數據從內核緩存區拷貝到接收方進程,通過兩次拷貝來實現嗎?答案是否定的。我們直接對比一下各類IPC的實現方式。

在這裏插入圖片描述問題 :發現 Binder 機制 的數據拷貝次數是0,那麼 Binder 是怎麼實現 0拷貝次數的呢?

這就就要說到 Linux 的另一個概念:內存映射。

  • Binder IPC 機制中涉及到的內存映射通過 mmap() 來實現,mmap() 是操作系統中一種內存映射的方法。內存映射簡單的講就是將用戶空間的一塊內存區域映射到內核空間。映射關係建立後,用戶對這塊內存區域的修改可以直接反應到內核空間;反之內核空間對這段區域的修改也能直接反應到用戶空間。內存映射能減少數據拷貝次數,實現用戶空間和內核空間的高效互動。兩個空間各自的修改能直接反映在映射的內存區域,從而被對方空間及時感知。也正因爲如此,內存映射能夠提供對進程間通信的支持。

Binder IPC 正是基於內存映射(mmap)來實現的,但是 mmap() 通常是用在有物理介質的文件系統上的。比如進程中的用戶區域是不能直接和物理設備打交道的,如果想要把磁盤上的數據讀取到進程的用戶區域,需要兩次拷貝(磁盤–>內核空間–>用戶空間);通常在這種場景下 mmap() 就能發揮作用,通過在物理介質和用戶空間之間建立映射,減少數據的拷貝次數,用內存讀寫取代I/O讀寫,提高文件讀取效率。而 Binder 並不存在物理介質,因此 Binder 驅動使用 mmap() 並不是爲了在物理介質和用戶空間之間建立映射,而是用來在內核空間創建數據接收的緩存空間。

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

  • 步驟1 : 首先 Binder 驅動在內核空間創建一個數據接收緩存區;

  • 步驟2: 接着在內核空間開闢一塊內核緩存區,建立內核緩存區和內核中數據接收緩存區之間的映射關係,以及內核中數據接收緩存區和接收進程用戶空間地址的映射關係;

  • 步驟3: 發送方進程通過系統調用 copyfromuser() 將數據 copy 到內核中的內核緩存區,由於內核緩存區和接收進程的用戶空間存在內存映射,因此也就相當於把數據發送到了接收進程的用戶空間,這樣便完成了一次進程間的通信。

如圖:
在這裏插入圖片描述

四 Binder 跨進程通信實現

4.1 Binder 跨進程通信實現簡述

Binder 通信模型是由一系列組件組成的,具體包括:Client、Server、ServiceManager、Binder 驅動四個組件。其中 Client、Server、Service Manager 運行在用戶空間,Binder 驅動運行在內核空間。

  • Client進程: 使用服務的進程
  • Server進程: 提供服務的進程
  • ServiceManager進程:管理 Server 的註冊和查詢
  • Binder 驅動 :是連接 Client進程、Server進程、ServiceManager進程之間的橋樑

Client、Server、ServiceManager、Binder 驅動這幾個組件在通信過程中扮演的角色就如同互聯網中服務器(Server)、客戶端(Client)、DNS域名服務器(ServiceManager)以及路由器(Binder 驅動)之前的關係。通常我們訪問一個網頁的步驟是這樣的:

  • 步驟1:首先在瀏覽器輸入一個地址,如 http://www.google.com 然後按下回車鍵。

  • 步驟2:由於沒有辦法通過域名地址直接找到我們要訪問的服務器,因此需要首先訪問 DNS 域名服務器,DNS 域名服務器中保存了 http://www.google.com 對應的 ip 地址 10.249.23.13,所以得到DNS服務器返回的IP地址:10.249.23.13。

  • 步驟3:向IP爲 10.249.23.13的服務器發起請求, 才能訪問到 http://www.google.com 對應的服務器。

  • 步驟4:應用服務器向客戶端返回請求的數據

在這裏插入圖片描述

4.2 Binder 跨進程通信實現步驟

說到底 Binder 跨進程通信本質來講就是能夠快速的實現進程之間的通訊,本着這一思想,下面列出 Binder 跨進程通信步驟

步驟一:註冊Server進程,即註冊服務。

  • 步驟1:Server進程 向 Binder驅動 發起服務註冊請求
  • 步驟2:Binder驅動 將 Server進程 的註冊請求轉發給 ServiceManager進程
  • 步驟3:ServiceManager進程 添加該 Server進程,即註冊Server服務
  • 結果:此時 ServiceManager進程 擁有了 Server進程 的信息

步驟二:獲取服務

  • 步驟1:Client進程 向 Binder驅動 發起獲取服務的請求,發送需要獲取的服務名稱
  • 步驟2:Binder驅動 將該請求轉發給 ServiceManager進程
  • 步驟3:ServiceManager進程 查找 Client進程 所需要的 Server進程服務
  • 步驟4:ServiceManager進程 通過 Binder驅動 將上述信息發送給 Client進程
  • 結果:至此 Client進程 與 Server進程 已經建立了鏈接

步驟三:使用服務

  • 步驟1:Binder驅動爲跨進程通信做準備,實現內存映射。即調用 mmap()系統函數

    • 步驟1.1: Binder驅動在內核空間創建 接收緩存區
    • 步驟1.2: Binder驅動實現地址映射關係,即根據 ServiceManager進程中註冊表找到 Server進程信息。並且將 內核緩存區 與 Server進程空間地址兩處空間地址同時映射到上面創建的 接收緩存區 。此時只是建立了空間映射,但並無數據傳輸。
  • 步驟2:Client進程 發送數據到 Server進程,即請求服務

    • 步驟 2.1: Client進程 通過系統調用 copy_from_user() 將數據發送拷貝到內核空間的 內核緩存區,拷貝結束後 Client進程被掛起。由於內核緩存區 與 Server進程空間 兩個空間 與內核數據緩存區之間存在內存映射,故此時相當於將數據拷貝到了 Server進程空間,即 Binder驅動 實現了 跨進程通訊。
    • 步驟 2.2:Binder驅動 通知 Server進程 執行解包
  • 步驟3:Server進程 根據 Client進程請求,調用目標方法

    • 步驟3.1:Server進程 接收到 Binder驅動通知後,Server進程從線程池中取出線程進行數據解包,並調用目標方法。
    • 步驟3.2:Server進程將最終結果寫入映射好的Server進程用戶空間
  • 步驟4:Server進程 將目標方法的執行結果返還給 Client進程

    • 步驟4.1:Server進程將最終結果寫入映射好的Server進程用戶空間,由於內核緩存區 與 Server進程空間 兩個空間 與內核數據緩存區之間存在內存映射。故相當於同時寫入到了內核緩存區以及數據接收緩存區。
    • 步驟4.2:Binder驅動 通知 Client進程 獲取返還結果(此時被掛起的Client進程被喚醒)
    • 步驟4.3: Client進程 通過系統調用:copy_to_user() 從內核緩存區中接收Server進程返回的數據。

過程如圖所示:
在這裏插入圖片描述
注意:

  • 注意1:
    Client、Server、Service Manager 運行在用戶空間,Binder 驅動運行在內核空間。其中 Binder驅動 & Service Manager進程 屬於 Android基礎架構(即系統已經實現好了),而Client 進程 和 Server 進程 屬於Android應用層(需要開發者自己實現)。
    在這裏插入圖片描述

  • 注意2:
    由於進程隔離,Client、Server 和 ServiceManager 均是通過系統調用 open、mmap 和 ioctl 來訪問設備文件 /dev/binder,從而實現與 Binder 驅動的交互來間接的實現跨進程通信。如下圖,虛線表示並非直接交互。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章