剖析binder

原文鏈接:https://www.jianshu.com/p/429a1ff3560c

what

背景知識IPC涉及的基本概念

  • 進程隔離 很好理解,不容進程不能直接訪問數據。不再贅述。
  • 進程空間劃分:用戶控件(UserSpace)/內核空間(Kernel Space)
  • 系統調用:用戶態/內核態

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

簡單的說就是,內核空間(Kernel)是系統內核運行的空間,用戶空間(User Space)是用戶程序運行的空間。爲了保證安全性,它們之間是隔離的。
在這裏插入圖片描述

系統調用: 用戶態和內核態

用戶空間訪問內核空間,比如文件操作,訪問網絡,爲了突破隔離限制,需要藉助系統調用來實現。系統調用是用戶控件訪問內核空間唯一的方式。保證所有資源訪問都在內核控制下進行,避免程序對系統資源的越權訪問。提升系統安全性穩定性。
Linnux使用兩級保護機制:0級供系統內核使用,3級供用戶程序使用。
當一個任務(進程)執行系統調用而陷入內核代碼中執行時,進程處於內核運行態(內核態),此時處理器處於特權級最高(0級)內核代碼中執行,進程處於內核態時,執行的內核代碼會使用當前進程的內核棧,每個進程都有自己的內核棧。

進程執行用戶自己打嗎的時候, 處於用戶運行態(用戶態),處理器在特權級最低的(3級)用戶代碼中運行。
copy_from_usr()//將數據從用戶控件拷貝到內核空間
copy_to_user()//反之

傳統IPC通信原理

通常的做法是消息發送方將要發送的數據存放在內存緩存區中,通過系統調用進入內核態。然後內核程序在內核空間分配內存,開闢一塊內核緩存區,調用 copy_from_user() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中。同樣的,接收方進程在接收數據時在自己的用戶空間開闢一塊內存緩存區,然後內核程序調用 copy_to_user() 函數將數據從內核緩存區拷貝到接收進程的內存緩存區。這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,我們稱完成了一次進程間通信。如下圖:
傳統IPC 原理這種傳統的 IPC 通信方式有兩個問題:

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

binder跨進程通信原理

動態內核可加載模塊 && 內存映射

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

在 Android 系統中,這個運行在內核空間,負責各個用戶進程通過 Binder 實現通信的內核模塊就叫** Binder 驅動(Binder Dirver)**。
那麼在 Android 系統中用戶進程之間是如何通過這個內核模塊(Binder 驅動)來實現通信的呢?難道是和前面說的傳統 IPC 機制一樣,先將數據從發送方進程拷貝到內核緩存區,然後再將數據從內核緩存區拷貝到接收方進程,通過兩次拷貝來實現嗎?顯然不是,否則也不會有開篇所說的 Binder 在性能方面的優勢了。

這就不得不通道 Linux 下的另一個概念:內存映射。
Binder IPC 機制中涉及到的內存映射通過 mmap() 來實現,mmap() 是操作系統中一種內存映射的方法。內存映射簡單的講就是將用戶空間的一塊內存區域映射到內核空間。映射關係建立後,用戶對這塊內存區域的修改可以直接反應到內核空間;反之內核空間對這段區域的修改也能直接反應到用戶空間。

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

Binder IPC 實現原理

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

比如進程中的用戶區域是不能直接和物理設備打交道的,如果想要把磁盤上的數據讀取到進程的用戶區域,需要兩次拷貝(磁盤–>內核空間–>用戶空間);通常在這種場景下 mmap() 就能發揮作用,通過在物理介質和用戶空間之間建立映射,減少數據的拷貝次數,用內存讀寫 取代I/O讀寫,提高文件讀取效率。

爲什麼內存映射要比IO讀寫更加快速?看這篇文章
https://blog.csdn.net/im_cheer/article/details/78409573

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

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

  1. 首先 Binder 驅動在內核空間創建一個數據接收緩存區;
  2. 接着在內核空間開闢一塊內核緩存區,建立內核緩存區和內核中數據接收緩存區之間的映射關係,以及內核中數據接收緩存區和接收進程用戶空間地址的映射關係;
  3. 發送方進程通過系統調用 copy_from_user() 將數據 copy 到內核中的內核緩存區,由於內核緩存區和接收進程的用戶空間存在內存映射,因此也就相當於把數據發送到了接收進程的用戶空間,這樣便完成了一次進程間的通信。
    在這裏插入圖片描述### binder通信模型

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 對應的服務器。

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